Compare commits

..

No commits in common. "49faed4fcb98bf807cab3d171925cac54daf75e9" and "5208496124f204c48307dac13209ecf3d1c234f8" have entirely different histories.

37 changed files with 390 additions and 494 deletions

View File

@ -1,10 +0,0 @@
---
import { leftTrimDedent } from "./utils";
import { thp_highlighter } from "../lexer/highlighter";
const { thpcode } = Astro.props;
const html_code = thp_highlighter(leftTrimDedent(thpcode).join("\n"));
---
<pre
class="language-thp"><code class="language-thp" set:html={html_code} /><span class="absolute top-2 right-2 inline-block text-sm select-none opacity-75">thp</span></pre>

View File

@ -1,11 +1,13 @@
---
import Code from "./Code.astro"
import { trimAndDedent } from "./utils";
const { thpcode } = Astro.props;
---
<div class="flex h-full py-8 items-center">
<div class="p-4 w-full">
<Code thpcode={thpcode} />
<pre
class="language-thp"
data-language="thp"><code class="language-thp">{trimAndDedent(thpcode).join("\n")}</code></pre>
</div>
</div>

View File

@ -2,7 +2,7 @@
import { lex } from "../lexer/lexer";
import type { Instruction } from "../thp_machine/machine_parser";
import { parse_str } from "../thp_machine/machine_parser";
import { leftTrimDedent } from "./utils";
import { trimAndDedent } from "./utils";
const { code, steps } = Astro.props;
function highlightCode(lines: Array<string>): string {
@ -31,7 +31,7 @@ function highlightCode(lines: Array<string>): string {
return outLines.join("\n");
}
const codeHtml = highlightCode(leftTrimDedent(code));
const codeHtml = highlightCode(trimAndDedent(code));
let instructionSet: Array<Array<Instruction>>;
try {
instructionSet = parse_str(steps);

View File

@ -1,133 +0,0 @@
import { expect, test } from 'vitest'
import { leftTrimDedent } from "./utils"
test("should trim empty string", () => {
const input = ``;
expect(leftTrimDedent(input)).toEqual([""]);
})
test("should work on a single line", () => {
const input = `hello`
expect(leftTrimDedent(input)).toEqual([
"hello"
]);
})
test("should trim a single line", () => {
const input = ` hello`
expect(leftTrimDedent(input)).toEqual([
"hello"
]);
})
test("should trim multiple lines", () => {
const input = ` hello\n world`
expect(leftTrimDedent(input)).toEqual([
"hello",
"world"
]);
})
test("should trim multiple lines without indentation", () => {
const input = `hello\nworld`
expect(leftTrimDedent(input)).toEqual([
"hello",
"world"
]);
})
test("should consume only whitespace", () => {
const input = ` hello\nworld`;
try {
const res = leftTrimDedent(input);
expect(res).not.toEqual([
"hello",
"rld",
]);
} catch (e) {
expect(e).toBeInstanceOf(Error);
expect(e).toHaveProperty("message", "Invalid indentation while trimming: Expected 2 spaces, got 0");
}
})
test("should preserve deeper indentation", () => {
const input = ` hello\n world`
expect(leftTrimDedent(input)).toEqual([
"hello",
" world",
]);
})
test("should ignore empty lines", () => {
const input = ` hello\n\n world`
expect(leftTrimDedent(input)).toEqual([
"hello",
"",
"world",
]);
})
test("should ignore lines with only whitespace", () => {
const input = ` hello\n \n \n world`
expect(leftTrimDedent(input)).toEqual([
"hello",
"",
" ",
"world",
]);
})
test("should trim multiple without indentation", () => {
const input = `hello\nworld\n!`
expect(leftTrimDedent(input)).toEqual([
"hello",
"world",
"!",
]);
})
test("should ignore empty first line", () => {
const input = `
hello
world`;
expect(leftTrimDedent(input)).toEqual([
"hello",
"world",
]);
})
test("should ignore empty first line 2", () => {
const input = `
hello
world`;
expect(leftTrimDedent(input)).toEqual([
"hello",
"",
"world",
]);
})
test("should ignore empty last line", () => {
const input = `
hello
world
`;
expect(leftTrimDedent(input)).toEqual([
"hello",
"world",
]);
});

View File

@ -5,16 +5,15 @@
* - Picks the indentation level from the first non-white line
* - Dedents the following lines
*/
export function leftTrimDedent(input: string): Array<string> {
export function trimAndDedent(input: string): Array<string> {
let lines = input.split("\n");
let output: Array<string> = [];
// Ignore first line
if (lines[0] === "" && lines.length > 1) {
// Remove empty lines at the start
while (lines[0] === "") {
lines = lines.slice(1);
}
// Get indentation level of the first line
// Get indentation level
let indentationLevel = 0;
for (const char of lines[0]!) {
if (char === " ") {
@ -24,37 +23,48 @@ export function leftTrimDedent(input: string): Array<string> {
}
}
for (const line of lines) {
// Ignore empty lines
if (line === "") {
output.push("");
// Enforce indentation, or trim
for (let i = 0; i < lines.length; i += 1) {
// If the line is empty, continue
const characters = lines[i]!.split("");
if (characters.length === 0) {
continue;
}
output.push(trimWhitespace(line, indentationLevel));
// If all characters are whitespace, append just a newline
const nonWhitespace = characters.find((x) => x !== " ");
if (nonWhitespace === undefined) {
lines[i] = "";
continue;
}
// Enforce indentation
if (characters.length < indentationLevel) {
throw new Error("Invalid indentation while parsing THP code: " + lines[i]);
}
let currentIndentation = 0;
for (const c of characters) {
if (c === " ") { currentIndentation += 1 }
else {break;}
}
if (currentIndentation < indentationLevel) {
throw new Error("Invalid indentation while parsing THP code: " + lines[i]);
}
lines[i] = characters.slice(4).join("");
}
if (output.length > 1 && output[output.length - 1] === "") {
output = output.slice(0, -1);
}
return output;
}
function trimWhitespace(input: string, count: number): string {
let indentCount = 0;
for (const char of input) {
if (char === " ") {
indentCount += 1;
// Remove empty lines at the end
let endPosition = lines.length - 1;
while (true) {
if (lines[endPosition] === "") {
lines = lines.slice(0, -1);
endPosition -= 1;
} else {
break;
}
}
if (indentCount >= count || indentCount == input.length) {
return input.slice(count);
} else {
throw new Error(`Invalid indentation while trimming: Expected ${count} spaces, got ${indentCount}`);
}
}
return lines;
}

View File

@ -3,7 +3,8 @@ import Navbar from "../components/Navbar.astro";
import BaseLayout from "./BaseLayout.astro";
import TOC from "../components/TOC.astro";
const { headings } = Astro.props;
const {headings} = Astro.props;
---
<BaseLayout>
@ -40,4 +41,9 @@ const { headings } = Astro.props;
</div>
<div class="h-32"></div>
<script>
import {highlightOnDom} from "./thpHighlighter";
document.addEventListener("DOMContentLoaded", highlightOnDom);
</script>
</BaseLayout>

View File

@ -4,13 +4,7 @@ import BaseLayout from "./BaseLayout.astro";
import TOC from "../components/TOC.astro";
import Sidebar from "../components/Sidebar.astro";
const {
frontmatter,
headings,
posts: _posts,
indexSubpath,
basePath,
} = Astro.props;
const { frontmatter, headings, posts: _posts, indexSubpath, basePath } = Astro.props;
const posts: Record<string, any>[] = _posts;
const indexPage = posts.find((post) => post.file.endsWith(indexSubpath));
@ -36,10 +30,8 @@ if (pagesIndex === undefined) {
function validateEntry(entry: PageEntry, basePath: string) {
if (!entry.children) {
// Attempt to get the page title from frontmatter
const pageData = posts.find(
(post) =>
post.file.endsWith(entry.path + ".md") ||
post.file.endsWith(entry.path + ".mdx"),
const pageData = posts.find((post) =>
post.file.endsWith(entry.path + ".md") || post.file.endsWith(entry.path + ".mdx"),
);
if (pageData === undefined) {
@ -120,8 +112,7 @@ for (const entry of pagesIndex) {
document.addEventListener("DOMContentLoaded", () => {
let current_uri = window.location.pathname;
const sidebar = document.getElementById("sidebar")!
.children[0]! as HTMLElement;
const sidebar = document.getElementById("sidebar")!.children[0]! as HTMLElement;
const links = sidebar.querySelectorAll("a");
for (const link of [...links]) {
if (link.getAttribute("href") === current_uri) {

View File

@ -1,5 +1,38 @@
import { thp_highlighter, CodeJar } from "../lexer/highlighter";
/**
* Highlights all THP code snippets mounted in the DOM with class .language-thp
*
* It assumes that the code is inside: <pre class="language-thp"><code>...
*/
export function highlightOnDom() {
const code_elements = document.querySelectorAll("code.language-thp");
for (const e of [...code_elements]) {
const el = e as HTMLElement;
// Some elements with .language-thp don't want to be processed
if (e.getAttribute("data-disabled") !== null) {
continue;
}
const pre_parent = el.parentElement!;
const new_div = document.createElement("div");
let code = el.innerHTML;
// Replace all &lt; with < and all &gt; with >
code = code.replace(/&lt;/g, "<").replace(/&gt;/g, ">");
el.parentElement!.className = "language-thp";
pre_parent.removeChild(el);
pre_parent.appendChild(new_div);
// Create and append a language name indicator
CodeJar(new_div, thp_highlighter, {
tab: " ",
}).updateCode(code);
}
const pre_elements = document.querySelectorAll("pre");
for (const pre_el of pre_elements) {
const language = pre_el.getAttribute("data-language");

View File

@ -1,6 +1,9 @@
import { lex } from "./lexer";
import {CodeJar as Codejar} from "codejar";
export function thp_highlighter(editor: any) {
let code: string = editor.textContent;
export function thp_highlighter(code: string) {
let tokens = lex(code);
let highlighted_code = "";
@ -9,5 +12,7 @@ export function thp_highlighter(code: string) {
highlighted_code += `<span class="token ${token.token_type}">${token.v}</span>`;
}
return highlighted_code;
editor.innerHTML = highlighted_code;
}
export const CodeJar = Codejar;

View File

@ -2,28 +2,6 @@
import BaseLayout from "../layouts/BaseLayout.astro";
import Navbar from "../components/Navbar.astro";
import HeroSection from "../components/HeroSection.astro";
import { thp_highlighter } from "../lexer/highlighter";
import { leftTrimDedent } from "../components/utils";
const thpcode = `union Animal {
Dog(String),
Cat(String, Int),
}
val cat_name = Post::get("cat_name") ?? "Michifu"
val my_cat = Animal::Cat(cat_name, 9)
try change_fur_color(cat) else die("Not a cat")
match random_animal()
case Dog(name)
{
print("{name} has 1 live ")
}
case Cat(name, lives)
{
print("{name} has {lives}")
}`;
---
<BaseLayout>
@ -117,9 +95,7 @@ case Cat(name, lives)
</g>
</svg>
<div class="h-1"></div>
<div id="editor" class="font-mono language-thp">
<pre set:html={thp_highlighter(leftTrimDedent(thpcode).join("\n"))}></pre>
</div>
<div id="editor" class="font-mono language-thp"></div>
</div>
</div>
</div>
@ -218,4 +194,38 @@ case Cat(name, lives)
<br>
Nullable types must be explicitly checked before using them.
</HeroSection>
<script>
import { thp_highlighter, CodeJar } from "../lexer/highlighter";
let jar = CodeJar(document.getElementById("editor")!, thp_highlighter, {
tab: " ",
});
jar.updateCode(
`union Animal {
Dog(String),
Cat(String, Int),
}
val cat_name = Post::get("cat_name") ?? "Michifu"
val my_cat = Animal::Cat(cat_name, 9)
try change_fur_color(cat) else die("Not a cat")
match random_animal()
case Dog(name)
{
print("{name} has 1 live ")
}
case Cat(name, lives)
{
print("{name} has {lives}")
}`,
);
</script>
<script>
import { highlightOnDom } from "../layouts/thpHighlighter";
document.addEventListener("DOMContentLoaded", highlightOnDom);
</script>
</BaseLayout>

View File

@ -2,7 +2,6 @@
layout: ../../../layouts/DocsLayout.astro
title: Comments
---
import Code from "../../../components/Code.astro"
# Comments
@ -13,12 +12,12 @@ THP supports single and multi line comments:
Begin with double slash `//` and continue until the end of the line.
<Code thpcode={`
```thp
// This is a single line comment
print("hello!")
print("the result is {5 + 5}") // This will print 10
`} />
```
## Multi line
@ -27,19 +26,19 @@ These begin with `/*` and end with `*/`. Everything in between is ignored.
Multi line comments can be nested in THP.
<Code thpcode={`
```thp
/*
This is a
multiline comment
*/
`} />
```
<Code thpcode={`
```thp
/*
Multiline comments
can be /* nested */
*/
`} />
```
## Documentation comments

View File

@ -2,7 +2,6 @@
layout: ../../../layouts/DocsLayout.astro
title: Datatypes
---
import Code from "../../../components/Code.astro"
# Datatypes
@ -15,7 +14,7 @@ The following are basic datatypes.
Same as php int
<Code thpcode={`
```thp
Int age = 32
// Hexadecimal numbers start with 0x
Int red = 0xff0000
@ -25,10 +24,10 @@ Int permissions = 0o775
Int char_code = 0b01000110
// IMPORTANT!
// Since Octal starts with \`0o\`, using just a leading 0
// Since Octal starts with `0o`, using just a leading 0
// will result in a decimal!
Int not_octal = 032 // This is 32, not 26
`} />
```
## Float
@ -36,10 +35,10 @@ Int not_octal = 032 // This is 32, not 26
Same as php float
<Code thpcode={`
```thp
Float pi = 3.141592
Float light = 2.99e+8
`} />
```
## String
@ -47,15 +46,15 @@ Float light = 2.99e+8
THP strings use **only** double quotes. Single quotes are
used elsewhere.
<Code thpcode={`
```thp
String name = "Rose"
`} />
```
Strings have interpolation with `{}`.
<Code thpcode={`
```thp
print("Hello, {name}") // Hello, Rose
`} />
```
## Bool
@ -63,11 +62,10 @@ print("Hello, {name}") // Hello, Rose
THP booleans are `true` and `false`. They are case sensitive,
**only lowercase.**
<Code thpcode={`
```thp
Bool is_true = true
Bool is_false = false
// This is a compile error
val invalid = TRUE
`} />
```

View File

@ -3,7 +3,6 @@ layout: ../../../layouts/DocsLayout.astro
title: Hello world
---
import InteractiveCode from "../../../components/InteractiveCode.astro";
import Code from "../../../components/Code.astro"
# Hello, world!
@ -17,9 +16,9 @@ detailed later on.
To write a hello world program write the following code in a file:
<Code thpcode={`
```thp
print("Hello, world!")
`} />
```
Then run `thp hello.thp` from your terminal.
@ -35,11 +34,11 @@ echo("B");
echo("C");
```
<Code thpcode={`
```thp
print("A")
print("B")
print("C")
`} />
```
As a consequence of this, there can only be 1 statement per line.

View File

@ -2,7 +2,6 @@
layout: ../../../layouts/DocsLayout.astro
title: Operators
---
import Code from "../../../components/Code.astro"
# Operators
@ -11,7 +10,7 @@ Most of the PHP operators are present in THP.
## Numbers
<Code thpcode={`
```thp
var number = 322
number + 1
@ -25,48 +24,48 @@ number -= 1
number *= 1
number /= 1
number %= 2
`} />
```
**There are no prefix/postfix increment**, use `+=` or `-=`.
<Code thpcode={`
```thp
// Use
number += 1
// instead of
number++ // This is a compile error
`} />
number++ // This is a compile error
```
### Comparison
These operators will not do implicit type conversion. They can
only be used with same datatypes.
<Code thpcode={`
```thp
v1 < v2
v1 <= v2
v1 > v2
v1 >= v2
`} />
```
There is only `==` and `!=`. They are equivalent to `===` and `!==`.
<Code thpcode={`
```thp
v1 == v2
v1 != v2
`} />
```
### Bitwise
TBD
<Code thpcode={`
```thp
number and 1
number or 2
number xor 1
number xand 1
`} />
```
## Strings
@ -74,15 +73,15 @@ TBD.
Strings **do not use `.`** for concatenation. They use `+`.
<Code thpcode={`
```thp
"Hello " + "world."
`} />
```
However, the plus operator `+` does not implicitly convert types.
<Code thpcode={`
```thp
"Hello " + 322 // This is an error
`} />
```
## Boolean
@ -90,10 +89,10 @@ However, the plus operator `+` does not implicitly convert types.
These operators work **only with booleans**, they do not perform
type coercion.
<Code thpcode={`
```thp
c1 && c2
c1 || c2
`} />
```
## Ternary
@ -104,7 +103,7 @@ There is no ternary operator. See [Conditionals](/learn/flow-control/conditional
These are detailed in their section: [Nullable types](/learn/error-handling/null)
<Code thpcode={`
```thp
val person = some_fun()
person?.name
@ -115,7 +114,7 @@ if person?
{
person.name
}
`} />
```

View File

@ -3,7 +3,6 @@ layout: ../../../layouts/DocsLayout.astro
title: Variables
---
import Code from "../../../components/Code.astro"
# Variables
@ -24,27 +23,27 @@ As a regex: `[a-z_][a-zA-Z0-9_]*`
Defined with `val`, followed by a variable name and a value.
<Code thpcode={`
```thp
val surname = "Doe"
val year_of_birth = 1984
`} />
```
### Datatype annotation
Written after the `val` keyword but before the variable name.
<Code thpcode={`
```thp
val String surname = "Doe"
val Int year_of_birth = 1984
`} />
```
When annotating an immutable variable the `val` keyword is optional
<Code thpcode={`
```thp
// Equivalent to the previous code
String surname = "Doe"
Int year_of_birth = 1984
`} />
```
This means that if a variable only has a datatype, it is immutable.
@ -54,27 +53,28 @@ This means that if a variable only has a datatype, it is immutable.
Defined with `var`, followed by a variable name and a value.
<Code thpcode={`
```thp
var name = "John"
var age = 32
`} />
```
### Datatype annotation
Written after the `var` keywords but before the variable name.
<Code thpcode={`
```thp
var String name = "John"
var Int age = 32
`} />
```
When annotating a mutable variable the keyword `var` is still **required**.
<Code thpcode={`
```thp
// Equivalent to the previous code
var String name = "John"
var Int age = 32
`} />
```

View File

@ -2,12 +2,10 @@
layout: ../../../layouts/DocsLayout.astro
title: Anonymous classes
---
import Code from "../../../components/Code.astro"
# Anonymous classes
<Code thpcode={`
```thp
class Logger
{
pub fun log(String msg)
@ -27,9 +25,9 @@ setLogger(class
print(msg)
}
})
`} />
```
<Code thpcode={`
```thp
setLogger(class(Int param1) -> SomeClass(param1), SomeInterface
{
pub fun method()
@ -37,4 +35,4 @@ setLogger(class(Int param1) -> SomeClass(param1), SomeInterface
// code
}
})
`} />
```

View File

@ -2,7 +2,6 @@
layout: ../../../layouts/DocsLayout.astro
title: Classes
---
import Code from "../../../components/Code.astro"
# Classes
@ -14,26 +13,26 @@ Syntax and semantics heavily inspired by Kotlin.
To create an instance of a class call it as if it were a function,
without `new`.
<Code thpcode={`
```thp
val animal = Animal()
`} />
```
## Simple class
Classes are declared with the `class` keyword.
<Code thpcode={`
```thp
class Animal
val instance = Animal()
`} />
```
## Properties
Properties are declared with `var`/`val`.
They **must** declare their datatype.
<Code thpcode={`
```thp
class Person
{
// This is an error. Properties must declare their datatype,
@ -43,21 +42,21 @@ class Person
// This is correct
val String name = "Jane Doe"
}
`} />
```
Properties are private by default,
but can be made public with `pub`.
<Code thpcode={`
```thp
class Person
{
// Properties are private by default
val String name = "John Doe"
// To make a property public use \`pub\`
// To make a property public use `pub`
pub var Int age = 30
}
`} />
```
More information about how properties interact with the constructor
is found in the contructor section.
@ -67,7 +66,7 @@ is found in the contructor section.
Methods are declared with `fun`, as regular functions.
<Code thpcode={`
```thp
class Person
{
fun greet()
@ -75,11 +74,11 @@ class Person
print("Hello")
}
}
`} />
```
Methods are private by default, and are made public with `pub`.
<Code thpcode={`
```thp
class Person
{
// This method is private
@ -88,7 +87,7 @@ class Person
print("Hello from private method")
}
// Use \`pub\` to make a method public
// Use `pub` to make a method public
pub fun greet()
{
print("Hello from greet")
@ -98,14 +97,14 @@ class Person
val p = Person()
p.greet() //: Hello from greet
p.private_greet() // Compile time error. Private method.
`} />
```
## This
THP uses the dollar sign `$` as this. It is **required** when
using a class property/method.
<Code thpcode={`
```thp
class Person
{
val String name = "Jane Doe"
@ -121,7 +120,7 @@ class Person
print("Hello, I'm {person_name}")
}
}
`} />
```
## Static members
@ -135,17 +134,17 @@ Static members are detailed in their own page.
PHP only allows a single constructor, and so does THP.
The basic constructor has the syntax of function parameters.
<Code thpcode={`
```thp
// |this is the constructor |
class Animal(String fullname, Int age)
val a1 = Animal("Nal", 4)
`} />
```
The class properties can be declared in the constructor,
using the keywords `pub`, `var`, `val`:
<Code thpcode={`
```thp
class Animal(
// Since we are using val/var, these are promoted to class properties
val String fullname,
@ -161,7 +160,7 @@ class Animal(
val a1 = Animal("Nal", 4)
a1.say() //: My name is Nal and i'm 4 years old
`} />
```
By using this syntax you are declaring properties and assigning them
at the same time.
@ -174,14 +173,14 @@ The contructor parameters can also have default values.
The constructor is public by default. It can be made private/protected
like this:
<Code thpcode={`
```thp
class Animal
private constructor(
val String fullname,
var Int age,
)
{...}
`} />
```
### Derived properties
@ -189,19 +188,19 @@ private constructor(
You can declare properties whose values depend on values
on the constructor.
<Code thpcode={`
```thp
class Animal(
val String fullname,
)
{
// A property whose value depends on \`fullname\`
// A property whose value depends on `fullname`
// This is executed after the contructor
pub val Int name_length = $fullname.length
}
val a2 = Animal("Doa")
print(a2.name_length) //: 3
`} />
```
### Init block
@ -209,7 +208,7 @@ print(a2.name_length) //: 3
If you need to additional logic in the constructor you can
use a `init` block.
<Code thpcode={`
```thp
class Animal(
val String fullname,
)
@ -221,13 +220,13 @@ class Animal(
}
val a3 = Animal("Lola") //: Lola in construction
`} />
```
## Inheritance
<Code thpcode={`
```thp
// Base class
class Animal(var String name)
{
@ -242,13 +241,13 @@ class Cat(String name, Int lives)
extends Animal(name)
Cat("Michi", 9).say_name()
`} />
```
## Mutable methods
By default methods cannot mutate the state of the object.
<Code thpcode={`
```thp
class Animal(var String name)
{
pub fun set_name(String new_name)
@ -256,12 +255,12 @@ class Animal(var String name)
$name = new_name // Error: Cannot mutate $
}
}
`} />
```
To do so the method must be annotated. The caller must also
declare a mutable variable.
<Code thpcode={`
```thp
class Animal(var String name)
{
pub mut fun set_name(String new_name)
@ -272,13 +271,13 @@ class Animal(var String name)
var michi = Animal("Michifu")
michi.set_name("Garfield")
`} />
```
## Class constructor that may return an error
Working theory:
<Code thpcode={`
```thp
class Fish(
val String name,
var Int lives = 9,
@ -286,9 +285,9 @@ class Fish(
extends Animal(name)
throws Error
val fish_result = Fish("bubble") // fish_result will be a \`Result[Fish,Error]\`
val fish_result = Fish("bubble") // fish_result will be a `Result[Fish,Error]`
val fish = try fish_result
`} />
```

View File

@ -2,13 +2,12 @@
layout: ../../../layouts/DocsLayout.astro
title: Interfaces
---
import Code from "../../../components/Code.astro"
# Interfaces
<Code thpcode={`
```thp
interface Serializable
{
// Methods are always public in interfaces
@ -25,7 +24,7 @@ class Cat -> Serializable
}
}
`} />
```
No interface inheritance.

View File

@ -2,13 +2,12 @@
layout: ../../../layouts/DocsLayout.astro
title: Magic methods
---
import Code from "../../../components/Code.astro"
# Magic methods
Don't get special treatment.
<Code thpcode={`
```thp
class Cat
{
@ -18,13 +17,13 @@ class Cat
}
}
`} />
```
<Code thpcode={`
```thp
val option = Some("GAAA")
val Some(value) = option
val colors = Array("red", "green", "blue")
val Array()
`} />
```

View File

@ -2,14 +2,13 @@
layout: ../../../layouts/DocsLayout.astro
title: Static
---
import Code from "../../../components/Code.astro"
# Static in classes
## Class constants
<Code thpcode={`
```thp
class Cat
{
// Stateful code
@ -21,7 +20,7 @@ static Cat
}
print(Cat::CONSTANT)
`} />
```
## Static methods
@ -29,7 +28,7 @@ print(Cat::CONSTANT)
aka. plain, old functions
<Code thpcode={`
```thp
static Cat
{
fun static_method() -> Int
@ -39,7 +38,7 @@ static Cat
}
Cat::static_method()
`} />
```
## Static properties
@ -47,7 +46,7 @@ Cat::static_method()
aka. global variables
<Code thpcode={`
```thp
static Cat
{
pub var access_count = 0
@ -56,7 +55,7 @@ static Cat
print(Cat::access_count) // 0
Cat::access_count += 1
print(Cat::access_count) // 1
`} />
```

View File

@ -2,7 +2,6 @@
layout: ../../../layouts/DocsLayout.astro
title: Arrays
---
import Code from "../../../components/Code.astro"
# Arrays
@ -10,7 +9,7 @@ Use square brackets as usual.
## Usage
<Code thpcode={`
```thp
val fruits = ["apple", "banana", "cherry"]
val apple = fruits[0]
@ -22,16 +21,16 @@ var numbers = [0, 1, 2, 3]
numbers[3] = 5
print(numbers[3]) // 5
`} />
```
## Type signature
<Code thpcode={`
```thp
Array[String]
Array[Int]
`} />
```
The Array signature __requires__ the word `Array`.
There is no `Int[]` or `[Int]` signature, since that would cause

View File

@ -2,7 +2,6 @@
layout: ../../../layouts/DocsLayout.astro
title: Enums
---
import Code from "../../../components/Code.astro"
# Enums
@ -16,7 +15,7 @@ THP enums are a 1 to 1 map of PHP enums, with a slightly different syntax.
Enums don't have a scalar value by default.
<Code thpcode={`
```thp
enum Suit
{
Hearts,
@ -26,14 +25,14 @@ enum Suit
}
val suit = Suit::Hearts
`} />
```
## Backed enums
Backed enums can have a scalar for each case. The scalar values can only be
`String` or `Int`.
<Code thpcode={`
```thp
enum Suit(String)
{
Hearts = "H",
@ -41,7 +40,7 @@ enum Suit(String)
Clubs = "C",
Spades = "S",
}
`} />
```
All cases must explicitly define their value, there is no automatic generation.

View File

@ -2,7 +2,6 @@
layout: ../../../layouts/DocsLayout.astro
title: Maps
---
import Code from "../../../components/Code.astro"
# Maps
@ -15,7 +14,7 @@ There can also be anonymous maps, which may contain any key.
## Named Maps
<Code thpcode={`
```thp
// Here we define a map, called Person
map Person {
String name,
@ -37,21 +36,21 @@ var Person mary_jane = .{
surname: "Jane",
age: 27,
}
`} />
```
To access the fields of a map we use square braces `[]`.
<Code thpcode={`
```thp
mary_jane["age"] += 1
print(mary_jane["name"]) // Mary
`} />
```
Or dot access `.` if the field's name is a valid identifier.
<Code thpcode={`
```thp
mary_jane.age += 1
print(mary_jane.name)
`} />
```
## Anonymous maps
@ -59,23 +58,23 @@ print(mary_jane.name)
An anonymous map allows us to store and retrieve any key of any datatype.
They are declared as `Map`.
<Code thpcode={`
```thp
val car = Map {
brand: "Toyota",
model: "Corolla",
year: 2012,
}
`} />
```
Anonymous maps can also can have their type omitted.
<Code thpcode={`
```thp
var car = .{
brand: "Toyota",
model: "Corolla",
year: 2012,
}
`} />
```
If the compiler encounters a map without a type (that is, `.{}`)
and doesn't expect a specific type, it will assume it is an
@ -83,30 +82,30 @@ anonymous map.
We can freely assign fields to an anonymous map:
<Code thpcode={`
```thp
// Modify an existing field
car["year"] = 2015
// Create a new field
car["status"] = "used"
`} />
```
However, if we try to access a field of an anonymous map we'll get
a nullable type, and we must annotate it.
<Code thpcode={`
```thp
// This is ok, we are declaring what datatype we expect
String? car_status = car["status"]
// This won't work, the compiler doesn't know what datatype to use
var car_status = car["status"]
`} />
```
Instead, we can use the `get` function of the map, which expects a
datatype and returns that type as nullable
<Code thpcode={`
```thp
val car_status = car.get[String]("status")
`} />
```
Both ways to get a value will check that the key exists in the map,
and that it has the correct datatype. If either the key doesn't exist
@ -114,10 +113,10 @@ or it has a different datatype, it will return `null`.
We can also use dynamic keys, following the same rules:
<Code thpcode={`
```thp
val generated_value = "key"
String? v = map[generated_value]
// or
val v = map[String](generated_value)
`} />
```

View File

@ -2,7 +2,6 @@
layout: ../../../layouts/DocsLayout.astro
title: Tuples
---
import Code from "../../../components/Code.astro"
# Tuples
@ -10,19 +9,18 @@ Uses `#()` just to avoid confusion with function calls and grouping (`()`).
## Definition
<Code thpcode={`
```thp
val person = #("John", "Doe", 32)
val #(name, surname, age) = person
`} />
```
## Signature
<Code thpcode={`
```thp
#(String, String, Int)
`} />
```

View File

@ -2,13 +2,12 @@
layout: ../../../layouts/DocsLayout.astro
title: Tagged unions
---
import Code from "../../../components/Code.astro"
# Tagged unions
Tagged unions can hold a value from a fixed selection of types.
<Code thpcode={`
```thp
union Shape
{
Dot,
@ -19,11 +18,11 @@ union Shape
val dot = Shape::Dot
val square1 = Shape::Square(10)
val rectangle1 = Shape::Rectangle(5, 15)
`} />
```
## Pattern matching
<Code thpcode={`
```thp
match shape_1
case ::Square(side)
{
@ -33,7 +32,7 @@ case ::Rectangle(length, height)
{
print("Area of the rectangle: {length * height}")
}
`} />
```
## Internal representation

View File

@ -2,7 +2,6 @@
layout: ../../../layouts/DocsLayout.astro
title: Nullable types
---
import Code from "../../../components/Code.astro"
# Nullable types
@ -13,18 +12,18 @@ by the question mark `?` character.
For instance, a POST request may have a `username` parameter,
or it may not. This can be represented with an `String?`.
<Code thpcode={`
```thp
String? new_username = POST::get("username")
`} />
```
When we have a `Type?` we cannot use it directly. We must first
check if the value is null, and then use it.
<Code thpcode={`
```thp
if new_username != null {
// Here \`new_username\` is automatically casted to String
// Here `new_username` is automatically casted to String
}
`} />
```
We must check explicitly that the value is not null. Doing
`if new_username {}` alone is not allowed.
@ -33,30 +32,30 @@ We must check explicitly that the value is not null. Doing
To create a nullable type we must explicitly annotate the type.
<Code thpcode={`
```thp
val favorite_color = null // Error, we must define the type
String? favorite_color = null // Ok
`} />
```
Other examples:
<Code thpcode={`
```thp
fun get_first(Array[String?] values) -> String? {}
val result = get_first([])
`} />
```
## Optional chaining
If you have a `Type?` and you wish to access a field of `Type` if it exists,
you can use the optional chaining operator.
<Code thpcode={`
```thp
Person? person = ...
val name = person?.name
`} />
```
- If `person` is null, `person?.name` will return `null`
- If `person` is not null, `person?.name` will return `name`
@ -66,12 +65,12 @@ val name = person?.name
The Elvis operator `??` is used to give a default value in case a `null` is found.
<Code thpcode={`
```thp
// This is a function that may return a Int
fun get_score() -> Int? {...}
val test_score = get_score() ?? 0
`} />
```
For the above code:
@ -80,9 +79,9 @@ For the above code:
You can use the Elvis operator to return early
<Code thpcode={`
```thp
val username = get_username() ?? return
`} />
```

View File

@ -3,7 +3,6 @@ layout: ../../../layouts/DocsLayout.astro
title: Try/Exceptions
---
import InteractiveCode from "../../../components/InteractiveCode.astro";
import Code from "../../../components/Code.astro"
# Try/exceptions
@ -18,7 +17,7 @@ is used.
For example, a function that returned a `DivisionByZero`
may be written like this:
<Code thpcode={`
```thp
fun invert(Int number) -> Int!DivisionByZero
{
if number == 0
@ -28,7 +27,7 @@ fun invert(Int number) -> Int!DivisionByZero
return 1 / number
}
`} />
```
In the previous segment, `Int!DivisionByZero` denotates
that the function may return either an `Int` or an `DivisionByZero`.
@ -45,12 +44,12 @@ TODO: fix?
If there are multiple error types that the function can return,
you can use the `|` operator:
<Code thpcode={`
```thp
type Exceptions = Exception1 | Exception2 | Exception3
fun sample() -> Int!Exceptions
{ /* ... */}
`} />
```
@ -133,14 +132,14 @@ otherwise will assign the success value and continue.
Try/return will run a function and assign its value if `Ok` is found.
Otherwise, it will return a new value specified by the programmer.
<Code thpcode={`
```thp
fun run() -> Int
{
val result = try dangerous() return 0
// ...
}
`} />
```
In the previous example:
@ -227,27 +226,27 @@ Either way, the function will continue executing.
Try/catch allows the error to be manually used & handled.
<Code thpcode={`
```thp
fun run()
{
val result = try dangerous()
catch Exception e
{
// This is run if \`dangerous()\` throws.
// \`e\` is the thrown error
// This is run if `dangerous()` throws.
// `e` is the thrown error
// Handle the error
// ...
// Return a new value to be assigned to \`result\`
// Return a new value to be assigned to `result`
0
}
}
`} />
```
A try/catch may have many `catch` clauses:
<Code thpcode={`
```thp
try dangerous()
catch Exception1 e
{...}
@ -255,6 +254,6 @@ catch Exception2 e
{...}
catch Exception3 e
{...}
`} />
```

View File

@ -2,7 +2,6 @@
layout: ../../../layouts/DocsLayout.astro
title: Blocks
---
import Code from "../../../components/Code.astro"
# Blocks
@ -12,13 +11,13 @@ Blocks can be assigned to variables, in which case
they are executed and their last expression is
returned.
<Code thpcode={`
```thp
val result = {
val temp = 161
temp * 2 // This will be assigned to \`result\`
temp * 2 // This will be assigned to `result`
}
print(result) // 322
`} />
```

View File

@ -2,7 +2,6 @@
layout: ../../../layouts/DocsLayout.astro
title: Conditionals
---
import Code from "../../../components/Code.astro"
# Conditionals
@ -13,7 +12,7 @@ import Code from "../../../components/Code.astro"
- There's no ternary operator
<Code thpcode={`
```thp
if condition
{
// code
@ -29,25 +28,25 @@ else
val result = if condition { value1 } else { value2 }
`} />
```
## Check for datatypes
<Code thpcode={`
```thp
if variable is Datatype
{
// code using variable
}
`} />
```
## If variable is of enum
TBD
<Code thpcode={`
```thp
val user_id = POST::get("user_id")
if Some(user_id) = user_id
@ -59,7 +58,7 @@ if Some(user_id) = user_id && user_id > 0
{
print("user_id is greater than 0: {user_id}")
}
`} />
```

View File

@ -2,7 +2,6 @@
layout: ../../../layouts/DocsLayout.astro
title: Loops
---
import Code from "../../../components/Code.astro"
# Loops
@ -12,16 +11,16 @@ This is simmilar to PHP's `foreach`. There is no equivalent to PHP's `for`.
Braces are required.
<Code thpcode={`
```thp
val numbers = [0, 1, 2, 3]
for #(index, number) in numbers
{
print(number)
}
`} />
```
<Code thpcode={`
```thp
val dict = .{
apple: 10,
banana: 7,
@ -32,13 +31,13 @@ for #(key, value) in dict
{
print("{key} => {value}")
}
`} />
```
## While loop
<Code thpcode={`
```thp
val colors = ["red", "green", "blue"]
var index = 0
@ -47,14 +46,14 @@ while index < colors.size()
print("{colors[index]}")
index += 1
}
`} />
```
## Infinite loop
Instead of doing `while true {}` use `loop`.
<Code thpcode={`
```thp
loop
{
print("looping")
@ -64,14 +63,14 @@ loop
break
}
}
`} />
```
## Labelled loops
You can give labels to loops, allowing you to `break` and `continue` in nested loops.
<Code thpcode={`
```thp
:top for i in values_1
{
for j in values_2
@ -80,6 +79,12 @@ You can give labels to loops, allowing you to `break` and `continue` in nested l
break :top
}
}
`} />
```

View File

@ -2,7 +2,6 @@
layout: ../../../layouts/DocsLayout.astro
title: Match
---
import Code from "../../../components/Code.astro"
# Match
@ -10,7 +9,7 @@ import Code from "../../../components/Code.astro"
Braces are **required**.
<Code thpcode={`
```thp
val user_id = POST::get("user_id")
@ -48,5 +47,5 @@ else
{
print("hello dear")
}
`} />
```

View File

@ -2,7 +2,6 @@
layout: ../../../layouts/DocsLayout.astro
title: Declaration
---
import Code from "../../../components/Code.astro"
# Declaration
@ -11,42 +10,42 @@ Function names **must** begin with a lowercase letter.
## No parameters, no return
<Code thpcode={`
```thp
fun say_hello()
{
print("Hello")
}
say_hello()
`} />
```
## With return type
<Code thpcode={`
```thp
fun get_random_number() -> Int
{
Random::get(0, 35_222)
}
val number = get_random_number()
`} />
```
## With parameters and return type
<Code thpcode={`
```thp
fun get_secure_random_number(Int min, Int max) -> Int
{
Random::get_secure(min, max)
}
val number = get_secure_random_number(0, 65535)
`} />
```
## Generic types
<Code thpcode={`
```thp
fun get_first_item[T](Array[T] array) -> T
{
array[0]
@ -56,23 +55,23 @@ val first = get_first_item[Int](numbers)
// The type annotation is optional if the compiler can infer the type
val first = get_first_item(numbers)
`} />
```
## Signature
<Code thpcode={`
```thp
() -> ()
() -> Int
(Int, Int) -> Int
[T](Array[T]) -> T
`} />
```
## Named arguments
<Code thpcode={`
```thp
fun html_special_chars(
String input,
Int? flags,
@ -84,13 +83,13 @@ fun html_special_chars(
}
html_special_chars(input, double_encoding: false)
`} />
```
TBD: If & how named arguments affect the order of the parameters
## Named arguments with different names
<Code thpcode={`
```thp
fun greet(
String name,
String from: city,
@ -100,7 +99,7 @@ fun greet(
}
greet("John", from: "LA")
`} />
```

View File

@ -2,24 +2,23 @@
layout: ../../../layouts/DocsLayout.astro
title: Higher Order Functions
---
import Code from "../../../components/Code.astro"
# Higher Order functions
## Function as parameter
<Code thpcode={`
```thp
fun map[A, B](Array[A] input, (A) -> B function) -> Array[B]
{
// implementation
}
`} />
```
## Function as return
<Code thpcode={`
```thp
fun generate_generator() -> () -> Int
{
// code...
@ -31,7 +30,7 @@ fun generate_generator() -> () -> Int
val generator = generate_generator() // A function
val value = generate_generator()() // An Int
`} />
```

View File

@ -2,14 +2,13 @@
layout: ../../../layouts/DocsLayout.astro
title: Lambdas
---
import Code from "../../../components/Code.astro"
# Lambdas / Anonymous functions
## Anonymous function
<Code thpcode={`
```thp
fun(Int x, Int y) -> Int {
x + y
}
@ -18,7 +17,7 @@ fun(Int x, Int y) -> Int {
numbers.map(fun(x) {
x * 2
})
`} />
```
@ -27,7 +26,7 @@ numbers.map(fun(x) {
By default closures **always** capture variables as **references**.
<Code thpcode={`
```thp
var x = 20
val f = fun() {
@ -38,18 +37,18 @@ f() // 20
x = 30
f() // 30
`} />
```
You can force a closure to capture variables by value.
<Code thpcode={`
```thp
fun(parameters) clone(variables) {
// code
}
`} />
```
<Code thpcode={`
```thp
var x = 20
val f = fun() clone(x) {
@ -60,7 +59,7 @@ f() // 20
x = 30
f() // 20
`} />
```
## Lambdas
@ -71,7 +70,7 @@ Inside the lambda you can access the parameters as `$1`, `$2`, etc.
Finally, lambdas be written outside of the parenthesis of function calls.
<Code thpcode={`
```thp
numbers.map() #{
$1 * 2
}
@ -81,7 +80,7 @@ numbers.map() #{
numbers.map(fun(param1) {
$1 * 2
})
`} />
```

View File

@ -2,40 +2,39 @@
layout: ../../../layouts/DocsLayout.astro
title: Function parameters
---
import Code from "../../../components/Code.astro"
# Function parameters
## Immutable reference
<Code thpcode={`
```thp
fun add_25(Array[Int] numbers) {
numbers.push(25) // Error: \`numbers\` is immutable
numbers.push(25) // Error: `numbers` is immutable
}
`} />
```
When using a regular type as a parameter, only it's immutable
properties can be used
<Code thpcode={`
```thp
fun count(Array[Int] numbers) -> Int {
val items_count = numbers.size() // Ok, \`size\` is pure
val items_count = numbers.size() // Ok, `size` is pure
items_count
}
`} />
```
To use immutable properties you must use a mutable reference.
## Mutable reference
<Code thpcode={`
```thp
fun push_25(mut Array[Int] numbers) {
numbers.push(25) // Ok, will also mutate the original array
}
`} />
```
Placing a `mut` before the type makes the parameter a mutable
reference. Mutable methods can be used, and the original
@ -43,34 +42,37 @@ data **can** be mutated.
The caller *must* also use `mut`.
<Code thpcode={`
```thp
val numbers = Array(0, 1, 2, 3)
push_25(mut numbers) // Pass \`numbers\` as reference.
push_25(mut numbers) // Pass `numbers` as reference.
print(numbers(4)) // \`Some(25)\`
`} />
print(numbers(4)) // `Some(25)`
```
## Clone
<Code thpcode={`
```thp
fun add_25(clone Array[Int] numbers) {
numbers.push(25) // Ok, the original array is unaffected
}
`} />
```
Using the `clone` keyword before the type creates a mutable copy
of the parameter (CoW). The original data will **not** be mutated.
<Code thpcode={`
```thp
val numbers = Array(1, 2, 3, 4)
add_25(clone numbers) // Pass \`numbers\` as clone.
add_25(clone numbers) // Pass `numbers` as clone.
print(numbers(4)) // None
`} />
```

View File

@ -54,7 +54,6 @@ pagesLayout:
- path: intro
---
import InteractiveCode from "../../components/InteractiveCode.astro";
import Code from "../../components/Code.astro"
# Welcome
@ -145,11 +144,11 @@ $has_key = str_contains($haystack, 'needle');
print("has key? " . $has_key);
```
<Code thpcode={`
```thp
// THP
val has_key = haystack.contains("needle")
print("has key? " + has_key)
`} />
```
- Explicit variable declaration
- No `$` for variable names (and thus no `$$variable`, use a map instead)
@ -171,14 +170,14 @@ $obj = [
]
```
<Code thpcode={`
```thp
// THP
val obj = .{
names: #("Toni", "Stark"), // Tuple
age: 33,
numbers: [32, 64, 128]
}
`} />
```
- Tuples, Arrays, Sets, Maps are clearly different
- JSON-like object syntax
@ -193,11 +192,11 @@ $cat->meow();
```
<Code thpcode={`
```thp
// THP
val cat = Cat("Michifu", 7)
cat.meow();
`} />
```
- Instantiate classes without `new`
- Use dot `.` instead of arrow `->` syntax
@ -212,10 +211,10 @@ use \Some\Deeply\Nested\Interface
```
<Code thpcode={`
```thp
// THP
use Some::Deeply::Nested::{Class, Interface}
`} />
```
- Different module syntax
- Explicit module declaration
@ -238,7 +237,7 @@ Where possible THP will compile to available PHP functions/classes/methods/etc.
For example:
<Code thpcode={`
```thp
// This expression
val greeting =
match get_person()
@ -254,7 +253,7 @@ val greeting =
{
"Nobody is here"
}
`} />
```
```php
// Would compile to:

View File

@ -2,7 +2,6 @@
layout: ../../../layouts/DocsLayout.astro
title: Introduction
---
import Code from "../../../components/Code.astro"
# THP templating
@ -61,19 +60,18 @@ and compose them.
The following would be the equivalent in THP:
<Code thpcode={`
```thp
fun Button(String name) -> Html {
<button class="some tailwind classes">
Hello {name}!
</button>
}
`} />
```
It is very similar to React. The HTML is inside the THP code, not the other
way around, so you can have arbitrary logic in the component.
<Code thpcode={`
```thp
fun User(String name) {
// Get info from the database
val user = try Model::get_user(name)
@ -98,6 +96,9 @@ fun TransactionItem(Transaction t) {
{t.date} - {t.name} ({t.price})
</li>
}
`} />
```