Compare commits

..

No commits in common. "a6c32df1ff509bbea097f957bd653a98bb0cba01" and "b5e22cca62b45191255d14018818415540374b5a" have entirely different histories.

91 changed files with 3640 additions and 8787 deletions

View File

@ -1,13 +0,0 @@
/** @type {import("prettier").Config} */
export default {
plugins: ["prettier-plugin-astro"],
useTabs: true,
overrides: [
{
files: "*.astro",
options: {
parser: "astro",
},
},
],
};

View File

@ -1,4 +1,4 @@
import { defineConfig } from "astro/config"; import { defineConfig } from 'astro/config';
import tailwind from "@astrojs/tailwind"; import tailwind from "@astrojs/tailwind";
import mdx from "@astrojs/mdx"; import mdx from "@astrojs/mdx";

View File

@ -20,3 +20,4 @@ networks:
proxy: proxy:
name: proxy name: proxy
external: true external: true

View File

@ -8,7 +8,6 @@
"build": "astro build", "build": "astro build",
"test": "vitest run", "test": "vitest run",
"preview": "astro preview", "preview": "astro preview",
"prettier": "prettier \"./src/**/*.astro\" --write",
"astro": "astro" "astro": "astro"
}, },
"dependencies": { "dependencies": {
@ -20,9 +19,5 @@
"tailwindcss": "^3.4.3", "tailwindcss": "^3.4.3",
"typescript": "^5.4.5", "typescript": "^5.4.5",
"vitest": "^1.5.0" "vitest": "^1.5.0"
},
"devDependencies": {
"prettier": "^3.3.3",
"prettier-plugin-astro": "^0.14.1"
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -9,19 +9,19 @@
} }
@font-face { @font-face {
font-family: "Iosevka Fixed Web"; font-family: 'Iosevka Fixed Web';
font-display: swap; font-display: swap;
font-weight: 700; font-weight: 700;
font-stretch: normal; font-stretch: normal;
font-style: normal; font-style: normal;
src: url("/Iosevka/Bold.woff2") format("woff2"); src: url('/Iosevka/Bold.woff2') format('woff2');
} }
:root { :root {
--c-thp: #f472b6; --c-thp: #f472b6;
--font-display: "Atkinson Hyperlegible", sans-serif; --font-display: 'Atkinson Hyperlegible', sans-serif;
--font-body: "Atkinson Hyperlegible", sans-serif; --font-body: 'Atkinson Hyperlegible', sans-serif;
--font-code: "Iosevka Fixed Web", "Iosevka Nerd Font", Iosevka, monospace; --font-code: "Iosevka Fixed Web", "Iosevka Nerd Font", Iosevka, monospace;
} }
@ -30,10 +30,10 @@
--c-text: rgb(200, 200, 200); --c-text: rgb(200, 200, 200);
--c-text-2: white; --c-text-2: white;
--c-primary: #884b6a; --c-primary: #884b6a;
--c-purple: #7f669d; --c-purple: #7F669D;
--c-purple-light: #ba94d1; --c-purple-light: #BA94D1;
--c-box-shadow: #fbfacd; --c-box-shadow: #FBFACD;
--c-pink: #ae508d; --c-pink: #AE508D;
--c-link: #38bdf8; --c-link: #38bdf8;
--c-nav-bg: rgb(18, 18, 18, 0.5); --c-nav-bg: rgb(18, 18, 18, 0.5);
@ -46,7 +46,7 @@
--c-text: #121212; --c-text: #121212;
--c-text-2: black; --c-text-2: black;
--c-purple: #374259; --c-purple: #374259;
--c-purple-light: #ba94d1; --c-purple-light: #BA94D1;
--c-box-shadow: #374259; --c-box-shadow: #374259;
--c-primary: rgb(255, 180, 180); --c-primary: rgb(255, 180, 180);
--c-pink: #374259; --c-pink: #374259;

View File

@ -1,3 +1,4 @@
.markdown > h1 { .markdown > h1 {
font-size: 2.25rem; font-size: 2.25rem;
line-height: 2.5rem; line-height: 2.5rem;
@ -101,3 +102,4 @@
.two-column h3 { .two-column h3 {
margin: 0.75rem 0; margin: 0.75rem 0;
} }

View File

@ -1,118 +1,3 @@
/* PrismJS 1.29.0 /* PrismJS 1.29.0
https://prismjs.com/download.html#themes=prism */ https://prismjs.com/download.html#themes=prism */
code[class*="language-"], code[class*=language-],pre[class*=language-]{color:#000;background:0 0;text-shadow:0 1px #fff;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{text-shadow:none;background:#b3d4fc}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow:none;background:#b3d4fc}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#f5f2f0}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#708090}.token.punctuation{color:#999}.token.namespace{opacity:.7}.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color:#905}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#690}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color:#9a6e3a;background:hsla(0,0%,100%,.5)}.token.atrule,.token.attr-value,.token.keyword{color:#07a}.token.class-name,.token.function{color:#dd4a68}.token.important,.token.regex,.token.variable{color:#e90}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}
pre[class*="language-"] {
color: #000;
background: 0 0;
text-shadow: 0 1px #fff;
font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
font-size: 1em;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
code[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection,
pre[class*="language-"] ::-moz-selection,
pre[class*="language-"]::-moz-selection {
text-shadow: none;
background: #b3d4fc;
}
code[class*="language-"] ::selection,
code[class*="language-"]::selection,
pre[class*="language-"] ::selection,
pre[class*="language-"]::selection {
text-shadow: none;
background: #b3d4fc;
}
@media print {
code[class*="language-"],
pre[class*="language-"] {
text-shadow: none;
}
}
pre[class*="language-"] {
padding: 1em;
margin: 0.5em 0;
overflow: auto;
}
:not(pre) > code[class*="language-"],
pre[class*="language-"] {
background: #f5f2f0;
}
:not(pre) > code[class*="language-"] {
padding: 0.1em;
border-radius: 0.3em;
white-space: normal;
}
.token.cdata,
.token.comment,
.token.doctype,
.token.prolog {
color: #708090;
}
.token.punctuation {
color: #999;
}
.token.namespace {
opacity: 0.7;
}
.token.boolean,
.token.constant,
.token.deleted,
.token.number,
.token.property,
.token.symbol,
.token.tag {
color: #905;
}
.token.attr-name,
.token.builtin,
.token.char,
.token.inserted,
.token.selector,
.token.string {
color: #690;
}
.language-css .token.string,
.style .token.string,
.token.entity,
.token.operator,
.token.url {
color: #9a6e3a;
background: hsla(0, 0%, 100%, 0.5);
}
.token.atrule,
.token.attr-value,
.token.keyword {
color: #07a;
}
.token.class-name,
.token.function {
color: #dd4a68;
}
.token.important,
.token.regex,
.token.variable {
color: #e90;
}
.token.bold,
.token.important {
font-weight: 700;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}

File diff suppressed because one or more lines are too long

View File

@ -5,10 +5,7 @@ import CodeError from "./docs/CodeError.astro";
const { thpcode, no_warnings, level } = Astro.props; const { thpcode, no_warnings, level } = Astro.props;
const [native_html, error_message] = await native_highlighter( const [native_html, error_message] = await native_highlighter(thpcode, level as HighlightLevel);
thpcode,
level as HighlightLevel,
);
--- ---
<pre <pre

View File

@ -1,5 +1,5 @@
--- ---
import Code from "./Code.astro"; import Code from "./Code.astro"
const { thpcode, no_warnings, level } = Astro.props; const { thpcode, no_warnings, level } = Astro.props;
--- ---

View File

@ -1,5 +1,5 @@
--- ---
const { showSidebarButton = true, version = "latest" } = Astro.props; const { showSidebarButton = true } = Astro.props;
--- ---
<nav <nav
@ -14,11 +14,18 @@ const { showSidebarButton = true, version = "latest" } = Astro.props;
) )
} }
<a href="/" class="px-4 flex gap-2 items-center"> <a
<img class="inline-block h-10" src="/img/thp_logo_exp.svg" alt="thp" /> href="/"
class="px-4 flex gap-2 items-center"
>
<img
class="inline-block h-10"
src="/img/thp_logo_exp.svg"
alt="thp"
/>
</a> </a>
<a <a
href={`/en/${version}/learn/`} href="/en/latest/learn/"
class="hidden sm:inline-block px-4 font-display font-bold-text-xl hover:underline" class="hidden sm:inline-block px-4 font-display font-bold-text-xl hover:underline"
> >
Learn Learn
@ -44,9 +51,7 @@ const { showSidebarButton = true, version = "latest" } = Astro.props;
const sidebarToggle = document.getElementById("sidebar-toggle"); const sidebarToggle = document.getElementById("sidebar-toggle");
if (!sidebar || !sidebarToggle) { if (!sidebar || !sidebarToggle) {
console.log( console.log("Sidebar or Sidebar toggle not found. Not enabling sidebar on mobile");
"Sidebar or Sidebar toggle not found. Not enabling sidebar on mobile",
);
return; return;
} }

View File

@ -7,7 +7,10 @@ const post_url = entry.url + (entry.url.endsWith("/") ? "" : "/");
// this may deal with folders. // this may deal with folders.
// if so, it will turn any `-` into whitespace, // if so, it will turn any `-` into whitespace,
// and remove any leading number // and remove any leading number
const entry_title = entry.title.replaceAll("-", " ").replaceAll(/\d+_/g, ""); const entry_title = entry.title
.replaceAll("-", " ")
.replaceAll(/\d+_/g, "");
--- ---
{ {
@ -31,7 +34,9 @@ const entry_title = entry.title.replaceAll("-", " ").replaceAll(/\d+_/g, "");
<ul class="my-1"> <ul class="my-1">
{entry.children!.map((nextEntry) => ( {entry.children!.map((nextEntry) => (
<Astro.self entry={nextEntry} /> <Astro.self
entry={nextEntry}
/>
))} ))}
</ul> </ul>
</> </>

View File

@ -20,7 +20,8 @@ function buildHierarchy(headings: any) {
toc.push(heading); toc.push(heading);
} else if (heading.depth === 1) { } else if (heading.depth === 1) {
/** empty */ /** empty */
} else { }
else {
parentHeadings.get(heading.depth - 1).subheadings.push(heading); parentHeadings.get(heading.depth - 1).subheadings.push(heading);
} }
}); });

View File

