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; const { thpcode } = Astro.props;
--- ---
<div class="flex h-full py-8 items-center"> <div class="flex h-full py-8 items-center">
<div class="p-4 w-full"> <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>
</div> </div>

View File

@ -2,7 +2,7 @@
import { lex } from "../lexer/lexer"; import { lex } from "../lexer/lexer";
import type { Instruction } from "../thp_machine/machine_parser"; import type { Instruction } from "../thp_machine/machine_parser";
import { parse_str } 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; const { code, steps } = Astro.props;
function highlightCode(lines: Array<string>): string { function highlightCode(lines: Array<string>): string {
@ -31,7 +31,7 @@ function highlightCode(lines: Array<string>): string {
return outLines.join("\n"); return outLines.join("\n");
} }
const codeHtml = highlightCode(leftTrimDedent(code)); const codeHtml = highlightCode(trimAndDedent(code));
let instructionSet: Array<Array<Instruction>>; let instructionSet: Array<Array<Instruction>>;
try { try {
instructionSet = parse_str(steps); 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 * - Picks the indentation level from the first non-white line
* - Dedents the following lines * - Dedents the following lines
*/ */
export function leftTrimDedent(input: string): Array<string> { export function trimAndDedent(input: string): Array<string> {
let lines = input.split("\n"); let lines = input.split("\n");
let output: Array<string> = [];
// Ignore first line // Remove empty lines at the start
if (lines[0] === "" && lines.length > 1) { while (lines[0] === "") {
lines = lines.slice(1); lines = lines.slice(1);
} }
// Get indentation level of the first line // Get indentation level
let indentationLevel = 0; let indentationLevel = 0;
for (const char of lines[0]!) { for (const char of lines[0]!) {
if (char === " ") { if (char === " ") {
@ -24,37 +23,48 @@ export function leftTrimDedent(input: string): Array<string> {
} }
} }
for (const line of lines) { // Enforce indentation, or trim
// Ignore empty lines for (let i = 0; i < lines.length; i += 1) {
if (line === "") { // If the line is empty, continue
output.push(""); const characters = lines[i]!.split("");
if (characters.length === 0) {
continue; 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;
} }
if (output.length > 1 && output[output.length - 1] === "") { // Enforce indentation
output = output.slice(0, -1); 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]);
} }
return output; lines[i] = characters.slice(4).join("");
} }
function trimWhitespace(input: string, count: number): string {
let indentCount = 0;
for (const char of input) { // Remove empty lines at the end
if (char === " ") { let endPosition = lines.length - 1;
indentCount += 1; while (true) {
if (lines[endPosition] === "") {
lines = lines.slice(0, -1);
endPosition -= 1;
} else { } else {
break; break;
} }
} }
if (indentCount >= count || indentCount == input.length) { return lines;
return input.slice(count);
} else {
throw new Error(`Invalid indentation while trimming: Expected ${count} spaces, got ${indentCount}`);
}
} }

View File

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

View File

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

View File

@ -1,6 +1,9 @@
import { lex } from "./lexer"; 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 tokens = lex(code);
let highlighted_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>`; 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 BaseLayout from "../layouts/BaseLayout.astro";
import Navbar from "../components/Navbar.astro"; import Navbar from "../components/Navbar.astro";
import HeroSection from "../components/HeroSection.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> <BaseLayout>
@ -117,9 +95,7 @@ case Cat(name, lives)
</g> </g>
</svg> </svg>
<div class="h-1"></div> <div class="h-1"></div>
<div id="editor" class="font-mono language-thp"> <div id="editor" class="font-mono language-thp"></div>
<pre set:html={thp_highlighter(leftTrimDedent(thpcode).join("\n"))}></pre>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -218,4 +194,38 @@ case Cat(name, lives)
<br> <br>
Nullable types must be explicitly checked before using them. Nullable types must be explicitly checked before using them.
</HeroSection> </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> </BaseLayout>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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