@ -9,10 +9,7 @@ const monoClass = parentMono ? " font-mono" : "";
--- ---
<li> <li>
<a <a class={"inline-block py-1 hover:underline" + monoClass} href={"#" + heading.slug}>
class={"inline-block py-1 hover:underline" + monoClass}
href={"#" + heading.slug}
>
{heading.text} {heading.text}
</a> </a>
{ {

View File

@ -6,8 +6,4 @@ const { thpcode, href } = Astro.props;
const [native_html] = await native_highlighter(thpcode); const [native_html] = await native_highlighter(thpcode);
--- ---
<a <a href={href} class="inline-block w-full py-2 font-mono whitespace-pre" set:html={native_html} />
href={href}
class="inline-block w-full py-2 font-mono whitespace-pre"
set:html={native_html}
/>

View File

@ -1,6 +1,4 @@
<div <div class="my-6 px-4 py-2 rounded bg-[#f8e287] text-[#221b00] dark:bg-[#dbc66e] dark:text-[#3a3000]">
class="my-6 px-4 py-2 rounded bg-[#f8e287] text-[#221b00] dark:bg-[#dbc66e] dark:text-[#3a3000]"
>
<div class="font-bold pt-2">Warning</div> <div class="font-bold pt-2">Warning</div>
<slot /> <slot />
</div> </div>

View File

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

View File

@ -1,3 +1,4 @@
/** /**
* Performs the following: * Performs the following:
* - Removes the first & last line, if they are empty * - Removes the first & last line, if they are empty
@ -53,9 +54,7 @@ function trimWhitespace(input: string, count: number): string {
if (indentCount >= count || indentCount == input.length) { if (indentCount >= count || indentCount == input.length) {
return input.slice(count); return input.slice(count);
} else { } else {
throw new Error( throw new Error(`Invalid indentation while trimming: Expected ${count} spaces, got ${indentCount}`);
`Invalid indentation while trimming: Expected ${count} spaces, got ${indentCount}`,
);
} }
} }

View File

@ -27,8 +27,6 @@ const { title } = Astro.props;
href="https://fonts.googleapis.com/css2?family=Atkinson+Hyperlegible:ital,wght@0,400;0,700;1,400;1,700&display=swap" href="https://fonts.googleapis.com/css2?family=Atkinson+Hyperlegible:ital,wght@0,400;0,700;1,400;1,700&display=swap"
rel="stylesheet" rel="stylesheet"
/> />
<script src="//unpkg.com/alpinejs" defer></script>
</head> </head>
<body class="bg-c-bg text-c-text"> <body class="bg-c-bg text-c-text">

View File

@ -11,40 +11,38 @@ export type PageEntry = {
}; };
export interface AstroFile { export interface AstroFile {
frontmatter: Frontmatter; frontmatter: Frontmatter
__usesAstroImage: boolean; __usesAstroImage: boolean
url: string; url: string
file: string; file: string
relative_file: string; relative_file: string
} }
export interface Frontmatter { export interface Frontmatter {
layout: string; layout: string
title: string; title: string
order: number; order: number
} }
type Props = { type Props = {
/** Base url. It is used to later build a tree file system */ /** Base url. It is used to later build a tree file system */
base_url: string; base_url: string,
frontmatter: Frontmatter; frontmatter: Frontmatter;
headings: any; headings: any;
posts: Array<AstroFile>; posts: Array<AstroFile>;
version: string;
}; };
const { const {
base_url, base_url,
frontmatter, frontmatter,
headings, headings,
posts, posts
version = "latest",
}: Props = Astro.props; }: Props = Astro.props;
const base_len = base_url.length; const base_len = base_url.length;
const posts_2 = posts const posts_2 = posts
.map((post) => ({ .map(post => ({
...post, ...post,
title: post.frontmatter.title, title: post.frontmatter.title,
// this should be a path relative to the base url. // this should be a path relative to the base url.
@ -52,11 +50,11 @@ const posts_2 = posts
// being `/spec/ast/tokens` would be `/ast/tokens` // being `/spec/ast/tokens` would be `/ast/tokens`
path: post.url.substring(base_len), path: post.url.substring(base_len),
})) }))
.sort((p1, p2) => (p1.frontmatter.order > p2.frontmatter.order ? 1 : -1)); .sort((p1, p2) => p1.frontmatter.order > p2.frontmatter.order? 1 : -1);
// build a hierarchy of the files // build a hierarchy of the files
const second_level: Record<string, Array<AstroFile>> = { const second_level: Record<string, Array<AstroFile>> = {
_: [], "_": [],
}; };
for (const post of posts_2) { for (const post of posts_2) {
const fragments = post.path.split("/"); const fragments = post.path.split("/");
@ -68,7 +66,8 @@ for (const post of posts_2) {
second_level[folder_name] = []; second_level[folder_name] = [];
} }
second_level[folder_name].push(post); second_level[folder_name].push(post);
} else { }
else {
// add to root folder // add to root folder
second_level["_"]!.push(post); second_level["_"]!.push(post);
} }
@ -89,7 +88,8 @@ for (const levels_key of levels_keys) {
if (levels_key === "_") { if (levels_key === "_") {
// top level, already inserted // top level, already inserted
continue; continue;
} else { }
else {
const posts = second_level[levels_key]!; const posts = second_level[levels_key]!;
const sorted_posts = posts.toSorted(sort_posts); const sorted_posts = posts.toSorted(sort_posts);
const parentEntry = { const parentEntry = {
@ -109,11 +109,6 @@ function sort_posts(p1, p2) {
return p1.title > p2.title? 1 : -1; return p1.title > p2.title? 1 : -1;
} }
} }
const versions = ["latest", "v0.0.1"].filter((x) => x !== version);
const url_components = base_url.split(version);
const lc = url_components[0] ?? "";
const rc = url_components[1] ?? "";
--- ---
<BaseLayout title={frontmatter.title}> <BaseLayout title={frontmatter.title}>
@ -128,53 +123,23 @@ const rc = url_components[1] ?? "";
border-c-border-1 -translate-x-64 lg:translate-x-0 transition-transform" border-c-border-1 -translate-x-64 lg:translate-x-0 transition-transform"
> >
<nav class="py-4 pr-2 overflow-x-scroll h-[calc(100vh-3rem)]"> <nav class="py-4 pr-2 overflow-x-scroll h-[calc(100vh-3rem)]">
<div class="mb-2 relative" x-data="{open: false}">
<div> <form class="pb-8 px-1">
<span> Language version: </span> <label for="version-select">THP version:</label>
</div> <select
<button id="version-select"
class="bg-c-bg text-c-text border border-c-primary rounded px-3 w-full font-mono mt-2 flex items-center hover:dark:bg-zinc-800 transition-colors cursor-pointer text-left" class="bg-c-bg text-c-on-bg border border-pink-700 rounded px-3 p-1 w-full font-mono"
@click="open = !open"
>
<span class="inline-block w-full">
{version}
</span>
<svg
xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
fill="var(--c-primary)"
viewBox="0 0 256 256"
style="padding-bottom: 4px"
><path
d="M213.66,101.66l-80,80a8,8,0,0,1-11.32,0l-80-80A8,8,0,0,1,53.66,90.34L128,164.69l74.34-74.35a8,8,0,0,1,11.32,11.32Z"
></path>
</svg>
</button>
<div
x-bind:class="open? '': 'hidden'"
class="bottom-0 w-full absolute"
>
<div
class="absolute top-0 w-full bg-c-bg border border-c-primary rounded"
> >
<option selected>latest</option>
<option>v0.0.1</option>
</select>
</form>
{ {
versions.map((x) => ( entries.map((entry) => (
<a <Sidebar entry={entry} />
class="px-3 py-1 hover:dark:bg-zinc-800 transition-colors font-mono inline-block w-full text-left"
href={`${lc}${x}${rc}`}
>
{x}
</a>
)) ))
} }
</div>
</div>
</div>
<hr class="my-6" />
{entries.map((entry) => <Sidebar entry={entry} />)}
</nav> </nav>
</div> </div>

View File

@ -1,20 +1,16 @@
export function highlightOnDom() { export function highlightOnDom() {
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");
if (language === null) { if (language === null) { continue; }
continue;
}
// Create a visual indicador // Create a visual indicador
const indicator = document.createElement("span"); const indicator = document.createElement("span");
let indicator_bg_class = ""; let indicator_bg_class = "";
if (language === "php") { if (language === "php") { indicator_bg_class = "bg-[#4f5b93]"; }
indicator_bg_class = "bg-[#4f5b93]"; else if (language === "html") { indicator_bg_class = "bg-[#dc4a20]"; }
} else if (language === "html") {
indicator_bg_class = "bg-[#dc4a20]";
}
indicator.className = `absolute top-1 right-0 inline-block text-sm select-none opacity-85 ${indicator_bg_class} px-2 rounded-full`; indicator.className = `absolute top-1 right-0 inline-block text-sm select-none opacity-85 ${indicator_bg_class} px-2 rounded-full`;
indicator.innerText = language; indicator.innerText = language;

View File

@ -1,3 +1,4 @@
export function sidebarHighlight() { export function sidebarHighlight() {
let current_uri = window.location.pathname; let current_uri = window.location.pathname;
@ -6,7 +7,8 @@ export function sidebarHighlight() {
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) {
sidebar.scrollTop = link.offsetTop - sidebar.offsetTop - 250; sidebar.scrollTop =
link.offsetTop - sidebar.offsetTop - 250;
link.classList.add("bg-pink-200", "dark:bg-pink-950"); link.classList.add("bg-pink-200", "dark:bg-pink-950");
break; break;

View File

@ -1,16 +1,9 @@
import { spawn } from "node:child_process"; import { spawn } from "node:child_process";
import { leftTrimDedent } from "../components/utils"; import { leftTrimDedent } from "../components/utils";
import { HighlightLevel } from "./types"; import { HighlightLevel } from "./types";
import type { import type { ErrorLabel, MistiErr, Token, TokenizeResult, TokenType } from "./types";
ErrorLabel,
MistiErr,
Token,
TokenizeResult,
TokenType,
} from "./types";
const error_classes = const error_classes = "underline underline-offset-4 decoration-wavy decoration-red-500";
"underline underline-offset-4 decoration-wavy decoration-red-500";
/** /**
* Highlights code using the compiler * Highlights code using the compiler
@ -19,10 +12,7 @@ const error_classes =
* - The tokens as a list of <span /> elements * - The tokens as a list of <span /> elements
* - An error message, if any * - An error message, if any
*/ */
export async function native_highlighter( export async function native_highlighter(code: string, level = HighlightLevel.Lexic): Promise<[string, string | null]> {
code: string,
level = HighlightLevel.Lexic,
): Promise<[string, string | null]> {
let formatted_code = leftTrimDedent(code).join("\n"); let formatted_code = leftTrimDedent(code).join("\n");
try { try {
@ -40,10 +30,7 @@ export async function native_highlighter(
* - The tokens as a list of <span /> elements * - The tokens as a list of <span /> elements
* - An error message, if any * - An error message, if any
*/ */
function highlight_syntax( function highlight_syntax(code: string, result: TokenizeResult): [string, string | null] {
code: string,
result: TokenizeResult,
): [string, string | null] {
if (result.Ok) { if (result.Ok) {
const tokens_html = render_tokens(code, result.Ok); const tokens_html = render_tokens(code, result.Ok);
@ -57,21 +44,21 @@ function highlight_syntax(
} else if (result.Err) { } else if (result.Err) {
// TODO: Implement error rendering, based on the new error schema // TODO: Implement error rendering, based on the new error schema
return [code, `lexical error ${result.Err.error_code}`]; return [code, `lexical error ${result.Err.error_code}`]
} else { } else {
console.error(result); console.error(result);
throw new Error( throw new Error("Web page error: The compiler returned a case that wasn't handled.");
"Web page error: The compiler returned a case that wasn't handled.",
);
} }
} }
/** A fatal error with the THP compiler */ /** A fatal error with the THP compiler */
function compiler_error(code: string, error: MistiErr): [string, string] { function compiler_error(code: string, error: MistiErr): [string, string] {
console.log(error); console.log(error);
return [code, "Fatal compiler error"]; return [code, "Fatal compiler error"];
} }
/** /**
* Transforms a list of tokens into colored HTML, and underlines present errors * Transforms a list of tokens into colored HTML, and underlines present errors
* *
@ -81,19 +68,12 @@ function compiler_error(code: string, error: MistiErr): [string, string] {
* @param error_end Absolute position to where the error ends. * @param error_end Absolute position to where the error ends.
* @returns * @returns
*/ */
function render_tokens( function render_tokens(input: string, tokens: Array<Token>, error_labels: Array<ErrorLabel> = []): string {
input: string,
tokens: Array<Token>,
error_labels: Array<ErrorLabel> = [],
): string {
const input_chars = input.split(""); const input_chars = input.split("");
let output = ""; let output = "";
// Collects all the token ranges in all error labels // Collects all the token ranges in all error labels
const error_ranges: Array<[number, number]> = error_labels.map((l) => [ const error_ranges: Array<[number, number]> = error_labels.map(l => [l.start, l.end]);
l.start,
l.end,
]);
let current_pos = 0; let current_pos = 0;
for (let i = 0; i < tokens.length; i += 1) { for (let i = 0; i < tokens.length; i += 1) {
@ -123,11 +103,7 @@ function render_tokens(
output += input_chars.slice(current_pos, token_start).join(""); output += input_chars.slice(current_pos, token_start).join("");
// Append the token // Append the token
const [token_value, new_token_end] = process_token_value_and_end( const [token_value, new_token_end] = process_token_value_and_end(t.value, t.token_type, token_end);
t.value,
t.token_type,
token_end,
);
const token_type = translate_token_type(t.token_type, token_value); const token_type = translate_token_type(t.token_type, token_value);
output += `<span class="token ${token_type} ${is_errored ? error_classes : ""}">${token_value}</span>`; output += `<span class="token ${token_type} ${is_errored ? error_classes : ""}">${token_value}</span>`;
@ -143,21 +119,12 @@ function render_tokens(
let offset = 0; let offset = 0;
for (const label of error_labels) { for (const label of error_labels) {
// get the line number of the label // get the line number of the label
const [line_number, col_number] = absolute_to_line_column( const [line_number, col_number] = absolute_to_line_column(input, label.start);
input,
label.start,
);
let spaces_len = col_number - 1; let spaces_len = col_number - 1;
if (spaces_len < 0) { if (spaces_len < 0) { spaces_len = 0 }
spaces_len = 0;
}
const spaces = new Array(spaces_len).fill("&nbsp;").join(""); const spaces = new Array(spaces_len).fill("&nbsp;").join("");
lines.splice( lines.splice(line_number + offset, 0, create_inline_error_message(spaces, label.message));
line_number + offset,
0,
create_inline_error_message(spaces, label.message),
);
offset += 1; offset += 1;
} }
@ -176,10 +143,7 @@ function create_inline_error_message(spaces: string, message: string): string {
* @param input the source code * @param input the source code
* @param absolute the absolute position * @param absolute the absolute position
*/ */
function absolute_to_line_column( function absolute_to_line_column(input: string, absolute: number): [number, number] {
input: string,
absolute: number,
): [number, number] {
let line_count = 1; let line_count = 1;
let last_newline_pos = 0; let last_newline_pos = 0;
@ -208,11 +172,7 @@ function absolute_to_line_column(
* @param first_end The position where the token ends according to the token value * @param first_end The position where the token ends according to the token value
* @returns * @returns
*/ */
function process_token_value_and_end( function process_token_value_and_end(value: string, token_type: TokenType, first_end: number): [string, number] {
value: string,
token_type: TokenType,
first_end: number,
): [string, number] {
let token_value = value; let token_value = value;
let new_end = first_end; let new_end = first_end;
if (token_type === "MultilineComment") { if (token_type === "MultilineComment") {
@ -226,49 +186,16 @@ function process_token_value_and_end(
// Escape html and return // Escape html and return
return [ return [
token_value.replaceAll(/</g, "&lt;").replaceAll(/>/g, "&gt;"), token_value.replaceAll(/</g, "&lt;").replaceAll(/>/g, "&gt;"),
new_end, new_end
]; ];
} }
function translate_token_type(tt: TokenType, value: string): string { function translate_token_type(tt: TokenType, value: string): string {
const keywords = [ const keywords = ["throws", "extends", "constructor", "static", "const",
"throws", "enum", "union", "use", "break", "catch", "continue", "as", "do",
"extends", "finally", "fun", "fn", "nil", "return", "throw",
"constructor", "try", "type", "with", "of", "abstract", "class", "interface",
"static", "private", "protected", "pub", "override", "open", "init", "val", "var", "mut", "clone"];
"const",
"enum",
"union",
"use",
"break",
"catch",
"continue",
"as",
"do",
"finally",
"fun",
"fn",
"nil",
"return",
"throw",
"try",
"type",
"with",
"of",
"abstract",
"class",
"interface",
"private",
"protected",
"pub",
"override",
"open",
"init",
"val",
"var",
"mut",
"clone",
];
switch (tt) { switch (tt) {
case "Datatype": case "Datatype":
@ -306,8 +233,7 @@ function translate_token_type(tt: TokenType, value: string): string {
} }
} }
const native_lex = (code: string, level: HighlightLevel) => const native_lex = (code: string, level: HighlightLevel) => new Promise<TokenizeResult>((resolve, reject) => {
new Promise<TokenizeResult>((resolve, reject) => {
// Get binary path from .env // Get binary path from .env
const binary = import.meta.env.THP_BINARY; const binary = import.meta.env.THP_BINARY;
if (!binary) { if (!binary) {
@ -336,4 +262,4 @@ const native_lex = (code: string, level: HighlightLevel) =>
reject(new Error(error)); reject(new Error(error));
} }
}); });
}); })

View File

@ -1,13 +1,13 @@
export type ReferenceItem = { export type ReferenceItem = {
symbol_start: number; symbol_start: number
symbol_end: number; symbol_end: number
reference: string; reference: string
}; }
export interface Token { export interface Token {
token_type: TokenType; token_type: TokenType
value: string; value: string
position: number; position: number
} }
export type TokenType = export type TokenType =
@ -40,29 +40,30 @@ export type TokenType =
| "IN" | "IN"
| "WHILE" | "WHILE"
| "MATCH" | "MATCH"
| "CASE"; | "CASE"
;
export interface MistiErr { export interface MistiErr {
error_code: number; error_code: number
error_offset: number; error_offset: number
labels: Array<ErrorLabel>; labels: Array<ErrorLabel>
note: string | null; note: string | null,
help: string | null; help: string | null,
} }
export interface ErrorLabel { export interface ErrorLabel {
message: string; message: string
start: number; start: number
end: number; end: number
} }
export interface TokenizeResult { export interface TokenizeResult {
/** All checks passed */ /** All checks passed */
Ok?: Array<Token>; Ok?: Array<Token>,
/** A non lexic error was found */ /** A non lexic error was found */
MixedErr?: [Array<Token>, MistiErr]; MixedErr?: [Array<Token>, MistiErr],
/** A lexic error was found */ /** A lexic error was found */
Err?: MistiErr; Err?: MistiErr,
} }
export enum HighlightLevel { export enum HighlightLevel {

View File

@ -1,15 +1,15 @@
export function is_digit(c: string): boolean { export function is_digit(c: string): boolean {
return c >= "0" && c <= "9"; return c >= '0' && c <= '9';
} }
export function is_lowercase(c: string): boolean { export function is_lowercase(c: string): boolean {
return c >= "a" && c <= "z"; return c >= 'a' && c <= 'z';
} }
export function is_uppercase(c: string): boolean { export function is_uppercase(c: string): boolean {
return c >= "A" && c <= "Z"; return c >= 'A' && c <= 'Z';
} }
export function is_identifier_char(c: string): boolean { export function is_identifier_char(c: string): boolean {
return is_lowercase(c) || is_uppercase(c) || is_digit(c) || c === "_"; return is_lowercase(c) || is_uppercase(c) || is_digit(c) || c === '_';
} }

View File

@ -1,11 +1,10 @@
--- ---
layout: ../../../layouts/ApiLayout.astro layout: ../../../layouts/ApiLayout.astro
--- ---
import TwoColumn from "../../../components/TwoColumn.astro"
import TwoColumn from "../../../components/TwoColumn.astro"; import Code from "../../../components/Code.astro"
import Code from "../../../components/Code.astro"; import CodeMin from "../../../components/docs/CodeMin.astro"
import CodeMin from "../../../components/docs/CodeMin.astro"; import Warning from "../../../components/docs/Warning.astro"
import Warning from "../../../components/docs/Warning.astro";
# Array # Array
@ -20,32 +19,28 @@ THP arrays are 0-indexed.
## Signature ## Signature
<Code <Code thpcode={`
thpcode={`
type Array[T] = Map[Int, T] type Array[T] = Map[Int, T]
`} `} />
/>
Where `T` is the datatype that the Array stores. For example: Where `T` is the datatype that the Array stores. For example:
<Code <Code thpcode={`
thpcode={`
Array[Int] // An array of integers Array[Int] // An array of integers
Array[Float] // An array of floats Array[Float] // An array of floats
Array[Array[String]] // A 2-dimensional array of strings Array[Array[String]] // A 2-dimensional array of strings
`} `} />
/>
## PHP array internals ## PHP array internals
<Warning> <Warning>
TL;DR: **Never** assign to an array using an invalid index. If you do the TL;DR: **Never** assign to an array using an invalid index. If you do
program will not crash, instead it will not behaved as expected [(this is a the program will not crash, instead it will not behaved as expected
common problem in [(this is a common problem in PHP)](https://eev.ee/blog/2012/04/09/php-a-fractal-of-bad-design/).
PHP)](https://eev.ee/blog/2012/04/09/php-a-fractal-of-bad-design/). THP tries THP tries its best to solve such behavior.
its best to solve such behavior.
</Warning> </Warning>
Since THP compiles down to PHP, it's important to understand how PHP Since THP compiles down to PHP, it's important to understand how PHP
represents arrays internally. represents arrays internally.
PHP doesn't have arrays. Instead, PHP has ordered maps and syntax sugar PHP doesn't have arrays. Instead, PHP has ordered maps and syntax sugar
@ -53,11 +48,9 @@ to make them look like arrays.
When declaring an array like: When declaring an array like:
<Code <Code thpcode={`
thpcode={`
var arr = ["a", "b", "c"] var arr = ["a", "b", "c"]
`} `} />
/>
in reality what goes into memory is a map with numbers as keys: in reality what goes into memory is a map with numbers as keys:
@ -109,7 +102,7 @@ numbers[7] = "!" // Out of bounds assignment
// In this loop, values will be printed following when // In this loop, values will be printed following when
// they were inserted, not by order of the index. // they were inserted, not by order of the index.
for #(\_, value) in numbers for #(_, value) in numbers
{ {
print("{value} ") // Will print: a b c ? ! print("{value} ") // Will print: a b c ? !
} }
@ -142,9 +135,12 @@ This is one of many fundamental flaws with PHP. The only way to solve it
would be to check every insertion at runtime, and this would have a would be to check every insertion at runtime, and this would have a
performance penalty. performance penalty.
From now on, the documentation will continue to work with the Array From now on, the documentation will continue to work with the Array
abstraction, as if all indexes were valid. abstraction, as if all indexes were valid.
## Usage ## Usage
### Empty array ### Empty array
@ -152,36 +148,31 @@ abstraction, as if all indexes were valid.
To create an empty array use square brackets. To create an empty array use square brackets.
If you create an empty array, you need to specify the datatype. If you create an empty array, you need to specify the datatype.
<Code <Code thpcode={`
thpcode={`
Array[Int] empty = [] Array[Int] empty = []
`} `} />
/>
### Creation ### Creation
To create an array use square brackets notation: To create an array use square brackets notation:
<Code <Code thpcode={`
thpcode={`
val numbers = [0, 1, 2, 3, 4, 5] val numbers = [0, 1, 2, 3, 4, 5]
`} `} />
/>
When the array is not empty, you don't need to specify a datatype. When the array is not empty, you don't need to specify a datatype.
When the Array is declared over many lines, the last When the Array is declared over many lines, the last
item should have a trailing comma: item should have a trailing comma:
<Code <Code thpcode={`
thpcode={`
val colors = [ val colors = [
"red", "red",
"blue", "blue",
"green", // trailing comma "green", // trailing comma
] ]
`} `} />
/>
If it doesn't, the code formatter will automatically If it doesn't, the code formatter will automatically
insert one for you. insert one for you.
@ -207,14 +198,13 @@ mutable[0] = 322
To append an element to an array, use the method `push()`: To append an element to an array, use the method `push()`:
<Code <Code thpcode={`
thpcode={`
mutable.push(4) mutable.push(4)
`} `} />
/>
Do not insert into an invalid index. See [PHP array internals](#php-array-internals) to learn why. Do not insert into an invalid index. See [PHP array internals](#php-array-internals) to learn why.
### Iteration ### Iteration
Use a `for` loop to iterate over the elements of an array: Use a `for` loop to iterate over the elements of an array:
@ -245,6 +235,7 @@ print("{c} ")
} }
`} /> `} />
You can also declare an index along with the value: You can also declare an index along with the value:
<Code thpcode={` <Code thpcode={`
@ -262,37 +253,41 @@ item 1: green
item 2: blue item 2: blue
``` ```
### Access ### Access
To access a value of the array use square brackets notation: To access a value of the array use square brackets notation:
<Code <Code thpcode={`
thpcode={`
print(colors[0]) print(colors[0])
`} `} />
/>
Since the index might not exist, this will return a Since the index might not exist, this will return a
[nullable type](/learn/error-handling/null/) that you have to handle. [nullable type](/learn/error-handling/null/) that you have to handle.
### Destructuring ### Destructuring
THP arrays don't have destructuring, since the values can all be `null`. THP arrays don't have destructuring, since the values can all be `null`.
If you know that the number of elements is fixed and valid, use Tuples instead. If you know that the number of elements is fixed and valid, use Tuples instead.
### Operators ### Operators
While PHP allows using certain operators with arrays, THP disallows that. While PHP allows using certain operators with arrays, THP disallows that.
Methods that perform comparisons should be used instead. Methods that perform comparisons should be used instead.
### Assignment ### Assignment
// TODO: Detail that assignment of arrays is copy on write // TODO: Detail that assignment of arrays is copy on write
## Methods ## Methods
In the parameters, <code class="token keyword">self</code> is the array to operate on. In the parameters, <code class="token keyword">self</code> is the array to operate on.
<table class="table-fixed w-full border-collapse border-2 dark:border-zinc-900 border-stone-400"> <table class="table-fixed w-full border-collapse border-2 dark:border-zinc-900 border-stone-400">
<thead> <thead>
<td class="p-2">Method</td> <td class="p-2">Method</td>
@ -301,34 +296,30 @@ In the parameters, <code class="token keyword">self</code> is the array to opera
<tbody> <tbody>
<tr class="dark:odd:bg-zinc-900 odd:bg-stone-200"> <tr class="dark:odd:bg-zinc-900 odd:bg-stone-200">
<td class="px-2"> <td class="px-2">
<CodeMin <CodeMin href="./concat" thpcode={`
href="./concat"
thpcode={`
fun concat[T]( fun concat[T](
self, self,
Array[T]... arrays, Array[T]... arrays,
) -> Array[T] ) -> Array[T]
`} `} />
/>
</td> </td>
<td> <td>
Concatenate with other arrays, and return the result as a new array. Concatenate with other arrays, and return the result
as a new array.
</td> </td>
</tr> </tr>
<tr class="dark:odd:bg-zinc-900 odd:bg-stone-200"> <tr class="dark:odd:bg-zinc-900 odd:bg-stone-200">
<td class="px-2"> <td class="px-2">
<CodeMin <CodeMin thpcode={`
thpcode={`
fun filter[T]( fun filter[T](
self, self,
(T) -> (Bool) callback, (T) -> (Bool) callback,
) -> Array[T] ) -> Array[T]
`} `} />
/>
</td> </td>
<td> <td>
Filters elements using a callback function, and returns them in a new Filters elements using a callback function, and returns
array. them in a new array.
</td> </td>
</tr> </tr>
<tr class="dark:odd:bg-zinc-900 odd:bg-stone-200"> <tr class="dark:odd:bg-zinc-900 odd:bg-stone-200">
@ -336,15 +327,19 @@ In the parameters, <code class="token keyword">self</code> is the array to opera
<CodeMin thpcode="fun push[T](self, T... elements) -> Int" /> <CodeMin thpcode="fun push[T](self, T... elements) -> Int" />
</td> </td>
<td> <td>
Appends one or more elements to the end of the array. Returns the new Appends one or more elements to the end of the array.
length of the array. Returns the new length of the array.
</td> </td>
</tr> </tr>
<tr class="dark:odd:bg-zinc-900 odd:bg-stone-200"> <tr class="dark:odd:bg-zinc-900 odd:bg-stone-200">
<td class="px-2"> <td class="px-2">
<CodeMin thpcode="fun pop[T](self) -> T" /> <CodeMin thpcode="fun pop[T](self) -> T" />
</td> </td>
<td>Removes the last value of the array, and returns it.</td> <td>
Removes the last value of the array, and returns it.
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View File

@ -1,8 +1,7 @@
--- ---
layout: ../../../../layouts/ApiLayout.astro layout: ../../../../layouts/ApiLayout.astro
--- ---
import Code from "../../../../components/Code.astro"
import Code from "../../../../components/Code.astro";
# `Array.concat` # `Array.concat`
@ -10,14 +9,12 @@ Concatenate with other arrays, and return the result as a new array.
## Signature ## Signature
<Code <Code thpcode={`
thpcode={`
fun concat[T]( fun concat[T](
self, self,
Array[T]... arrays, Array[T]... arrays,
) -> Array[T] ) -> Array[T]
`} `} />
/>
## Parameters ## Parameters
@ -28,6 +25,7 @@ Concatenate with other arrays, and return the result as a new array.
`Array[T]`: A new array that contains the elements from all arrays. `Array[T]`: A new array that contains the elements from all arrays.
## Description ## Description
Concatenates the elements of the callee and the elements of each array in the Concatenates the elements of the callee and the elements of each array in the
@ -51,7 +49,6 @@ Example concatenating 2 arrays:
val result = first.concat(second) val result = first.concat(second)
assert_eq(result, [1, 2, 3, 4, 5, 6]) assert_eq(result, [1, 2, 3, 4, 5, 6])
`} /> `} />
Example concatenating 3 arrays: Example concatenating 3 arrays:
@ -63,7 +60,6 @@ Example concatenating 3 arrays:
val result = first.concat(second, third) val result = first.concat(second, third)
assert_eq(result, ["a", "b", "c", "d", "e", "f"]) assert_eq(result, ["a", "b", "c", "d", "e", "f"])
`} /> `} />
Example concatenating without any other array. In this case Example concatenating without any other array. In this case
@ -74,7 +70,6 @@ Example concatenating without any other array. In this case
val result = first.concat() val result = first.concat()
assert_eq(result, [1, 2, 3]) assert_eq(result, [1, 2, 3])
`} /> `} />
Example concatenating an empty array with a filled array: Example concatenating an empty array with a filled array:
@ -85,9 +80,9 @@ Example concatenating an empty array with a filled array:
val result = first.concat(second) val result = first.concat(second)
assert_eq(result, [10, 20, 30]) assert_eq(result, [10, 20, 30])
`} /> `} />
## PHP interop ## PHP interop
This method is directly compiled to `array_merge`. Since THP guarantees that This method is directly compiled to `array_merge`. Since THP guarantees that
@ -112,3 +107,6 @@ array(3) {
[2]=> string(1) "c" [2]=> string(1) "c"
} }
``` ```

View File

@ -1,8 +1,7 @@
--- ---
layout: ../../../../layouts/ApiLayout.astro layout: ../../../../layouts/ApiLayout.astro
--- ---
import Code from "../../../../components/Code.astro"
import Code from "../../../../components/Code.astro";
# `Array.fold` # `Array.fold`
@ -12,15 +11,13 @@ the left.
## Signature ## Signature
<Code <Code thpcode={`
thpcode={`
fun fold[A, B]( fun fold[A, B](
self[A], self[A],
B init, B init,
(B acc, A next) -> (B) transform_function, (B acc, A next) -> (B) transform_function,
) -> B ) -> B
`} `} />
/>
## Parameters ## Parameters
@ -34,6 +31,7 @@ the left.
- Otherwise, returns the result of applying `transform_function` to the accumulator `acc`, - Otherwise, returns the result of applying `transform_function` to the accumulator `acc`,
and the next element `next`. and the next element `next`.
## Description ## Description
Fold allows you to transform an array of `A` into a single `B`, following Fold allows you to transform an array of `A` into a single `B`, following
@ -41,15 +39,14 @@ an arbitraty function.
For example, let's say that you have an array of numbers: For example, let's say that you have an array of numbers:
<Code <Code thpcode={`
thpcode={`
val digits = [1, 9, 9, 5] val digits = [1, 9, 9, 5]
`} `} />
/>
And you want to join all digits into a String like `"1985"`. You can And you want to join all digits into a String like `"1985"`. You can
achieve this with a fold. achieve this with a fold.
<Code thpcode={` <Code thpcode={`
val digits = [1, 9, 8, 5] val digits = [1, 9, 8, 5]
val init = "" val init = ""
@ -96,12 +93,10 @@ The `transform_function` can be any function, and can operate over any type.
For example, you could sum all numbers instead of concatenating For example, you could sum all numbers instead of concatenating
like this: like this:
<Code <Code thpcode={`
thpcode={`
val digits = [1, 9, 8, 5] val digits = [1, 9, 8, 5]
digits.fold(0, fun(acc, next) = acc + next) // 23 digits.fold(0, fun(acc, next) = acc + next) // 23
`} `} />
/>
```php ```php
f( f( f( f(0, 1), 9), 8), 5) f( f( f( f(0, 1), 9), 8), 5)
@ -123,3 +118,8 @@ val result = builder.build()
`} /> `} />
Or anything. Or anything.

View File

@ -1,8 +1,7 @@
--- ---
layout: ../../../../layouts/ApiLayout.astro layout: ../../../../layouts/ApiLayout.astro
--- ---
import Code from "../../../../components/Code.astro"
import Code from "../../../../components/Code.astro";
# `Array.map` # `Array.map`
@ -20,9 +19,10 @@ and returns their result in a new array.
self[], self[],
(A) -> (B) map_function (A) -> (B) map_function
) -> Array[B] ) -> Array[B]
`} /> `} />
<Code thpcode={` <Code thpcode={`
fun multi_def fun multi_def
@ -40,3 +40,4 @@ fmt.Println("prosor prosor %d", 322)
} }
`} /> `} />

View File

@ -1,9 +1,8 @@
--- ---
layout: ../../../layouts/ApiLayout.astro layout: ../../../layouts/ApiLayout.astro
--- ---
import TwoColumn from "../../../components/TwoColumn.astro"
import TwoColumn from "../../../components/TwoColumn.astro"; import Code from "../../../components/Code.astro"
import Code from "../../../components/Code.astro";
# module `std` # module `std`
@ -29,14 +28,12 @@ if (str_contains("abc", "a")) {
In THP there is no `str_contains` function. Instead, you'd call the In THP there is no `str_contains` function. Instead, you'd call the
`contains` method on the string: `contains` method on the string:
<Code <Code thpcode={`
thpcode={`
if "abc".contains("a") if "abc".contains("a")
{ {
// ... // ...
} }
`} `} />
/>
## On naming ## On naming
@ -59,11 +56,10 @@ If you need to use a PHP class with a lowercase name you can use the following s
class animal {} class animal {}
``` ```
<Code <Code thpcode={`
thpcode={`
val my_animal = 'animal() val my_animal = 'animal()
`} `} />
/>
## API: Datatypes ## API: Datatypes
@ -100,3 +96,5 @@ A [IEEE 754](https://en.wikipedia.org/wiki/IEEE_754) double precision floating p
Prints text into stdout. Prints text into stdout.
</TwoColumn> </TwoColumn>

View File

@ -1,9 +1,8 @@
--- ---
layout: ../../../layouts/ApiLayout.astro layout: ../../../layouts/ApiLayout.astro
--- ---
import TwoColumn from "../../../components/TwoColumn.astro"
import TwoColumn from "../../../components/TwoColumn.astro"; import Code from "../../../components/Code.astro"
import Code from "../../../components/Code.astro";
# `print` # `print`
@ -11,11 +10,9 @@ Prints to stdout.
## Signature ## Signature
<Code <Code thpcode={`
thpcode={`
fun print(String value) {} fun print(String value) {}
`} `} />
/>
## Description ## Description
@ -23,8 +20,10 @@ Prints a single `String` into stdout. Doesn't return anything.
## Examples ## Examples
<Code <Code thpcode={`
thpcode={`
print("Hello world!") print("Hello world!")
`} `} />
/>

View File

@ -3,13 +3,13 @@ layout: "../_wrapper.astro"
title: Comments title: Comments
order: 2 order: 2
--- ---
import Code from "@/components/Code.astro"
import Code from "@/components/Code.astro";
# Comments # Comments
THP supports single and multi line comments: THP supports single and multi line comments:
## Single line ## Single line
Begin with double slash `//` and continue until the end of the line. Begin with double slash `//` and continue until the end of the line.
@ -21,37 +21,33 @@ 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
These begin with `/*` and end with `*/`. Everything in between is ignored. 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 <Code thpcode={`
thpcode={`
/* /*
This is a This is a
multiline comment multiline comment
*/ */
`} `} />
/>
<Code <Code thpcode={`
thpcode={`
/* /*
Multiline comments Multiline comments
can be /* nested */ can be /* nested */
*/ */
`} `} />
/>
## Documentation comments ## Documentation comments
Documentation comments use triple slashes `///`. Documentation comments use triple slashes `///`.
These use [CommonMark Markdown](https://commonmark.org/) syntax. These use [CommonMark Markdown](https://commonmark.org/) syntax.
<Code <Code thpcode={`
thpcode={`
/// Transforms the format from A to B... /// Transforms the format from A to B...
/// ///
/// ## Errors /// ## Errors
@ -61,5 +57,6 @@ These use [CommonMark Markdown](https://commonmark.org/) syntax.
fun transform() { fun transform() {
// ... // ...
} }
`} `} />
/>

View File

@ -3,8 +3,7 @@ layout: "../_wrapper.astro"
title: Datatypes title: Datatypes
order: 4 order: 4
--- ---
import Code from "@/components/Code.astro"
import Code from "@/components/Code.astro";
# Datatypes # Datatypes
@ -35,48 +34,44 @@ Int not_octal = 032 // This is 32, not 26
// TODO: Make it a compile error to have leading zeroes, // TODO: Make it a compile error to have leading zeroes,
and force users to use `0o` for octal and force users to use `0o` for octal
## Float ## Float
Same as php float Same as php float
<Code
thpcode={` <Code thpcode={`
Float pi = 3.141592 Float pi = 3.141592
Float light = 2.99e+8 Float light = 2.99e+8
`} `} />
/>
## String ## String
THP strings use **only** double quotes. Single quotes are THP strings use **only** double quotes. Single quotes are
used elsewhere. used elsewhere.
<Code <Code thpcode={`
thpcode={`
String name = "Rose" String name = "Rose"
`} `} />
/>
Strings have interpolation with `{}`. Strings have interpolation with `{}`.
<Code <Code thpcode={`
thpcode={`
print("Hello, {name}") // Hello, Rose print("Hello, {name}") // Hello, Rose
`} `} />
/>
Unlike PHP, THP strings are concatenated with `++`, not with `.`. Unlike PHP, THP strings are concatenated with `++`, not with `.`.
This new operator implicitly converts any operator into a string. This new operator implicitly converts any operator into a string.
<Code <Code thpcode={`
thpcode={`
val name = "John" ++ " " ++ "Doe" val name = "John" ++ " " ++ "Doe"
val greeting = "My name is " ++ name ++ " and I'm " ++ 32 ++ " years old" val greeting = "My name is " ++ name ++ " and I'm " ++ 32 ++ " years old"
`} `} />
/>
The plus operator `+` is reserved for numbers. The plus operator `+` is reserved for numbers.
## Bool ## Bool
THP booleans are `true` and `false`. They are case sensitive, THP booleans are `true` and `false`. They are case sensitive,
@ -89,3 +84,4 @@ Bool is_false = false
// This is a compile error // This is a compile error
val invalid = TRUE val invalid = TRUE
`} /> `} />

View File

@ -3,9 +3,8 @@ layout: "../_wrapper.astro"
title: Hello world title: Hello world
order: 1 order: 1
--- ---
import InteractiveCode from "@/components/InteractiveCode.astro"; import InteractiveCode from "@/components/InteractiveCode.astro";
import Code from "@/components/Code.astro"; import Code from "@/components/Code.astro"
# Hello, world! # Hello, world!
@ -19,14 +18,13 @@ 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 <Code thpcode={`
thpcode={`
print("Hello, world!") print("Hello, world!")
`} `} />
/>
Then run `thp hello.thp` from your terminal. Then run `thp hello.thp` from your terminal.
## Instruction separation ## Instruction separation
THP uses whitespace to determine when a statement is over. In short, THP uses whitespace to determine when a statement is over. In short,
@ -38,12 +36,11 @@ echo("B");
echo("C"); echo("C");
``` ```
<Code <Code thpcode={`
thpcode={`
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

@ -3,11 +3,11 @@ layout: "../_wrapper.astro"
title: Operators title: Operators
order: 5 order: 5
--- ---
import Code from "@/components/Code.astro"
import Code from "@/components/Code.astro";
# Operators # Operators
Most of the PHP operators are present in THP. Most of the PHP operators are present in THP.
## Numbers ## Numbers
@ -17,13 +17,13 @@ var number = 322
number + 1 number + 1
number - 1 number - 1
number \* 1 number * 1
number / 1 number / 1
number % 2 number % 2
number += 1 number += 1
number -= 1 number -= 1
number \*= 1 number *= 1
number /= 1 number /= 1
number %= 2 number %= 2
`} /> `} />
@ -44,71 +44,62 @@ number++ // This is a compile error
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 <Code thpcode={`
thpcode={`
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 <Code thpcode={`
thpcode={`
v1 == v2 v1 == v2
v1 != v2 v1 != v2
`} `} />
/>
### Bitwise ### Bitwise
TBD TBD
<Code <Code thpcode={`
thpcode={`
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
Strings **do not use `.`** for concatenation. They use `++`. Strings **do not use `.`** for concatenation. They use `++`.
<Code <Code thpcode={`
thpcode={`
"Hello " ++ "world." "Hello " ++ "world."
`} `} />
/>
This new operator **implicitly converts** types to string This new operator **implicitly converts** types to string
<Code <Code thpcode={`
thpcode={`
"Hello " ++ 322 // 322 will be converted to "322" "Hello " ++ 322 // 322 will be converted to "322"
`} `} />
/>
## Boolean ## Boolean
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 <Code thpcode={`
thpcode={`
c1 && c2 c1 && c2
c1 || c2 c1 || c2
`} `} />
/>
## Ternary ## Ternary
There is no ternary operator. See [Conditionals](/learn/flow-control/conditionals) for alternatives. There is no ternary operator. See [Conditionals](/learn/flow-control/conditionals) for alternatives.
## Null ## Null
These are detailed in their section: [Nullable types](/learn/error-handling/null) These are detailed in their section: [Nullable types](/learn/error-handling/null)
@ -124,3 +115,6 @@ if person? {
person.name person.name
} }
`} /> `} />

View File

@ -3,8 +3,7 @@ layout: "../_wrapper.astro"
title: Variables title: Variables
order: 3 order: 3
--- ---
import Code from "@/components/Code.astro"
import Code from "@/components/Code.astro";
# Variables # Variables
@ -25,59 +24,47 @@ 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 <Code level={2} thpcode={`
level={2}
thpcode={`
val surname = "Doe" val surname = "Doe"
val year_of_birth = 1984 val year_of_birth = 1984
`} `} />
/>
It's a compile error to attempt to modify it It's a compile error to attempt to modify it
<Code <Code level={2} thpcode={`
level={2}
thpcode={`
val surname = "Doe" val surname = "Doe"
surname = "Dane" // Error surname = "Dane" // Error
`} `} />
/>
### 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 <Code level={2} thpcode={`
level={2}
thpcode={`
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 <Code level={2} thpcode={`
level={2}
thpcode={`
// 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.
It is a compile error to declare a variable of a datatype, It is a compile error to declare a variable of a datatype,
but use another. but use another.
<Code <Code level={2} thpcode={`
level={2}
thpcode={`
// Declare the variable as a String, but use a Float as its value // Declare the variable as a String, but use a Float as its value
String capital = 123.456 String capital = 123.456
`} `} />
/>
## Mutable variables ## Mutable variables
@ -94,21 +81,18 @@ age = 33
Written after the `var` keywords but before the variable name. Written after the `var` keywords but before the variable name.
<Code <Code level={2} thpcode={`
level={2}
thpcode={`
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 <Code level={2} thpcode={`
level={2}
thpcode={`
// 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,8 +2,7 @@
layout: "../_wrapper.astro" layout: "../_wrapper.astro"
title: Arrays title: Arrays
--- ---
import Code from "@/components/Code.astro"
import Code from "@/components/Code.astro";
# Arrays # Arrays
@ -28,15 +27,19 @@ numbers[3] = 5
print(numbers[3]) // 5 print(numbers[3]) // 5
`} /> `} />
## Type signature ## Type signature
<Code
thpcode={` <Code thpcode={`
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
problems with the language's grammar. problems with the language's grammar.

View File

@ -2,8 +2,7 @@
layout: "../_wrapper.astro" layout: "../_wrapper.astro"
title: Enums title: Enums
--- ---
import Code from "@/components/Code.astro"
import Code from "@/components/Code.astro";
# Enums # Enums
@ -34,8 +33,7 @@ val suit = Suit::Hearts
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 <Code thpcode={`
thpcode={`
enum Suit(String) enum Suit(String)
{ {
Hearts = "H", Hearts = "H",
@ -43,7 +41,8 @@ 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,8 +2,7 @@
layout: "../_wrapper.astro" layout: "../_wrapper.astro"
title: Maps title: Maps
--- ---
import Code from "@/components/Code.astro"
import Code from "@/components/Code.astro";
# Maps # Maps
@ -42,48 +41,41 @@ 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 <Code thpcode={`
thpcode={`
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 <Code thpcode={`
thpcode={`
mary_jane.age += 1 mary_jane.age += 1
print(mary_jane.name) print(mary_jane.name)
`} `} />
/>
## Anonymous maps ## Anonymous maps
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 <Code thpcode={`
thpcode={`
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 <Code thpcode={`
thpcode={`
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
@ -91,14 +83,12 @@ anonymous map.
We can freely assign fields to an anonymous map: We can freely assign fields to an anonymous map:
<Code <Code thpcode={`
thpcode={`
// 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.
@ -114,11 +104,9 @@ 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 <Code thpcode={`
thpcode={`
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

View File

@ -2,8 +2,7 @@
layout: "../_wrapper.astro" layout: "../_wrapper.astro"
title: Tuples title: Tuples
--- ---
import Code from "@/components/Code.astro"
import Code from "@/components/Code.astro";
# Tuples # Tuples
@ -17,10 +16,13 @@ val person = #("John", "Doe", 32)
val #(name, surname, age) = person val #(name, surname, age) = person
`} /> `} />
## Signature ## Signature
<Code <Code thpcode={`
thpcode={`
#(String, String, Int) #(String, String, Int)
`} `} />
/>

View File

@ -2,8 +2,7 @@
layout: "../_wrapper.astro" layout: "../_wrapper.astro"
title: Tagged unions title: Tagged unions
--- ---
import Code from "@/components/Code.astro"
import Code from "@/components/Code.astro";
# Tagged unions # Tagged unions
@ -24,8 +23,7 @@ val rectangle1 = Shape::Rectangle(5, 15)
## Pattern matching ## Pattern matching
<Code <Code thpcode={`
thpcode={`
match shape_1 match shape_1
case ::Square(side) case ::Square(side)
{ {
@ -35,8 +33,7 @@ case ::Rectangle(length, height)
{ {
print("Area of the rectangle: {length * height}") print("Area of the rectangle: {length * height}")
} }
`} `} />
/>
## Internal representation ## Internal representation
@ -45,6 +42,7 @@ When compiled down to PHP, tagged unions are a combination of an enum and an arr
THP creates an enum of the same name and with the same cases, and the values THP creates an enum of the same name and with the same cases, and the values
are contained as part of an array. are contained as part of an array.
```php ```php
// The first snippet is compiled to: // The first snippet is compiled to:
enum Shape enum Shape
@ -58,3 +56,4 @@ $dot = [Shape::Dot];
$square1 = [Shape::Square, 10]; $square1 = [Shape::Square, 10];
$rectangle1 = [Shape::Rectangle, 5, 15] $rectangle1 = [Shape::Rectangle, 5, 15]
``` ```

View File

@ -2,8 +2,7 @@
layout: "../_wrapper.astro" layout: "../_wrapper.astro"
title: Blocks title: Blocks
--- ---
import Code from "@/components/Code.astro"
import Code from "@/components/Code.astro";
# Blocks # Blocks
@ -18,8 +17,8 @@ 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,8 +2,7 @@
layout: "../_wrapper.astro" layout: "../_wrapper.astro"
title: Conditionals title: Conditionals
--- ---
import Code from "@/components/Code.astro"
import Code from "@/components/Code.astro";
# Conditionals # Conditionals
@ -13,6 +12,7 @@ import Code from "@/components/Code.astro";
- Paretheses for the condition are not required. - Paretheses for the condition are not required.
- There's no ternary operator - There's no ternary operator
<Code thpcode={` <Code thpcode={`
if condition { if condition {
// code // code
@ -24,20 +24,22 @@ else {
// even more code // even more code
} }
val result = if condition { value1 } else { value2 } val result = if condition { value1 } else { value2 }
`} /> `} />
## Check for datatypes ## Check for datatypes
TBD TBD
<Code <Code thpcode={`
thpcode={`
if variable is Datatype { if variable is Datatype {
// code using variable // code using variable
} }
`} `} />
/>
## If variable is of enum ## If variable is of enum
@ -54,3 +56,6 @@ 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,8 +2,7 @@
layout: "../_wrapper.astro" layout: "../_wrapper.astro"
title: Loops title: Loops
--- ---
import Code from "@/components/Code.astro"
import Code from "@/components/Code.astro";
# Loops # Loops
@ -35,6 +34,7 @@ print("{value}")
} }
`} /> `} />
### Loop over keys and values ### Loop over keys and values
<Code thpcode={` <Code thpcode={`
@ -57,6 +57,7 @@ print("{key} => {value}")
} }
`} /> `} />
## While loop ## While loop
<Code thpcode={` <Code thpcode={`
@ -69,19 +70,20 @@ index += 1
} }
`} /> `} />
## Labelled loops ## Labelled loops
TBD TBD
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 <Code thpcode={`
thpcode={`
:top for i in values_1 { :top for i in values_1 {
for j in values_2 { for j in values_2 {
// ... // ...
break :top break :top
} }
} }
`} `} />
/>

View File

@ -2,8 +2,7 @@
layout: "../_wrapper.astro" layout: "../_wrapper.astro"
title: Match title: Match
--- ---
import Code from "@/components/Code.astro"
import Code from "@/components/Code.astro";
# Match # Match
@ -14,6 +13,7 @@ Braces are **required**.
<Code thpcode={` <Code thpcode={`
val user_id = POST::get("user_id") val user_id = POST::get("user_id")
match user_id match user_id
case Some(id) { print("user_id exists: {id}") } case Some(id) { print("user_id exists: {id}") }
case None { print("user_id doesn't exist") } case None { print("user_id doesn't exist") }
@ -26,6 +26,7 @@ case None {
print("user_id doesn't exist") print("user_id doesn't exist")
} }
match user_id match user_id
case Some(id) if id > 0 { case Some(id) if id > 0 {
print("user_id exists: {id}") print("user_id exists: {id}")
@ -42,14 +43,15 @@ else {
print("hello dear") print("hello dear")
} }
match customer*id match customer_id
| 1 | 2 | 3 { | 1 | 2 | 3 {
print("ehhhh") print("ehhhh")
} }
| 4 | 5 { | 4 | 5 {
print("ohhh") print("ohhh")
} }
| * { | _ {
print("???") print("???")
} }
`} /> `} />

View File

@ -2,8 +2,7 @@
layout: "../_wrapper.astro" layout: "../_wrapper.astro"
title: Pipes title: Pipes
--- ---
import Code from "@/components/Code.astro"
import Code from "@/components/Code.astro";
# Pipes # Pipes
@ -12,30 +11,24 @@ expression as input to another.
For example, instead of writing: For example, instead of writing:
<Code <Code thpcode={`
thpcode={`
val result = third_function(second_function(first_function(my_value))) val result = third_function(second_function(first_function(my_value)))
`} `} />
/>
You can use pipes: You can use pipes:
<Code <Code thpcode={`
thpcode={`
val result = my_value val result = my_value
|> first_function |> first_function
|> second_function |> second_function
|> third_function |> third_function
`} `} />
/>
Or use it to group expressions: Or use it to group expressions:
<Code <Code thpcode={`
thpcode={`
print <| (2 + 3 * 4) / 7 print <| (2 + 3 * 4) / 7
`} `} />
/>
TBD: How to handle piping to functions with more than 1 param TBD: How to handle piping to functions with more than 1 param
@ -47,7 +40,7 @@ fun add_one(x: Int) -> Int {
} }
fun times_two(x: Int) -> Int { fun times_two(x: Int) -> Int {
x \* 2 x * 2
} }
// (Int) -> (Int) // (Int) -> (Int)
@ -55,3 +48,5 @@ val plus_one_times_2 = add_one >> times_two
print(plus_one_times_2(5)) // 12 print(plus_one_times_2(5)) // 12
`} /> `} />

View File

@ -2,8 +2,7 @@
layout: "../_wrapper.astro" layout: "../_wrapper.astro"
title: Declaration title: Declaration
--- ---
import Code from "@/components/Code.astro"
import Code from "@/components/Code.astro";
# Declaration # Declaration
@ -11,6 +10,7 @@ Functions in THP have a different syntax than PHP.
Function names **must** begin with a lowercase letter. Function names **must** begin with a lowercase letter.
## Minimal function ## Minimal function
The following code shows a function without parameters The following code shows a function without parameters
@ -27,12 +27,14 @@ say_hello()
Functions are called the same way as in PHP. Functions are called the same way as in PHP.
## With return type ## With return type
If your function return any value, annotating the return If your function return any value, annotating the return
type is **mandatory**, and it's done by placing an type is __mandatory__, and it's done by placing an
arrow `->` followed by the return datatype: arrow `->` followed by the return datatype:
<Code thpcode={` <Code thpcode={`
fun get_random_number() -> Int fun get_random_number() -> Int
{ {
@ -42,38 +44,38 @@ fun get_random_number() -> Int
val number = get_random_number() val number = get_random_number()
`} /> `} />
It's an error to return from a function that doesn't declare It's an error to return from a function that doesn't declare
a return type: a return type:
<Code <Code thpcode={`
thpcode={`
fun get_random_number() fun get_random_number()
{ {
// Error: the function does not define a return type // Error: the function does not define a return type
return Random::get(0, 35_222) return Random::get(0, 35_222)
} }
`} `} />
/>
You can omit the `return` keyword if it's placed on the last You can omit the `return` keyword if it's placed on the last
expression on the function: expression on the function:
<Code <Code thpcode={`
thpcode={`
fun get_random_number() -> Int fun get_random_number() -> Int
{ {
// The last expression of a function is // The last expression of a function is
// automatically returned // automatically returned
Random::get(0, 35_222) Random::get(0, 35_222)
} }
`} `} />
/>
## Function parameters ## Function parameters
Parameters are declared like C-style languages: Parameters are declared like C-style languages:
`Type name`, separated by commas. `Type name`, separated by commas.
<Code thpcode={` <Code thpcode={`
fun add(Int a, Int b) -> Int fun add(Int a, Int b) -> Int
{ {
@ -87,6 +89,8 @@ THP has different semantics on parameters concerning
pass by value, pass by reference and copy on write. pass by value, pass by reference and copy on write.
This is detailed in another chapter. This is detailed in another chapter.
## Generic types ## Generic types
Functions can declare generic types by using the syntax Functions can declare generic types by using the syntax
@ -95,6 +99,7 @@ Functions can declare generic types by using the syntax
The following example declares a generic `T` and uses it The following example declares a generic `T` and uses it
in the parameters and return type: in the parameters and return type:
<Code thpcode={` <Code thpcode={`
fun get_first_item[T](Array[T] array) -> T fun get_first_item[T](Array[T] array) -> T
{ {
@ -107,24 +112,27 @@ val first = get_first_item[Int](numbers)
val first = get_first_item(numbers) val first = get_first_item(numbers)
`} /> `} />
## Default arguments ## Default arguments
TBD TBD
## Variadic arguments ## Variadic arguments
TBD TBD
## Signature ## Signature
<Code
thpcode={` <Code thpcode={`
() -> () () -> ()
() -> (Int) () -> (Int)
(Int, Int) -> (Int) (Int, Int) -> (Int)
[T](Array[T]) -> (T) [T](Array[T]) -> (T)
`} `} />
/>
## Named arguments ## Named arguments
@ -157,3 +165,8 @@ fun greet(
greet("John", from: "LA") greet("John", from: "LA")
`} /> `} />

View File

@ -2,11 +2,11 @@
layout: "../_wrapper.astro" layout: "../_wrapper.astro"
title: Higher Order Functions title: Higher Order Functions
--- ---
import Code from "@/components/Code.astro"
import Code from "@/components/Code.astro";
# Higher Order functions # Higher Order functions
## Function as parameters ## Function as parameters
<Code thpcode={` <Code thpcode={`
@ -28,6 +28,10 @@ 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,45 +2,40 @@
layout: "../_wrapper.astro" layout: "../_wrapper.astro"
title: Lambdas title: Lambdas
--- ---
import Code from "@/components/Code.astro"
import Code from "@/components/Code.astro";
# Lambdas / Anonymous functions # Lambdas / Anonymous functions
## Anonymous function ## Anonymous function
An anonymous function is declared with `fn`. An anonymous function is declared with `fn`.
Other than not having a name, it can declare parameter Other than not having a name, it can declare parameter
and return types. and return types.
<Code <Code thpcode={`
thpcode={`
fn(Int x, Int y) -> Int { fn(Int x, Int y) -> Int {
x + y x + y
} }
`} `} />
/>
Anonymous function can omit declaring those types as well: Anonymous function can omit declaring those types as well:
<Code <Code thpcode={`
thpcode={`
numbers.map(fun(x) { numbers.map(fun(x) {
x * 2 x * 2
}) })
`} `} />
/>
## Anonymous function short form ## Anonymous function short form
If you need an anonymous function that returns a single If you need an anonymous function that returns a single
expression you can write it like this: expression you can write it like this:
<Code <Code thpcode={`
thpcode={`
fun(x, y) = x + y fun(x, y) = x + y
`} `} />
/>
It uses an equal `=` instead of an arrow. It can contain It uses an equal `=` instead of an arrow. It can contain
only a single expression. only a single expression.
@ -48,18 +43,20 @@ only a single expression.
This is common when declaring functions that immediately This is common when declaring functions that immediately
return a map. return a map.
<Code <Code thpcode={`
thpcode={`
fn(value) = .{ fn(value) = .{
value value
} }
`} `} />
/>
## Closure types ## Closure types
By default closures **always** capture variables as **references**. By default closures **always** capture variables as **references**.
<Code thpcode={` <Code thpcode={`
var x = 20 var x = 20
@ -73,15 +70,14 @@ 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 <Code thpcode={`
thpcode={`
fun(parameters) clone(variables) { fun(parameters) clone(variables) {
// code // code
} }
`} `} />
/>
<Code thpcode={` <Code thpcode={`
var x = 20 var x = 20
@ -96,6 +92,7 @@ x = 30
f() // 20 f() // 20
`} /> `} />
## Lambdas ## Lambdas
Lambdas are a short form of anonymous functions. They are declared with `#{}`. Lambdas are a short form of anonymous functions. They are declared with `#{}`.
@ -112,6 +109,9 @@ numbers.map() #{
// the above lambda is equivalent to: // the above lambda is equivalent to:
numbers.map(fun(param1) { numbers.map(fun(param1) {
$1 \* 2 $1 * 2
}) })
`} /> `} />

View File

@ -2,20 +2,18 @@
layout: "../_wrapper.astro" layout: "../_wrapper.astro"
title: Function parameters title: Function parameters
--- ---
import Code from "@/components/Code.astro"
import Code from "@/components/Code.astro";
# Function parameters # Function parameters
## Immutable reference ## Immutable reference
<Code <Code thpcode={`
thpcode={`
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
@ -25,21 +23,19 @@ 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 <Code thpcode={`
thpcode={`
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
@ -56,21 +52,22 @@ push_25(mut numbers) // Pass \`numbers\` as reference.
print(numbers(4)) // \`Some(25)\` print(numbers(4)) // \`Some(25)\`
`} /> `} />
## Clone ## Clone
<Code <Code thpcode={`
thpcode={`
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.
The caller must also use `clone` when calling the function. The caller must also use `clone` when calling the function.
<Code thpcode={` <Code thpcode={`
val numbers = Array(1, 2, 3, 4) val numbers = Array(1, 2, 3, 4)
@ -78,3 +75,5 @@ add_25(clone numbers) // Pass \`numbers\` as clone.
print(numbers(4)) // None print(numbers(4)) // None
`} /> `} />

View File

@ -2,8 +2,7 @@
layout: "../_wrapper.astro" layout: "../_wrapper.astro"
title: Nullable types title: Nullable types
--- ---
import Code from "@/components/Code.astro"
import Code from "@/components/Code.astro";
# Nullable types # Nullable types
@ -14,11 +13,9 @@ 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 <Code thpcode={`
thpcode={`
?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.
@ -74,6 +71,7 @@ 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`
## Null unboxing ## Null unboxing
The `!!` operator transforms a `?Type` into `Type`. The `!!` operator transforms a `?Type` into `Type`.
@ -92,17 +90,16 @@ String s = lastname!!
You can use it to chain access: You can use it to chain access:
<Code <Code thpcode={`
thpcode={`
val children_lastname = person!!.child!!.lastname val children_lastname = person!!.child!!.lastname
`} `} />
/>
However, if at runtime you use `!!` on a null value, However, if at runtime you use `!!` on a null value,
the null value will be returned and your program will the null value will be returned and your program will
blow up later. So make sure to use this operator blow up later. So make sure to use this operator
only when you are sure a value cannot be null. only when you are sure a value cannot be null.
## Elvis operator ## Elvis operator
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.
@ -117,12 +114,13 @@ val test_score = get_score() ?? 0
For the above code: For the above code:
- If `get_score()` is not null, `??` will return `get_score()` - If `get_score()` is not null, `??` will return `get_score()`
- If `get_score()` _is_ null, `??` will return `0` - If `get_score()` *is* null, `??` will return `0`
You can use the Elvis operator to return early You can use the Elvis operator to return early
<Code <Code thpcode={`
thpcode={`
val username = get_username() ?? return val username = get_username() ?? return
`} `} />
/>

View File

@ -2,9 +2,8 @@
layout: "../_wrapper.astro" layout: "../_wrapper.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"; import Code from "@/components/Code.astro"
# Try/exceptions # Try/exceptions
@ -28,7 +27,6 @@ fun invert(Int number) -> DivisionByZero!Int
} }
return 1 / number return 1 / number
} }
`} /> `} />
@ -38,6 +36,7 @@ or an `Int`.
There is no `throw` keyword, errors are just returned. There is no `throw` keyword, errors are just returned.
### Multiple error returns ### Multiple error returns
TODO: properly define syntax, how this interacts with type unions. TODO: properly define syntax, how this interacts with type unions.
@ -45,12 +44,12 @@ TODO: properly define syntax, how this interacts with type unions.
Multiple errors are chained with `!`. The last one is always Multiple errors are chained with `!`. The last one is always
the success value. the success value.
<Code <Code thpcode={`
thpcode={`
fun sample() -> Error1!Error2!Error3!Int fun sample() -> Error1!Error2!Error3!Int
{ /* ... */} { /* ... */}
`} `} />
/>
## Error handling ## Error handling
@ -111,11 +110,10 @@ Use a naked `try` when you want to rethrow an error, if there is any.
unset " = = =" unset " = = ="
set " dangerous()" "Int 50" set " dangerous()" "Int 50"
} }
`} `}
></InteractiveCode> ></InteractiveCode>
In the previous example: In the previous example:
- If `dangerous()` returns an `Exception`, this exception - If `dangerous()` returns an `Exception`, this exception
@ -123,6 +121,7 @@ In the previous example:
- If `dangerous()` succeedes, its value is assigned - If `dangerous()` succeedes, its value is assigned
to `result`, and the function continues executing. to `result`, and the function continues executing.
### Try/return ### Try/return
Try/return will return a new value if an expression fails, Try/return will return a new value if an expression fails,
@ -137,7 +136,6 @@ fun run() -> Int
val result = try dangerous() return 0 val result = try dangerous() return 0
// ... // ...
} }
`} /> `} />
@ -148,6 +146,7 @@ In the previous example:
- If `dangerous()` succeedes, its value will be assigned to `result`, - If `dangerous()` succeedes, its value will be assigned to `result`,
and the function continues executing. and the function continues executing.
### Try/else ### Try/else
Try/else will assign a new value if an expression fails. Try/else will assign a new value if an expression fails.
@ -211,9 +210,7 @@ Try/else will assign a new value if an expression fails.
} }
step{line 0} step{line 0}
`} `}
> >
</InteractiveCode> </InteractiveCode>
- If `possible_value` is an error, the value `666` is used. - If `possible_value` is an error, the value `666` is used.
@ -221,10 +218,12 @@ Try/else will assign a new value if an expression fails.
Either way, the function will continue executing. Either way, the function will continue executing.
### Try/catch ### Try/catch
Try/catch allows the error to be manually used & handled. Try/catch allows the error to be manually used & handled.
<Code thpcode={` <Code thpcode={`
fun run() fun run()
{ {
@ -240,14 +239,12 @@ fun run()
// 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 <Code thpcode={`
thpcode={`
try dangerous() try dangerous()
catch Exception1 e catch Exception1 e
{...} {...}
@ -255,5 +252,6 @@ catch Exception2 e
{...} {...}
catch Exception3 e catch Exception3 e
{...} {...}
`} `} />
/>

View File

@ -2,7 +2,8 @@
layout: "../_wrapper.astro" layout: "../_wrapper.astro"
title: Abstract title: Abstract
--- ---
import Code from "@/components/Code.astro"
import Code from "@/components/Code.astro";
# Abstract # Abstract

View File

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

View File

@ -2,11 +2,11 @@
layout: "../_wrapper.astro" layout: "../_wrapper.astro"
title: Constructor/Destructor title: Constructor/Destructor
--- ---
import Code from "@/components/Code.astro"
import Code from "@/components/Code.astro";
# Constructor/Destructor # Constructor/Destructor
## Constructor ## Constructor
The constructor syntax in THP is inspired by Kotlin. The constructor syntax in THP is inspired by Kotlin.
@ -37,6 +37,7 @@ If you want to declare a constructor as protected
or private you need to add the `constructor` or private you need to add the `constructor`
keyword, after the visibility modifier: keyword, after the visibility modifier:
<Code thpcode={` <Code thpcode={`
// Cow has a protected constructor // Cow has a protected constructor
class Cow class Cow
@ -47,6 +48,7 @@ class Bat
private constructor(Int height) private constructor(Int height)
`} /> `} />
### Init block ### Init block
The `init` block allow us to run code during the The `init` block allow us to run code during the
@ -60,7 +62,6 @@ class Dog(String name) {
init { init {
print("Dog created: {name}") print("Dog created: {name}")
} }
} }
`} /> `} />
@ -79,13 +80,15 @@ class Dog {
} }
``` ```
### Constructor promotion ### Constructor promotion
Constructor parameters can serve as class properties. Constructor parameters can serve as class properties.
This is done by adding a modifier and `var`/`val`. This is done by adding a modifier and `var`/`val`.
<Code <Code thpcode={`
thpcode={`
class Parrot( class Parrot(
// A public property // A public property
pub val String name, pub val String name,
@ -94,14 +97,15 @@ class Parrot(
// A private property // A private property
var String last_name, var String last_name,
) )
`} `} />
/>
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.
The contructor parameters can also have default values. The contructor parameters can also have default values.
### Derived properties ### Derived properties
You can declare properties whose values depend on values You can declare properties whose values depend on values
@ -121,37 +125,40 @@ val a2 = Animal("Doa")
print(a2.name_length) //: 3 print(a2.name_length) //: 3
`} /> `} />
### Constructor that may fail ### Constructor that may fail
A constructor may only fail if there is code A constructor may only fail if there is code
that can fail on the `init` block. that can fail on the `init` block.
TBD TBD
Proposal 1: Proposal 1:
<Code <Code thpcode={`
thpcode={`
class PrayingMantis(String name) class PrayingMantis(String name)
throws Error { throws Error {
init -> Error! { init -> Error! {
// Initialization code that may fail // Initialization code that may fail
} }
} }
`} `} />
/>
## Destructor ## Destructor
The destructor in THP is the same as PHP. The destructor in THP is the same as PHP.
<Code <Code thpcode={`
thpcode={`
class Owl() { class Owl() {
pub fun __destruct() { pub fun __destruct() {
// Cleanup // Cleanup
print("Destroying Owl...") print("Destroying Owl...")
} }
} }
`} `} />
/>

View File

@ -2,8 +2,7 @@
layout: "../_wrapper.astro" layout: "../_wrapper.astro"
title: Basics title: Basics
--- ---
import Code from "@/components/Code.astro"
import Code from "@/components/Code.astro";
# Classes # Classes
@ -15,27 +14,25 @@ Classes in THP are significantly different than PHP.
A class is defined as follows: A class is defined as follows:
<Code <Code thpcode={`
thpcode={`
class Animal() {} class Animal() {}
`} `} />
/>
The name of the class **MUST** begin with an uppercase letter. The name of the class **MUST** begin with an uppercase letter.
Classes have a parameter list even if they have no parameters Classes have a parameter list even if they have no parameters
for consistency sake. for consistency sake.
## Instanciation ## Instanciation
To create an instance of a class call it as if it was a function, To create an instance of a class call it as if it was a function,
without `new`. without `new`.
<Code <Code thpcode={`
thpcode={`
val animal = Animal() val animal = Animal()
`} `} />
/>
## Properties ## Properties
@ -53,7 +50,6 @@ class Person() {
// This is also okay // This is also okay
String name = "Jane Doe" String name = "Jane Doe"
} }
`} /> `} />
@ -66,7 +62,6 @@ class Person() {
// To make a property public use \`pub\` // To make a property public use \`pub\`
pub var Int age = 30 pub var Int age = 30
} }
val p = Person() val p = Person()
@ -84,19 +79,19 @@ Readonly properties are explained in the Readonly page.
The interaction between properties and the constructor is explained The interaction between properties and the constructor is explained
in the Constructor page. in the Constructor page.
## Methods ## Methods
Methods are declared with `fun`, as regular functions. Methods are declared with `fun`, as regular functions.
<Code <Code thpcode={`
thpcode={`
class Person() { class Person() {
fun greet() { fun greet() {
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`.
@ -111,7 +106,6 @@ class Person() {
pub fun greet() { pub fun greet() {
print("Hello from greet") print("Hello from greet")
} }
} }
val p = Person() val p = Person()
@ -131,10 +125,10 @@ class Person() {
fun name() -> String { fun name() -> String {
"Rose" "Rose"
} }
} }
`} /> `} />
## This ## This
THP uses the dollar sign `$` as this inside classes. THP uses the dollar sign `$` as this inside classes.
@ -152,23 +146,21 @@ class Person() {
val person_name = $get_name() val person_name = $get_name()
print("Hello, I'm {person_name}") print("Hello, I'm {person_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 <Code thpcode={`
thpcode={`
class Animal(var String name) { class Animal(var String name) {
pub fun set_name(String new_name) { pub fun set_name(String new_name) {
$name = new_name // Error: Cannot mutate $name $name = new_name // Error: Cannot mutate $name
} }
} }
`} `} />
/>
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.
@ -183,3 +175,4 @@ class Animal(var String name) {
var michi = Animal("Michifu") var michi = Animal("Michifu")
michi.set_name("Garfield") michi.set_name("Garfield")
`} /> `} />

View File

@ -2,8 +2,7 @@
layout: "../_wrapper.astro" layout: "../_wrapper.astro"
title: Inheritance title: Inheritance
--- ---
import Code from "@/components/Code.astro"
import Code from "@/components/Code.astro";
# Inheritance # Inheritance
@ -29,14 +28,12 @@ Cat("Michi", 9).say_name()
The call to the parent constructor is done right there, after The call to the parent constructor is done right there, after
the parent class name. the parent class name.
<Code <Code thpcode={`
thpcode={`
class Dog(String name) class Dog(String name)
extends Animal(name) {} extends Animal(name) {}
// |----| // |----|
// This is the call to the parent constructor // This is the call to the parent constructor
`} `} />
/>
You must always call super, even if the parent doesn't You must always call super, even if the parent doesn't
define any parameters: define any parameters:
@ -47,3 +44,10 @@ class Parent() {}
class Child() class Child()
extends Parent() {} extends Parent() {}
`} /> `} />

View File

@ -2,11 +2,12 @@
layout: "../_wrapper.astro" layout: "../_wrapper.astro"
title: Interfaces title: Interfaces
--- ---
import Code from "@/components/Code.astro"
import Code from "@/components/Code.astro";
# Interfaces # Interfaces
<Code thpcode={` <Code thpcode={`
interface Serializable interface Serializable
{ {
@ -14,6 +15,8 @@ interface Serializable
fun serialize() -> String fun serialize() -> String
} }
class Cat -> Serializable class Cat -> Serializable
{ {
pub fun Serializable() -> String pub fun Serializable() -> String
@ -25,3 +28,4 @@ pub fun Serializable() -> String
`} /> `} />
No interface inheritance. No interface inheritance.

View File

@ -2,8 +2,7 @@
layout: "../_wrapper.astro" layout: "../_wrapper.astro"
title: Magic methods title: Magic methods
--- ---
import Code from "@/components/Code.astro"
import Code from "@/components/Code.astro";
# Magic methods # Magic methods
@ -13,7 +12,7 @@ Don't get special treatment.
class Cat class Cat
{ {
pub fun \_\_sleep() -> Array[String] pub fun __sleep() -> Array[String]
{ {
// logic // logic
} }
@ -21,6 +20,7 @@ pub fun \_\_sleep() -> Array[String]
`} /> `} />
<Code thpcode={` <Code thpcode={`
val option = Some("GAAA") val option = Some("GAAA")
val Some(value) = option val Some(value) = option

View File

@ -2,15 +2,14 @@
layout: "../_wrapper.astro" layout: "../_wrapper.astro"
title: Readonly title: Readonly
--- ---
import Code from "@/components/Code.astro"
import Code from "@/components/Code.astro";
# Readonly # Readonly
<Code <Code thpcode={`
thpcode={`
class Caño class Caño
{ {
} }
`} `} />
/>

View File

@ -2,11 +2,11 @@
layout: "../_wrapper.astro" layout: "../_wrapper.astro"
title: Static title: Static
--- ---
import Code from "@/components/Code.astro"
import Code from "@/components/Code.astro";
# Static in classes # Static in classes
## Class constants ## Class constants
<Code thpcode={` <Code thpcode={`
@ -23,10 +23,12 @@ const CONSTANT = "constant value"
print(Cat::CONSTANT) print(Cat::CONSTANT)
`} /> `} />
## Static methods ## Static methods
aka. plain, old functions aka. plain, old functions
<Code thpcode={` <Code thpcode={`
static Cat static Cat
{ {
@ -39,10 +41,12 @@ static Cat
Cat::static_method() Cat::static_method()
`} /> `} />
## Static properties ## Static properties
aka. global variables aka. global variables
<Code thpcode={` <Code thpcode={`
static Cat static Cat
{ {
@ -53,3 +57,7 @@ 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: "../_wrapper.astro" layout: "../_wrapper.astro"
title: Visibility title: Visibility
--- ---
import Code from "@/components/Code.astro"
import Code from "@/components/Code.astro";
# Visibility # Visibility

View File

@ -2,9 +2,8 @@
layout: "../_wrapper.astro" layout: "../_wrapper.astro"
title: Components title: Components
--- ---
import Code from "@/components/Code.astro"
import Code from "@/components/Code.astro"; import Info from "@/components/docs/Info.astro"
import Info from "@/components/docs/Info.astro";
# Components # Components
@ -15,17 +14,14 @@ Like React, a component is any function that returns `HTML`.
will be uppercase or not. will be uppercase or not.
For now they will be uppercase. For now they will be uppercase.
</Info> </Info>
<Code <Code thpcode={`
thpcode={`
fun MyComponent() -> HTML fun MyComponent() -> HTML
{ {
<p>Hello templates!</p> <p>Hello templates!</p>
} }
`} `} />
/>
Inside the HTML tags you can (mostly) write the HTML you already Inside the HTML tags you can (mostly) write the HTML you already
know. know.
@ -41,7 +37,6 @@ fun MyComponent() -> HTML
val name = "John" val name = "John"
<p>Hello {name}!</p> <p>Hello {name}!</p>
} }
`} /> `} />
@ -62,7 +57,6 @@ fun MyComponent() -> HTML
{if is_vip {"Welcome"} else {"Hi"}} {if is_vip {"Welcome"} else {"Hi"}}
{name}! {name}!
</p> </p>
} }
`} /> `} />
@ -80,10 +74,8 @@ fun MyComponent() -> HTML
val user_input = "<b>BOLD</b>" val user_input = "<b>BOLD</b>"
<p>answer: {user_input}</p> <p>answer: {user_input}</p>
} }
`} /> `} />
```html ```html
<p>answer: &lt;b&gt;BOLD&lt;/b&gt;</p> <p>answer: &lt;b&gt;BOLD&lt;/b&gt;</p>
``` ```
@ -96,30 +88,25 @@ fun MyComponent() -> HTML
val user_input = "<b>BOLD</b>" val user_input = "<b>BOLD</b>"
<p>answer: <span raw-html={user_input}></span></p> <p>answer: <span raw-html={user_input}></span></p>
} }
`} /> `} />
```html ```html
<p> <p>answer: <span><b>BOLD</b></span></p>
answer: <span><b>BOLD</b></span>
</p>
``` ```
## Dynamic attributes ## Dynamic attributes
TODO: boolean attributes TODO: boolean attributes
Normal attributes (plain strings) work as you'd expect: Normal attributes (plain strings) work as you'd expect:
<Code <Code thpcode={`
thpcode={`
fun MyComponent() -> HTML fun MyComponent() -> HTML
{ {
<button class="red">hello</button> <button class="red">hello</button>
} }
`} `} />
/>
```html ```html
<button class="red">hello</button> <button class="red">hello</button>
``` ```
@ -134,30 +121,29 @@ fun MyComponent() -> HTML
// Note the braces // Note the braces
<button class={button_class}>hello</button> <button class={button_class}>hello</button>
} }
`} /> `} />
```html ```html
<button class="blue">hello</button> <button class="blue">hello</button>
``` ```
## Fragments ## Fragments
An HTML expression consist of a single tag that may have children inside. An HTML expression consist of a single tag that may have children inside.
If you need to return multiple tags at once you can use fragments. If you need to return multiple tags at once you can use fragments.
The following code doesn't work as you would expect: The following code doesn't work as you would expect:
<Code <Code thpcode={`
thpcode={`
fun MyComponent() -> HTML fun MyComponent() -> HTML
{ {
<p>hello</p> // This is an error, an ignored expression <p>hello</p> // This is an error, an ignored expression
<p>world</p> <p>world</p>
} }
`} `} />
/>
Each `<p>` is a single expression, they are not grouped. Each `<p>` is a single expression, they are not grouped.
And since we cannot have unused expressions, the code will not compile. And since we cannot have unused expressions, the code will not compile.
@ -165,8 +151,7 @@ And since we cannot have unused expressions, the code will not compile.
To have these two `<p>` tags as a single expression use a fragment: To have these two `<p>` tags as a single expression use a fragment:
`<></>` `<></>`
<Code <Code thpcode={`
thpcode={`
fun MyComponent() -> HTML fun MyComponent() -> HTML
{ {
// This is the root "element" // This is the root "element"
@ -175,8 +160,7 @@ fun MyComponent() -> HTML
<p>world</p> <p>world</p>
</> </>
} }
`} `} />
/>
```html ```html
<p>hello</p> <p>hello</p>
<p>world</p> <p>world</p>
@ -195,14 +179,16 @@ fun User() -> HTML
fun MyComponent() -> HTML fun MyComponent() -> HTML
{ {
<> <>
<p>status</p> <p>status</p>
<User /> // Here we are using the other component <User /> // Here we are using the other component
</> </>
} `} /> }
`} />
```html ```html
<p>status</p> <p>status</p>
<span>world</span> <span>world</span>
``` ```

View File

@ -2,9 +2,8 @@
layout: "../_wrapper.astro" layout: "../_wrapper.astro"
title: Control flow title: Control flow
--- ---
import Code from "@/components/Code.astro"
import Code from "@/components/Code.astro"; import Info from "@/components/docs/Info.astro"
import Info from "@/components/docs/Info.astro";
# Control flow # Control flow
@ -13,8 +12,7 @@ import Info from "@/components/docs/Info.astro";
Use `@if`, `@else if` and `@else` to branch during the template Use `@if`, `@else if` and `@else` to branch during the template
creation. creation.
<Code <Code thpcode={`
thpcode={`
fun User(User user) -> HTML fun User(User user) -> HTML
{ {
<div> <div>
@ -28,8 +26,8 @@ fun User(User user) -> HTML
} }
</div> </div>
} }
`} `} />
/>
## Loops ## Loops
@ -46,16 +44,15 @@ fun Users() -> HTML
<User user={user} /> <User user={user} />
} }
</div> </div>
} }
`} /> `} />
## Match ## Match
Use `@match` for pattern matching: Use `@match` for pattern matching:
<Code <Code thpcode={`
thpcode={`
fun UserDetail(User user) -> HTML fun UserDetail(User user) -> HTML
{ {
<div> <div>
@ -70,5 +67,8 @@ fun UserDetail(User user) -> HTML
} }
</div> </div>
} }
`} `} />
/>

View File

@ -2,9 +2,8 @@
layout: "../_wrapper.astro" layout: "../_wrapper.astro"
title: Introduction title: Introduction
--- ---
import Code from "@/components/Code.astro"
import Code from "@/components/Code.astro"; import Info from "@/components/docs/Info.astro"
import Info from "@/components/docs/Info.astro";
# THP templating # THP templating
@ -63,19 +62,18 @@ and compose them.
The following would be the equivalent in THP: The following would be the equivalent in THP:
<Code <Code thpcode={`
thpcode={`
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.
<Code thpcode={` <Code thpcode={`
fun User(String name) -> HTML { fun User(String name) -> HTML {
// Get info from the database // Get info from the database
@ -94,15 +92,15 @@ fun User(String name) -> HTML {
<TransactionItem t={t} /> <TransactionItem t={t} />
} }
</div> </div>
} }
fun TransactionItem(Transaction t) -> HTML { fun TransactionItem(Transaction t) -> HTML {
<li> <li>
{t.date} - {t.name} ({t.price}) {t.date} - {t.name} ({t.price})
</li> </li>
} `} /> }
`} />
## Is this a JavaScript Front-End Framework? ## Is this a JavaScript Front-End Framework?
@ -116,14 +114,19 @@ If you need reactivity on the front-end and want to use this templating
take a look at [htmx](https://htmx.org/), [Alpine.js](https://alpinejs.dev/) take a look at [htmx](https://htmx.org/), [Alpine.js](https://alpinejs.dev/)
and [hyperscript](https://hyperscript.org/). and [hyperscript](https://hyperscript.org/).
## Styling ## Styling
We don't provide any syntax or facility for styling. We don't provide any syntax or facility for styling.
However, this component model is good to use with TailwindCSS. However, this component model is good to use with TailwindCSS.
## Conversion ## Conversion
<Info>TBD: This is a draft, subject to change.</Info> <Info>
TBD: This is a draft, subject to change.
</Info>
HTML expressions will be compiled to plain strings, and HTML expressions will be compiled to plain strings, and
those will be then composed. those will be then composed.
@ -142,7 +145,6 @@ fun Sample(String name) -> HTML {
Not logged in Not logged in
} }
</button> </button>
} }
`} /> `} />
@ -161,3 +163,6 @@ function Sample(name) {
return "<button>{$__expr_1}</button>" return "<button>{$__expr_1}</button>"
} }
``` ```

View File

@ -2,9 +2,8 @@
layout: "../_wrapper.astro" layout: "../_wrapper.astro"
title: Props title: Props
--- ---
import Code from "@/components/Code.astro"
import Code from "@/components/Code.astro"; import Info from "@/components/docs/Info.astro"
import Info from "@/components/docs/Info.astro";
# Props # Props
@ -16,27 +15,23 @@ and they are defined as normal parameters.
For example, to receive a `String` declare it as a For example, to receive a `String` declare it as a
parameter: parameter:
<Code <Code thpcode={`
thpcode={`
fun Greeter(String name) -> HTML fun Greeter(String name) -> HTML
{ {
<p>Hello {name}!</p> <p>Hello {name}!</p>
} }
`} `} />
/>
And to send its value type the parameter name as an attribute: And to send its value type the parameter name as an attribute:
<Code <Code thpcode={`
thpcode={`
fun Home() -> HTML fun Home() -> HTML
{ {
<div> <div>
<Greeter name="Rose" /> <Greeter name="Rose" />
</div> </div>
} }
`} `} />
/>
```html ```html
<div><p>Hello Rose</p></div> <div><p>Hello Rose</p></div>
@ -44,21 +39,18 @@ fun Home() -> HTML
You can have as many props as you'd like, of any datatype: You can have as many props as you'd like, of any datatype:
<Code <Code thpcode={`
thpcode={`
fun Greeter(String name, Int age, Array[String] friends) -> HTML fun Greeter(String name, Int age, Array[String] friends) -> HTML
{ {
// ... // ...
} }
`} `} />
/>
## Static props ## Static props
If the prop has a type `String` you can use a normal attribute. If the prop has a type `String` you can use a normal attribute.
<Code <Code thpcode={`
thpcode={`
fun Home() -> HTML fun Home() -> HTML
{ {
<div> <div>
@ -66,8 +58,8 @@ fun Home() -> HTML
<Greeter name="Rose" /> <Greeter name="Rose" />
</div> </div>
} }
`} `} />
/>
## Dynamic props ## Dynamic props
@ -88,17 +80,16 @@ val my_cat = Cat("Michifu")
<div> <div>
<Sample cat={my_cat} /> <Sample cat={my_cat} />
</div> </div>
} }
`} /> `} />
## Components as props ## Components as props
If for some reason you want to use a component as a prop If for some reason you want to use a component as a prop
use the `HTML` datatype: use the `HTML` datatype:
<Code <Code thpcode={`
thpcode={`
// The parameter can have any name, not only \`child\` // The parameter can have any name, not only \`child\`
fun Sample(HTML child) -> HTML fun Sample(HTML child) -> HTML
{ {
@ -107,22 +98,19 @@ fun Sample(HTML child) -> HTML
{child} {child}
</> </>
} }
`} `} />
/>
This, however, means that your prop component must be declared This, however, means that your prop component must be declared
as an attribute: as an attribute:
<Code <Code thpcode={`
thpcode={`
fun Home() -> HTML fun Home() -> HTML
{ {
<div> <div>
<Sample child={<span>I am the child</span>} /> <Sample child={<span>I am the child</span>} />
</div> </div>
} }
`} `} />
/>
```html ```html
<div> <div>
@ -148,16 +136,21 @@ fun MyButton() -> HTML
fun Home() -> HTML fun Home() -> HTML
{ {
<div> <div>
<MyButton> <MyButton>
buy <b>now!</b> buy <b>now!</b>
</MyButton> </MyButton>
</div> </div>
} `} /> }
`} />
```html ```html
<div> <div>
<button>buy <b>now!</b></button> <button>
buy <b>now!</b>
</button>
</div> </div>
``` ```

View File

@ -4,13 +4,10 @@ import NewDocsLayout, { type AstroFile } from "@/layouts/NewDocsLayout.astro";
const { frontmatter, headings } = Astro.props; const { frontmatter, headings } = Astro.props;
// Get all the posts from this dir // Get all the posts from this dir
const posts = (await Astro.glob( const posts = await Astro.glob("./**/*.{md,mdx}") as unknown as Array<AstroFile>;
"./**/*.{md,mdx}",
)) as unknown as Array<AstroFile>;
// The base of every URL under this glob // The base of every URL under this glob
const base_url = "/en/latest/learn"; const base_url = "/en/latest/learn"
const version = "latest";
--- ---
<NewDocsLayout <NewDocsLayout
@ -18,7 +15,6 @@ const version = "latest";
frontmatter={frontmatter} frontmatter={frontmatter}
headings={headings} headings={headings}
posts={posts} posts={posts}
version={version}
> >
<slot /> <slot />
</NewDocsLayout> </NewPagesLayout>

View File

@ -3,9 +3,9 @@ layout: "./_wrapper.astro"
title: Welcome title: Welcome
order: 1 order: 1
--- ---
import InteractiveCode from "@/components/InteractiveCode.astro"; import InteractiveCode from "@/components/InteractiveCode.astro";
import Code from "@/components/Code.astro"; import Code from "@/components/Code.astro"
# Welcome # Welcome
@ -15,9 +15,11 @@ THP is a new programming language that compiles to PHP.
![Accurate visual description of THP](/img/desc_thp.jpg) ![Accurate visual description of THP](/img/desc_thp.jpg)
This page details the main design desitions of the language, This page details the main design desitions of the language,
if you want to install THP go to the [installation guide](install) if you want to install THP go to the [installation guide](install)
## Why? ## Why?
PHP is an old language. It has been growing since 1995, adopting a PHP is an old language. It has been growing since 1995, adopting a
@ -69,6 +71,10 @@ essential today: A good, unified LSP, type definitions,
accesible documentation, an opinionated code formatter accesible documentation, an opinionated code formatter
and plugins for major editors like VSCode and Neovim. and plugins for major editors like VSCode and Neovim.
## Goals ## Goals
- Bring static typing to PHP: Generics, type checks at compile and runtime, etc. - Bring static typing to PHP: Generics, type checks at compile and runtime, etc.
@ -87,6 +93,7 @@ and plugins for major editors like VSCode and Neovim.
![Friendship ended with Rust, now Zig is my best friend.](/img/mudasir.jpg) ![Friendship ended with Rust, now Zig is my best friend.](/img/mudasir.jpg)
## Not goals ## Not goals
These are **not** things that THP wants to solve or implement These are **not** things that THP wants to solve or implement
@ -97,6 +104,7 @@ These are **not** things that THP wants to solve or implement
THP **_intentionally_** uses a different syntax from PHP to signal THP **_intentionally_** uses a different syntax from PHP to signal
that it is a different language, and has different semantics. that it is a different language, and has different semantics.
## Some differences with PHP ## Some differences with PHP
```php ```php
@ -105,13 +113,11 @@ $has_key = str_contains($haystack, 'needle');
print("has key? " . $has_key); print("has key? " . $has_key);
``` ```
<Code <Code thpcode={`
thpcode={`
// 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)
@ -120,6 +126,7 @@ print("has key? " + has_key)
- Strings use only double quotes - Strings use only double quotes
- String concatenation with `+` - String concatenation with `+`
<br/> <br/>
<br/> <br/>
@ -132,16 +139,14 @@ $obj = [
] ]
``` ```
<Code <Code thpcode={`
thpcode={`
// 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
@ -155,13 +160,12 @@ $cat = new Cat("Michifu", 7);
$cat->meow(); $cat->meow();
``` ```
<Code
thpcode={` <Code thpcode={`
// 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
@ -175,12 +179,11 @@ use \Some\Deeply\Nested\Class
use \Some\Deeply\Nested\Interface use \Some\Deeply\Nested\Interface
``` ```
<Code
thpcode={` <Code thpcode={`
// 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
@ -190,19 +193,20 @@ use Some::Deeply::Nested::{Class, Interface}
<br/> <br/>
<br/> <br/>
Other things: Other things:
- Pattern matching - Pattern matching
- ADTs - ADTs
## Runtime changes ## Runtime changes
Where possible THP will compile to available PHP functions/classes/methods/etc. Where possible THP will compile to available PHP functions/classes/methods/etc.
For example: For example:
<Code <Code thpcode={`
thpcode={`
// This expression // This expression
val greeting = val greeting =
match get_person() match get_person()
@ -218,8 +222,7 @@ val greeting =
{ {
"Nobody is here" "Nobody is here"
} }
`} `} />
/>
```php ```php
// Would compile to: // Would compile to:
@ -240,3 +243,5 @@ else {
However, more advanced datatypes & helper functions will require a sort of However, more advanced datatypes & helper functions will require a sort of
runtime (new classes/functions/etc) or abuse the language's syntax/semantics. runtime (new classes/functions/etc) or abuse the language's syntax/semantics.

View File

@ -1,24 +0,0 @@
---
import NewDocsLayout, { type AstroFile } from "@/layouts/NewDocsLayout.astro";
const { frontmatter, headings } = Astro.props;
// Get all the posts from this dir
const posts = (await Astro.glob(
"./**/*.{md,mdx}",
)) as unknown as Array<AstroFile>;
// The base of every URL under this glob
const base_url = "/en/v0.0.1/learn";
const version = "v0.0.1";
---
<NewDocsLayout
base_url={base_url}
frontmatter={frontmatter}
headings={headings}
posts={posts}
version={version}
>
<slot />
</NewDocsLayout>

View File

@ -1,12 +0,0 @@
---
layout: "./_wrapper.astro"
title: Welcome
order: 1
---
import InteractiveCode from "@/components/InteractiveCode.astro";
import Code from "@/components/Code.astro";
# THP :D
Version 0.0.1

View File

@ -45,8 +45,8 @@ const [thp_html] = await native_highlighter(
compiled to PHP compiled to PHP
</h1> </h1>
<p class="text-c-text text-lg pt-6 lg:pr-12"> <p class="text-c-text text-lg pt-6 lg:pr-12">
Inspired by Rust, Zig and Kotlin, THP has a modern syntax, semantics, Inspired by Rust, Zig and Kotlin, THP has a modern syntax,
type system and stdlib. semantics, type system and stdlib.
</p> </p>
<div class="text-center pb-4"> <div class="text-center pb-4">
<a <a
@ -85,7 +85,11 @@ const [thp_html] = await native_highlighter(
height="14" height="14"
viewBox="0 0 54 14" viewBox="0 0 54 14"
> >
<g fill="none" fill-rule="evenodd" transform="translate(1 1)"> <g
fill="none"
fill-rule="evenodd"
transform="translate(1 1)"
>
<circle <circle
cx="6" cx="6"
cy="6" cy="6"
@ -140,10 +144,10 @@ const [thp_html] = await native_highlighter(
val result = mock() as String val result = mock() as String
`} `}
> >
Type safety is enforced at compile time. Everything has a specific type. Type safety is enforced at compile time. Everything
There is no `mixed`. has a specific type. There is no `mixed`.
<br /> <br>
<br /> <br>
You can use generics where neccesary. You can use generics where neccesary.
</HeroSection> </HeroSection>
@ -195,9 +199,9 @@ const [thp_html] = await native_highlighter(
} }
`} `}
> >
Nulls are explicit and require handling. Types can be nullable, and they Nulls are explicit and require handling. Types can be nullable,
must be checked before usage. and they must be checked before usage.
<br /> <br>
The stdlib makes extensive use of them. The stdlib makes extensive use of them.
</HeroSection> </HeroSection>
@ -212,9 +216,11 @@ const [thp_html] = await native_highlighter(
} }
`} `}
> >
Exceptions are values and don't disrupt control flow. Exceptions are values and don't disrupt
<br /> control flow.
<br /> <br>
Errors cannot be ignored, and we have syntax sugar to ease them. <br>
Errors cannot be ignored, and we have
syntax sugar to ease them.
</HeroSection> </HeroSection>
</BaseLayout> </BaseLayout>

View File

@ -4,12 +4,10 @@ import NewDocsLayout, { type AstroFile } from "@/layouts/NewDocsLayout.astro";
const { frontmatter, headings } = Astro.props; const { frontmatter, headings } = Astro.props;
// Get all the posts from this dir // Get all the posts from this dir
const posts = (await Astro.glob( const posts = await Astro.glob("./**/*.{md,mdx}") as unknown as Array<AstroFile>;
"./**/*.{md,mdx}",
)) as unknown as Array<AstroFile>;
// The base of every URL under this glob // The base of every URL under this glob
const base_url = "/spec"; const base_url = "/spec"
--- ---
<NewDocsLayout <NewDocsLayout
@ -19,4 +17,5 @@ const base_url = "/spec";
posts={posts} posts={posts}
> >
<slot /> <slot />
</NewDocsLayout> </NewPagesLayout>

View File

@ -69,6 +69,7 @@ BlockMember = Statement
| Expression | Expression
``` ```
## Assignment ## Assignment
The target of an assignment can only be an identifier for now. The target of an assignment can only be an identifier for now.
@ -86,3 +87,5 @@ AssignmentOperator = "="
| "/=" | "/="
| "%=" | "%="
``` ```

View File

@ -8,12 +8,14 @@ title: Expression
The expression parser effectively implements a precedence table. The expression parser effectively implements a precedence table.
| Operator | Precedence | | Operator | Precedence |
| --------- | ---------- | |------------|------------|
| == != | 5 | | == != | 5 |
| > >= < <= | 4 | | > >= < <= | 4 |
| - + ++ | 3 | | - + ++ | 3 |
| . ?. !. | 2 | | . ?. !. | 2 |
| / \* % | 1 | | / * % | 1 |
```ebnf ```ebnf
Expression = Equality Expression = Equality
@ -27,6 +29,7 @@ Unary = ("!" | "-"), Expression
| CallExpression | CallExpression
``` ```
## CallExpression ## CallExpression
It's so hard to properly name these constructions. It's so hard to properly name these constructions.
@ -37,3 +40,6 @@ CallExpression = primary, "(", (arguments list)?, ")"
| primary, "[", (expression, (comma, expression)*, comma?)? "]" | primary, "[", (expression, (comma, expression)*, comma?)? "]"
| primary | primary
``` ```

View File

@ -2,8 +2,8 @@
layout: "./_wrapper.astro" layout: "./_wrapper.astro"
title: Welcome title: Welcome
--- ---
import Code from "@/components/Code.astro"
import Code from "@/components/Code.astro";
# The THP Language Specification # The THP Language Specification
@ -42,11 +42,14 @@ The compiler consists of 5 common phases:
- **IR**: Transforms the THP AST into a PHP AST - **IR**: Transforms the THP AST into a PHP AST
- **Codegen**: Generates PHP source code from the PHP AST - **Codegen**: Generates PHP source code from the PHP AST
## Source Code representation ## Source Code representation
Source code is encoded in UTF-8, and a single UTF-8 codepoint is Source code is encoded in UTF-8, and a single UTF-8 codepoint is
a single character. a single character.
## Basic characters ## Basic characters
Although the source code must be encoded in UTF-8, most of the actual Although the source code must be encoded in UTF-8, most of the actual
@ -65,6 +68,7 @@ lowercase_letter = "a".."z"
uppercase_letter = "A".."Z" uppercase_letter = "A".."Z"
``` ```
## Whitespace & Automatic semicolon insertion ## Whitespace & Automatic semicolon insertion
This section is being reworked on the Zig rewrite of the compiler. This section is being reworked on the Zig rewrite of the compiler.
@ -81,12 +85,10 @@ parenthesis, square brackets, etc.
Other statements require a explicit terminator. For example, Other statements require a explicit terminator. For example,
the assignment statement: the assignment statement:
<Code <Code thpcode={`
thpcode={`
val computation = 123 + 456 // how to detect if the statement ends here val computation = 123 + 456 // how to detect if the statement ends here
* 789 // or extends up to here? * 789 // or extends up to here?
`} `} />
/>
In other languages a semicolon would be used to signal the end of the In other languages a semicolon would be used to signal the end of the
statement: statement:
@ -104,25 +106,21 @@ to the rule:
No matter the indentation, whitespace or others, every statement ends No matter the indentation, whitespace or others, every statement ends
with a newline. with a newline.
<Code <Code thpcode={`
thpcode={`
val compute = 1 + 2 * 3 / 4 val compute = 1 + 2 * 3 / 4
// statement ends here ↑ // statement ends here ↑
`} `} />
/>
As mentioned before, this does not affect statements that have clear delimiters. As mentioned before, this does not affect statements that have clear delimiters.
For example, the following code will work as expected: For example, the following code will work as expected:
<Code <Code thpcode={`
thpcode={`
val compute = my_function( val compute = my_function(
param1, param1,
param2, param2,
) / 64 ) / 64
// ↑ statement ends here // ↑ statement ends here
`} `} />
/>
In a way, the parenthesis will "disable" the rule. In a way, the parenthesis will "disable" the rule.
@ -135,13 +133,11 @@ continues.
For example: For example:
<Code <Code thpcode={`
thpcode={`
val computation = 123 + 456 val computation = 123 + 456
* 789 * 789
// ↑ statement ends here, and there is a single statement // ↑ statement ends here, and there is a single statement
`} `} />
/>
This is so no matter the indentation: This is so no matter the indentation:
@ -149,26 +145,25 @@ This is so no matter the indentation:
// weird indentation: // weird indentation:
val computation = 123 + 456 val computation = 123 + 456
* 789
- 789
// ↑ statement still ends here // ↑ statement still ends here
`} /> `} />
What is important is that an operator begins the new line. What is important is that an operator begins the new line.
If the operator is left on the previous line, this will not work: If the operator is left on the previous line, this will not work:
<Code <Code thpcode={`
thpcode={`
// statement ends here ↓, and now there is a syntax error (dangling operator) // statement ends here ↓, and now there is a syntax error (dangling operator)
val computation = 123 + 456 * val computation = 123 + 456 *
789 789
// ↑ this is a different statement // ↑ this is a different statement
`} `} />
/>
For this the parser must do look-ahead of 1 token. This is the only place the parser For this the parser must do look-ahead of 1 token. This is the only place the parser
does so. does so.
## Old Whitespace rules ## Old Whitespace rules
THP is partially whitespace sensitive. It uses the following tokens: Indent, Dedent & NewLine THP is partially whitespace sensitive. It uses the following tokens: Indent, Dedent & NewLine
@ -179,33 +174,32 @@ compares the previous indentation to the new one. If the amount of whitespace is
greater than before, it emits a Indent token. If it's lower, emits a Dedent token, and greater than before, it emits a Indent token. If it's lower, emits a Dedent token, and
if it's the same it does nothing. if it's the same it does nothing.
<Code
thpcode={` <Code thpcode={`
1 + 2 1 + 2
+ 3 + 3
+ 4 + 4
`} `} />
/>
The previous code would emit the following tokens: `1` `+` `2` `NewLine` `Indent` `+` `3` `NewLine` The previous code would emit the following tokens: `1` `+` `2` `NewLine` `Indent` `+` `3` `NewLine`
`+` `4` `Dedent` `+` `4` `Dedent`
Additionaly, it is a lexical error to have wrong indentation. The lexer stores all Additionaly, it is a lexical error to have wrong indentation. The lexer stores all
previous indentation levels in a stack, and reports an error if a decrease in indentation previous indentation levels in a stack, and reports an error if a decrease in indentation
doesn't match a previous level. doesn't match a previous level.
<Code <Code thpcode={`
thpcode={`
if true { // 0 indentation if true { // 0 indentation
print() // 4 indentation print() // 4 indentation
print() // 2 indentation. Error. There is no 2-indentation level print() // 2 indentation. Error. There is no 2-indentation level
} }
`} `} />
/>
All productions of the grammar ignore whitespace/indentation, except those involved in All productions of the grammar ignore whitespace/indentation, except those involved in
semicolon inference. semicolon inference.
## Statement termination / Semicolon inference ## Statement termination / Semicolon inference
**Only inside a block of code** whitespace is used to determine where a statement ends **Only inside a block of code** whitespace is used to determine where a statement ends
@ -213,19 +207,18 @@ and a new one begins. Everywhere else whitespace is ignored.
Statements in THP end when a new line is encountered: Statements in THP end when a new line is encountered:
<Code
thpcode={`
<Code thpcode={`
// The statement ends | here, on the newline // The statement ends | here, on the newline
val value = (123 + 456) * 0.75 val value = (123 + 456) * 0.75
`} `} />
/>
<Code thpcode={` <Code thpcode={`
// Each line contains a different statement. They all end on their new lines // Each line contains a different statement. They all end on their new lines
var a = 1 + 2 // a = 3 var a = 1 + 2 // a = 3
+ 3 // this is not part of \`a\`, this is a different statement
- 3 // this is not part of \`a\`, this is a different statement
`} /> `} />
This is true even if the line ends with an operator: This is true even if the line ends with an operator:
@ -237,19 +230,18 @@ var a = 1 + 2 + // This is now a compile error, there is a hanging `+`
3 // This is still a different statement 3 // This is still a different statement
`} /> `} />
### Parenthesis ### Parenthesis
Exception 1: When a parenthesis is open, all following whitespace is ignored Exception 1: When a parenthesis is open, all following whitespace is ignored
until the closing parenthesis. until the closing parenthesis.
<Code <Code thpcode={`
thpcode={`
// open parenthesis found, all whitespace is ignored until the closing // open parenthesis found, all whitespace is ignored until the closing
name.contains( name.contains(
"weird" "weird"
) )
`} `} />
/>
However, for a parenthesis to begin to act, it needs to be open on the same line. However, for a parenthesis to begin to act, it needs to be open on the same line.
@ -272,21 +264,17 @@ Exception 2:
- When a binary operator is followed by indentation: - When a binary operator is followed by indentation:
<Code <Code thpcode={`
thpcode={`
val sum = 1 + 2 + // The line ends with a binary operator val sum = 1 + 2 + // The line ends with a binary operator
3 // There is indentation 3 // There is indentation
`} `} />
/>
- Or when indentation is followed by a binary operator: - Or when indentation is followed by a binary operator:
<Code <Code thpcode={`
thpcode={`
val sum = 1 + 2 val sum = 1 + 2
+ 3 // Indentation and a binary operator + 3 // Indentation and a binary operator
`} `} />
/>
In theses cases, all whitespace will be ignored In theses cases, all whitespace will be ignored
until the indentation returns to the initial level. until the indentation returns to the initial level.
@ -303,3 +291,5 @@ val person = PersonBuilder()
// Here indentation returns, and a new statement begins // Here indentation returns, and a new statement begins
print(person) print(person)
`} /> `} />

View File

@ -2,8 +2,7 @@
layout: "../_wrapper.astro" layout: "../_wrapper.astro"
title: Comment title: Comment
--- ---
import Code from "@/components/Code.astro"
import Code from "@/components/Code.astro";
# Comment # Comment
@ -11,10 +10,8 @@ import Code from "@/components/Code.astro";
Comment = "//", any_except_new_line Comment = "//", any_except_new_line
``` ```
<Code <Code thpcode={`
thpcode={`
// This is a comment // This is a comment
// //
// Another // comment // Another // comment
`} `} />
/>

View File

@ -2,8 +2,7 @@
layout: "../_wrapper.astro" layout: "../_wrapper.astro"
title: Identifiers & Datatypes title: Identifiers & Datatypes
--- ---
import Code from "@/components/Code.astro"
import Code from "@/components/Code.astro";
# Identifiers & Datatypes # Identifiers & Datatypes
@ -20,15 +19,14 @@ Identifier = (underscore | lowercase_letter), identifier_letter*
identifier_letter = underscore | lowercase_letter | uppercase_letter | decimal_digit identifier_letter = underscore | lowercase_letter | uppercase_letter | decimal_digit
``` ```
<Code <Code thpcode={`
thpcode={`
identifier identifier
_identifier _identifier
_123 _123
_many_letters _many_letters
camelCase camelCase
`} `} />
/>
## Datatype ## Datatype
@ -36,23 +34,22 @@ camelCase
Datatype = uppercase_letter, indentifier_letter* Datatype = uppercase_letter, indentifier_letter*
``` ```
<Code <Code thpcode={`
thpcode={`
Datatype Datatype
PDO PDO
WEIRD_DATATYPE WEIRD_DATATYPE
`} `} />
/>
## Keywords ## Keywords
The following are (currently) THP keywords: The following are (currently) THP keywords:
<Code <Code thpcode={`
thpcode={`
val var fun val var fun
`} `} />
/>
Keywords are scanned first as identifiers, then transformed Keywords are scanned first as identifiers, then transformed
to their respective tokens. to their respective tokens.

View File

@ -2,8 +2,7 @@
layout: "../_wrapper.astro" layout: "../_wrapper.astro"
title: Numbers title: Numbers
--- ---
import Code from "@/components/Code.astro"
import Code from "@/components/Code.astro";
# Numbers # Numbers
@ -21,8 +20,7 @@ binary_number = "0", ("b" | "B"), binary_digit+
decimal_number = "1".."9", decimal_digit* decimal_number = "1".."9", decimal_digit*
``` ```
<Code <Code thpcode={`
thpcode={`
12345 12345
01234 // This is an error, a decimal number cant have 01234 // This is an error, a decimal number cant have
// leading zeroes // leading zeroes
@ -30,11 +28,12 @@ decimal_number = "1".."9", decimal_digit*
0b0110 0b0110
0xff25 0xff25
0XFfaA 0XFfaA
`} `} />
/>
`TODO`: Allow underscores `_` between any number: `1_000_000`. `TODO`: Allow underscores `_` between any number: `1_000_000`.
## Float ## Float
```ebnf ```ebnf
@ -53,7 +52,12 @@ scientific_notation = "e", ("+" | "-"), decimal_digit+
123e-3 123e-3
`} /> `} />
All floating point numbers must start with at least 1 digit. All floating point numbers must start with at least 1 digit.
`.5` is not a valid floating point number. `.5` is not a valid floating point number.
`TODO`: Allow scientific notation to omit the `+`/`-`: `10e4`. `TODO`: Allow scientific notation to omit the `+`/`-`: `10e4`.

View File

@ -2,11 +2,11 @@
layout: "../_wrapper.astro" layout: "../_wrapper.astro"
title: Operator title: Operator
--- ---
import Code from "@/components/Code.astro"
import Code from "@/components/Code.astro";
# Operator # Operator
```ebnf ```ebnf
Operator = operator_char+ Operator = operator_char+
@ -15,11 +15,9 @@ operator_char = "+" | "-" | "=" | "*" | "!" | "/" | "|"
| "<" | ">" | "^" | "." | ":" | "<" | ">" | "^" | "." | ":"
``` ```
<Code <Code thpcode={`
thpcode={`
+ - / * % < > <= >= -> => + - / * % < > <= >= -> =>
`} `} />
/>
These are all the characters that can make an operator. These are all the characters that can make an operator.

View File

@ -2,8 +2,7 @@
layout: "../_wrapper.astro" layout: "../_wrapper.astro"
title: String title: String
--- ---
import Code from "@/components/Code.astro"
import Code from "@/components/Code.astro";
# String # String
@ -21,13 +20,11 @@ escape_seq = "\n"
string_char = any_unicode_except_newline_and_double_quote string_char = any_unicode_except_newline_and_double_quote
``` ```
<Code <Code thpcode={`
thpcode={`
"hello" "hello"
"" ""
"it's me" "it's me"
"\\"Mario\\"" "\\"Mario\\""
`} `} />
/>
`TODO`: String interpolation `TODO`: String interpolation

View File

@ -34,3 +34,5 @@ pub enum TokenType {
``` ```
Every keyword has its own token. Every keyword has its own token.

View File

@ -1,6 +1,6 @@
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
export default { export default {
content: ["./src/**/*.{astro,html,js,jsx,md,mdx,ts,tsx}"], content: ['./src/**/*.{astro,html,js,jsx,md,mdx,ts,tsx}'],
theme: { theme: {
extend: { extend: {
colors: { colors: {
@ -17,48 +17,48 @@ export default {
"c-primary": "var(--c-primary)", "c-primary": "var(--c-primary)",
"c-secondary": "var(--c-secondary)", "c-secondary": "var(--c-secondary)",
"c-nav-bg": "var(--c-nav-bg)", "c-nav-bg": "var(--c-nav-bg)",
}, }
}, },
fontFamily: { fontFamily: {
mono: "var(--font-code)", "mono": "var(--font-code)",
display: "var(--font-display)", "display": "var(--font-display)",
body: "var(--font-body)", "body": "var(--font-body)",
}, },
}, },
corePlugins: { corePlugins: {
container: false, container: false
}, },
plugins: [ plugins: [
function ({ addComponents }) { function ({ addComponents }) {
addComponents({ addComponents({
".container": { '.container': {
width: "98%", width: '98%',
"@screen sm": { '@screen sm': {
maxWidth: "640px", maxWidth: '640px',
}, },
"@screen md": { '@screen md': {
maxWidth: "768px", maxWidth: '768px',
}, },
"@screen lg": { '@screen lg': {
maxWidth: "1024px", maxWidth: '1024px',
}, },
"@screen xl": { '@screen xl': {
maxWidth: "1400px", maxWidth: '1400px',
}, },
}, },
".small-container": { '.small-container': {
width: "98%", width: '98%',
"@screen sm": { '@screen sm': {
maxWidth: "640px", maxWidth: '640px',
}, },
"@screen md": { '@screen md': {
maxWidth: "768px", maxWidth: '768px',
}, },
"@screen lg": { '@screen lg': {
maxWidth: "inherit", maxWidth: 'inherit',
},
},
});
}, },
}
})
}
], ],
}; }

View File

@ -3,7 +3,7 @@
"compilerOptions": { "compilerOptions": {
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {
"@/*": ["src/*"] "@/*": ["src/*"],
} }
} }
} }