feat: add prettier
This commit is contained in:
parent
b5e22cca62
commit
5814b145fe
13
.prettierrc.mjs
Normal file
13
.prettierrc.mjs
Normal file
@ -0,0 +1,13 @@
|
||||
/** @type {import("prettier").Config} */
|
||||
export default {
|
||||
plugins: ["prettier-plugin-astro"],
|
||||
useTabs: true,
|
||||
overrides: [
|
||||
{
|
||||
files: "*.astro",
|
||||
options: {
|
||||
parser: "astro",
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
@ -1,4 +1,4 @@
|
||||
import { defineConfig } from 'astro/config';
|
||||
import { defineConfig } from "astro/config";
|
||||
import tailwind from "@astrojs/tailwind";
|
||||
|
||||
import mdx from "@astrojs/mdx";
|
||||
|
@ -20,4 +20,3 @@ networks:
|
||||
proxy:
|
||||
name: proxy
|
||||
external: true
|
||||
|
||||
|
@ -8,6 +8,7 @@
|
||||
"build": "astro build",
|
||||
"test": "vitest run",
|
||||
"preview": "astro preview",
|
||||
"prettier": "prettier \"./src/**/*.astro\" --write",
|
||||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
@ -19,5 +20,9 @@
|
||||
"tailwindcss": "^3.4.3",
|
||||
"typescript": "^5.4.5",
|
||||
"vitest": "^1.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "^3.3.3",
|
||||
"prettier-plugin-astro": "^0.14.1"
|
||||
}
|
||||
}
|
5156
pnpm-lock.yaml
5156
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -9,19 +9,19 @@
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Iosevka Fixed Web';
|
||||
font-family: "Iosevka Fixed Web";
|
||||
font-display: swap;
|
||||
font-weight: 700;
|
||||
font-stretch: normal;
|
||||
font-style: normal;
|
||||
src: url('/Iosevka/Bold.woff2') format('woff2');
|
||||
src: url("/Iosevka/Bold.woff2") format("woff2");
|
||||
}
|
||||
|
||||
:root {
|
||||
--c-thp: #f472b6;
|
||||
|
||||
--font-display: 'Atkinson Hyperlegible', sans-serif;
|
||||
--font-body: 'Atkinson Hyperlegible', sans-serif;
|
||||
--font-display: "Atkinson Hyperlegible", sans-serif;
|
||||
--font-body: "Atkinson Hyperlegible", sans-serif;
|
||||
--font-code: "Iosevka Fixed Web", "Iosevka Nerd Font", Iosevka, monospace;
|
||||
}
|
||||
|
||||
@ -30,10 +30,10 @@
|
||||
--c-text: rgb(200, 200, 200);
|
||||
--c-text-2: white;
|
||||
--c-primary: #884b6a;
|
||||
--c-purple: #7F669D;
|
||||
--c-purple-light: #BA94D1;
|
||||
--c-box-shadow: #FBFACD;
|
||||
--c-pink: #AE508D;
|
||||
--c-purple: #7f669d;
|
||||
--c-purple-light: #ba94d1;
|
||||
--c-box-shadow: #fbfacd;
|
||||
--c-pink: #ae508d;
|
||||
--c-link: #38bdf8;
|
||||
|
||||
--c-nav-bg: rgb(18, 18, 18, 0.5);
|
||||
@ -46,7 +46,7 @@
|
||||
--c-text: #121212;
|
||||
--c-text-2: black;
|
||||
--c-purple: #374259;
|
||||
--c-purple-light: #BA94D1;
|
||||
--c-purple-light: #ba94d1;
|
||||
--c-box-shadow: #374259;
|
||||
--c-primary: rgb(255, 180, 180);
|
||||
--c-pink: #374259;
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
.markdown > h1 {
|
||||
font-size: 2.25rem;
|
||||
line-height: 2.5rem;
|
||||
@ -102,4 +101,3 @@
|
||||
.two-column h3 {
|
||||
margin: 0.75rem 0;
|
||||
}
|
||||
|
||||
|
117
public/css/prism.min.css
vendored
117
public/css/prism.min.css
vendored
@ -1,3 +1,118 @@
|
||||
/* PrismJS 1.29.0
|
||||
https://prismjs.com/download.html#themes=prism */
|
||||
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}
|
||||
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: 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;
|
||||
}
|
||||
|
2855
public/js/alpine-3.14.0.min.js
vendored
2855
public/js/alpine-3.14.0.min.js
vendored
File diff suppressed because one or more lines are too long
@ -5,7 +5,10 @@ import CodeError from "./docs/CodeError.astro";
|
||||
|
||||
const { thpcode, no_warnings, level } = Astro.props;
|
||||
|
||||
const [native_html, error_message] = await native_highlighter(thpcode, level as HighlightLevel);
|
||||
const [native_html, error_message] = await native_highlighter(
|
||||
thpcode,
|
||||
level as HighlightLevel,
|
||||
);
|
||||
---
|
||||
|
||||
<pre
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
import Code from "./Code.astro"
|
||||
import Code from "./Code.astro";
|
||||
|
||||
const { thpcode, no_warnings, level } = Astro.props;
|
||||
---
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
const { showSidebarButton = true } = Astro.props;
|
||||
const { showSidebarButton = true, version = "latest" } = Astro.props;
|
||||
---
|
||||
|
||||
<nav
|
||||
@ -14,18 +14,11 @@ const { showSidebarButton = true } = Astro.props;
|
||||
)
|
||||
}
|
||||
|
||||
<a
|
||||
href="/"
|
||||
class="px-4 flex gap-2 items-center"
|
||||
>
|
||||
<img
|
||||
class="inline-block h-10"
|
||||
src="/img/thp_logo_exp.svg"
|
||||
alt="thp"
|
||||
/>
|
||||
<a 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
|
||||
href="/en/latest/learn/"
|
||||
href={`/en/${version}/learn/`}
|
||||
class="hidden sm:inline-block px-4 font-display font-bold-text-xl hover:underline"
|
||||
>
|
||||
Learn
|
||||
@ -51,7 +44,9 @@ const { showSidebarButton = true } = Astro.props;
|
||||
const sidebarToggle = document.getElementById("sidebar-toggle");
|
||||
|
||||
if (!sidebar || !sidebarToggle) {
|
||||
console.log("Sidebar or Sidebar toggle not found. Not enabling sidebar on mobile");
|
||||
console.log(
|
||||
"Sidebar or Sidebar toggle not found. Not enabling sidebar on mobile",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -7,10 +7,7 @@ const post_url = entry.url + (entry.url.endsWith("/") ? "" : "/");
|
||||
// this may deal with folders.
|
||||
// if so, it will turn any `-` into whitespace,
|
||||
// and remove any leading number
|
||||
const entry_title = entry.title
|
||||
.replaceAll("-", " ")
|
||||
.replaceAll(/\d+_/g, "");
|
||||
|
||||
const entry_title = entry.title.replaceAll("-", " ").replaceAll(/\d+_/g, "");
|
||||
---
|
||||
|
||||
{
|
||||
@ -34,9 +31,7 @@ const entry_title = entry.title
|
||||
|
||||
<ul class="my-1">
|
||||
{entry.children!.map((nextEntry) => (
|
||||
<Astro.self
|
||||
entry={nextEntry}
|
||||
/>
|
||||
<Astro.self entry={nextEntry} />
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
|
@ -20,8 +20,7 @@ function buildHierarchy(headings: any) {
|
||||
toc.push(heading);
|
||||
} else if (heading.depth === 1) {
|
||||
/** empty */
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
parentHeadings.get(heading.depth - 1).subheadings.push(heading);
|
||||
}
|
||||
});
|
||||
|
@ -9,7 +9,10 @@ const monoClass = parentMono? " font-mono" : "";
|
||||
---
|
||||
|
||||
<li>
|
||||
<a class={"inline-block py-1 hover:underline" + monoClass} href={"#" + heading.slug}>
|
||||
<a
|
||||
class={"inline-block py-1 hover:underline" + monoClass}
|
||||
href={"#" + heading.slug}
|
||||
>
|
||||
{heading.text}
|
||||
</a>
|
||||
{
|
||||
|
@ -6,4 +6,8 @@ const { thpcode, href } = Astro.props;
|
||||
const [native_html] = await native_highlighter(thpcode);
|
||||
---
|
||||
|
||||
<a href={href} class="inline-block w-full py-2 font-mono whitespace-pre" set:html={native_html} />
|
||||
<a
|
||||
href={href}
|
||||
class="inline-block w-full py-2 font-mono whitespace-pre"
|
||||
set:html={native_html}
|
||||
/>
|
||||
|
@ -1,4 +1,6 @@
|
||||
<div class="my-6 px-4 py-2 rounded bg-[#f8e287] text-[#221b00] dark:bg-[#dbc66e] dark:text-[#3a3000]">
|
||||
<div
|
||||
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>
|
||||
<slot />
|
||||
</div>
|
||||
|
@ -1,111 +1,82 @@
|
||||
import { expect, test } from 'vitest'
|
||||
import { leftTrimDedent } from "./utils"
|
||||
import { expect, test } from "vitest";
|
||||
import { leftTrimDedent } from "./utils";
|
||||
|
||||
test("should trim empty string", () => {
|
||||
const input = ``;
|
||||
|
||||
expect(leftTrimDedent(input)).toEqual([""]);
|
||||
})
|
||||
});
|
||||
|
||||
test("should work on a single line", () => {
|
||||
const input = `hello`
|
||||
const input = `hello`;
|
||||
|
||||
expect(leftTrimDedent(input)).toEqual([
|
||||
"hello"
|
||||
]);
|
||||
})
|
||||
expect(leftTrimDedent(input)).toEqual(["hello"]);
|
||||
});
|
||||
|
||||
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", () => {
|
||||
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", () => {
|
||||
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", () => {
|
||||
const input = ` hello\nworld`;
|
||||
|
||||
try {
|
||||
const res = leftTrimDedent(input);
|
||||
expect(res).not.toEqual([
|
||||
"hello",
|
||||
"rld",
|
||||
]);
|
||||
expect(res).not.toEqual(["hello", "rld"]);
|
||||
} catch (e) {
|
||||
expect(e).toBeInstanceOf(Error);
|
||||
expect(e).toHaveProperty("message", "Invalid indentation while trimming: Expected 2 spaces, got 0");
|
||||
expect(e).toHaveProperty(
|
||||
"message",
|
||||
"Invalid indentation while trimming: Expected 2 spaces, got 0",
|
||||
);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
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", () => {
|
||||
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", () => {
|
||||
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", () => {
|
||||
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", () => {
|
||||
const input = `
|
||||
hello
|
||||
world`;
|
||||
|
||||
expect(leftTrimDedent(input)).toEqual([
|
||||
"hello",
|
||||
"world",
|
||||
]);
|
||||
})
|
||||
expect(leftTrimDedent(input)).toEqual(["hello", "world"]);
|
||||
});
|
||||
|
||||
test("should ignore empty first line 2", () => {
|
||||
const input = `
|
||||
@ -113,12 +84,8 @@ test("should ignore empty first line 2", () => {
|
||||
|
||||
world`;
|
||||
|
||||
expect(leftTrimDedent(input)).toEqual([
|
||||
"hello",
|
||||
"",
|
||||
"world",
|
||||
]);
|
||||
})
|
||||
expect(leftTrimDedent(input)).toEqual(["hello", "", "world"]);
|
||||
});
|
||||
|
||||
test("should ignore empty last line", () => {
|
||||
const input = `
|
||||
@ -126,8 +93,5 @@ test("should ignore empty last line", () => {
|
||||
world
|
||||
`;
|
||||
|
||||
expect(leftTrimDedent(input)).toEqual([
|
||||
"hello",
|
||||
"world",
|
||||
]);
|
||||
expect(leftTrimDedent(input)).toEqual(["hello", "world"]);
|
||||
});
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
/**
|
||||
* Performs the following:
|
||||
* - Removes the first & last line, if they are empty
|
||||
@ -54,7 +53,9 @@ function trimWhitespace(input: string, count: number): string {
|
||||
if (indentCount >= count || indentCount == input.length) {
|
||||
return input.slice(count);
|
||||
} else {
|
||||
throw new Error(`Invalid indentation while trimming: Expected ${count} spaces, got ${indentCount}`);
|
||||
throw new Error(
|
||||
`Invalid indentation while trimming: Expected ${count} spaces, got ${indentCount}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,8 @@ 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"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<script src="//unpkg.com/alpinejs" defer></script>
|
||||
</head>
|
||||
|
||||
<body class="bg-c-bg text-c-text">
|
||||
|
@ -11,38 +11,40 @@ export type PageEntry = {
|
||||
};
|
||||
|
||||
export interface AstroFile {
|
||||
frontmatter: Frontmatter
|
||||
__usesAstroImage: boolean
|
||||
url: string
|
||||
file: string
|
||||
relative_file: string
|
||||
frontmatter: Frontmatter;
|
||||
__usesAstroImage: boolean;
|
||||
url: string;
|
||||
file: string;
|
||||
relative_file: string;
|
||||
}
|
||||
|
||||
export interface Frontmatter {
|
||||
layout: string
|
||||
title: string
|
||||
order: number
|
||||
layout: string;
|
||||
title: string;
|
||||
order: number;
|
||||
}
|
||||
|
||||
type Props = {
|
||||
/** Base url. It is used to later build a tree file system */
|
||||
base_url: string,
|
||||
base_url: string;
|
||||
frontmatter: Frontmatter;
|
||||
headings: any;
|
||||
posts: Array<AstroFile>;
|
||||
version: string;
|
||||
};
|
||||
|
||||
const {
|
||||
base_url,
|
||||
frontmatter,
|
||||
headings,
|
||||
posts
|
||||
posts,
|
||||
version = "latest",
|
||||
}: Props = Astro.props;
|
||||
|
||||
const base_len = base_url.length;
|
||||
|
||||
const posts_2 = posts
|
||||
.map(post => ({
|
||||
.map((post) => ({
|
||||
...post,
|
||||
title: post.frontmatter.title,
|
||||
// this should be a path relative to the base url.
|
||||
@ -50,11 +52,11 @@ const posts_2 = posts
|
||||
// being `/spec/ast/tokens` would be `/ast/tokens`
|
||||
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
|
||||
const second_level: Record<string, Array<AstroFile>> = {
|
||||
"_": [],
|
||||
_: [],
|
||||
};
|
||||
for (const post of posts_2) {
|
||||
const fragments = post.path.split("/");
|
||||
@ -66,8 +68,7 @@ for (const post of posts_2) {
|
||||
second_level[folder_name] = [];
|
||||
}
|
||||
second_level[folder_name].push(post);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// add to root folder
|
||||
second_level["_"]!.push(post);
|
||||
}
|
||||
@ -88,8 +89,7 @@ for (const levels_key of levels_keys) {
|
||||
if (levels_key === "_") {
|
||||
// top level, already inserted
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
const posts = second_level[levels_key]!;
|
||||
const sorted_posts = posts.toSorted(sort_posts);
|
||||
const parentEntry = {
|
||||
@ -109,6 +109,8 @@ function sort_posts(p1, p2) {
|
||||
return p1.title > p2.title ? 1 : -1;
|
||||
}
|
||||
}
|
||||
|
||||
const versions = ["latest", "v0.0.1"];
|
||||
---
|
||||
|
||||
<BaseLayout title={frontmatter.title}>
|
||||
@ -123,23 +125,24 @@ function sort_posts(p1, p2) {
|
||||
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)]">
|
||||
|
||||
<form class="pb-8 px-1">
|
||||
<label for="version-select">THP version:</label>
|
||||
<select
|
||||
id="version-select"
|
||||
class="bg-c-bg text-c-on-bg border border-pink-700 rounded px-3 p-1 w-full font-mono"
|
||||
x-on:change="console.log(':D')"
|
||||
>
|
||||
<option selected>latest</option>
|
||||
<option>v0.0.1</option>
|
||||
{
|
||||
versions.map((x) => (
|
||||
<option selected={x === version} value={x}>
|
||||
{x}
|
||||
</option>
|
||||
))
|
||||
}
|
||||
</select>
|
||||
</form>
|
||||
|
||||
{
|
||||
entries.map((entry) => (
|
||||
<Sidebar entry={entry} />
|
||||
))
|
||||
}
|
||||
{entries.map((entry) => <Sidebar entry={entry} />)}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
|
@ -1,16 +1,20 @@
|
||||
|
||||
export function highlightOnDom() {
|
||||
const pre_elements = document.querySelectorAll("pre");
|
||||
for (const pre_el of pre_elements) {
|
||||
const language = pre_el.getAttribute("data-language");
|
||||
if (language === null) { continue; }
|
||||
if (language === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create a visual indicador
|
||||
const indicator = document.createElement("span");
|
||||
|
||||
let indicator_bg_class = "";
|
||||
if (language === "php") { indicator_bg_class = "bg-[#4f5b93]"; }
|
||||
else if (language === "html") { indicator_bg_class = "bg-[#dc4a20]"; }
|
||||
if (language === "php") {
|
||||
indicator_bg_class = "bg-[#4f5b93]";
|
||||
} 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.innerText = language;
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
export function sidebarHighlight() {
|
||||
let current_uri = window.location.pathname;
|
||||
|
||||
@ -7,8 +6,7 @@ export function sidebarHighlight() {
|
||||
const links = sidebar.querySelectorAll("a");
|
||||
for (const link of [...links]) {
|
||||
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");
|
||||
break;
|
||||
|
@ -1,9 +1,16 @@
|
||||
import { spawn } from "node:child_process";
|
||||
import { leftTrimDedent } from "../components/utils";
|
||||
import { HighlightLevel } from "./types";
|
||||
import type { ErrorLabel, MistiErr, Token, TokenizeResult, TokenType } from "./types";
|
||||
import type {
|
||||
ErrorLabel,
|
||||
MistiErr,
|
||||
Token,
|
||||
TokenizeResult,
|
||||
TokenType,
|
||||
} from "./types";
|
||||
|
||||
const error_classes = "underline underline-offset-4 decoration-wavy decoration-red-500";
|
||||
const error_classes =
|
||||
"underline underline-offset-4 decoration-wavy decoration-red-500";
|
||||
|
||||
/**
|
||||
* Highlights code using the compiler
|
||||
@ -12,7 +19,10 @@ const error_classes = "underline underline-offset-4 decoration-wavy decoration-r
|
||||
* - The tokens as a list of <span /> elements
|
||||
* - An error message, if any
|
||||
*/
|
||||
export async function native_highlighter(code: string, level = HighlightLevel.Lexic): Promise<[string, string | null]> {
|
||||
export async function native_highlighter(
|
||||
code: string,
|
||||
level = HighlightLevel.Lexic,
|
||||
): Promise<[string, string | null]> {
|
||||
let formatted_code = leftTrimDedent(code).join("\n");
|
||||
|
||||
try {
|
||||
@ -30,7 +40,10 @@ export async function native_highlighter(code: string, level = HighlightLevel.Le
|
||||
* - The tokens as a list of <span /> elements
|
||||
* - An error message, if any
|
||||
*/
|
||||
function highlight_syntax(code: string, result: TokenizeResult): [string, string | null] {
|
||||
function highlight_syntax(
|
||||
code: string,
|
||||
result: TokenizeResult,
|
||||
): [string, string | null] {
|
||||
if (result.Ok) {
|
||||
const tokens_html = render_tokens(code, result.Ok);
|
||||
|
||||
@ -44,21 +57,21 @@ function highlight_syntax(code: string, result: TokenizeResult): [string, string
|
||||
} else if (result.Err) {
|
||||
// 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 {
|
||||
console.error(result);
|
||||
throw new Error("Web page error: The compiler returned a case that wasn't handled.");
|
||||
throw new Error(
|
||||
"Web page error: The compiler returned a case that wasn't handled.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** A fatal error with the THP compiler */
|
||||
function compiler_error(code: string, error: MistiErr): [string, string] {
|
||||
console.log(error);
|
||||
return [code, "Fatal compiler error"];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Transforms a list of tokens into colored HTML, and underlines present errors
|
||||
*
|
||||
@ -68,12 +81,19 @@ function compiler_error(code: string, error: MistiErr): [string, string] {
|
||||
* @param error_end Absolute position to where the error ends.
|
||||
* @returns
|
||||
*/
|
||||
function render_tokens(input: string, tokens: Array<Token>, error_labels: Array<ErrorLabel> = []): string {
|
||||
function render_tokens(
|
||||
input: string,
|
||||
tokens: Array<Token>,
|
||||
error_labels: Array<ErrorLabel> = [],
|
||||
): string {
|
||||
const input_chars = input.split("");
|
||||
let output = "";
|
||||
|
||||
// Collects all the token ranges in all error labels
|
||||
const error_ranges: Array<[number, number]> = error_labels.map(l => [l.start, l.end]);
|
||||
const error_ranges: Array<[number, number]> = error_labels.map((l) => [
|
||||
l.start,
|
||||
l.end,
|
||||
]);
|
||||
|
||||
let current_pos = 0;
|
||||
for (let i = 0; i < tokens.length; i += 1) {
|
||||
@ -103,7 +123,11 @@ function render_tokens(input: string, tokens: Array<Token>, error_labels: Array<
|
||||
output += input_chars.slice(current_pos, token_start).join("");
|
||||
|
||||
// Append the token
|
||||
const [token_value, new_token_end] = process_token_value_and_end(t.value, t.token_type, token_end);
|
||||
const [token_value, new_token_end] = process_token_value_and_end(
|
||||
t.value,
|
||||
t.token_type,
|
||||
token_end,
|
||||
);
|
||||
const token_type = translate_token_type(t.token_type, token_value);
|
||||
output += `<span class="token ${token_type} ${is_errored ? error_classes : ""}">${token_value}</span>`;
|
||||
|
||||
@ -119,12 +143,21 @@ function render_tokens(input: string, tokens: Array<Token>, error_labels: Array<
|
||||
let offset = 0;
|
||||
for (const label of error_labels) {
|
||||
// get the line number of the label
|
||||
const [line_number, col_number] = absolute_to_line_column(input, label.start);
|
||||
const [line_number, col_number] = absolute_to_line_column(
|
||||
input,
|
||||
label.start,
|
||||
);
|
||||
let spaces_len = col_number - 1;
|
||||
if (spaces_len < 0) { spaces_len = 0 }
|
||||
if (spaces_len < 0) {
|
||||
spaces_len = 0;
|
||||
}
|
||||
|
||||
const spaces = new Array(spaces_len).fill(" ").join("");
|
||||
lines.splice(line_number + offset, 0, create_inline_error_message(spaces, label.message));
|
||||
lines.splice(
|
||||
line_number + offset,
|
||||
0,
|
||||
create_inline_error_message(spaces, label.message),
|
||||
);
|
||||
offset += 1;
|
||||
}
|
||||
|
||||
@ -143,7 +176,10 @@ function create_inline_error_message(spaces: string, message: string): string {
|
||||
* @param input the source code
|
||||
* @param absolute the absolute position
|
||||
*/
|
||||
function absolute_to_line_column(input: string, absolute: number): [number, number] {
|
||||
function absolute_to_line_column(
|
||||
input: string,
|
||||
absolute: number,
|
||||
): [number, number] {
|
||||
let line_count = 1;
|
||||
let last_newline_pos = 0;
|
||||
|
||||
@ -172,7 +208,11 @@ function absolute_to_line_column(input: string, absolute: number): [number, numb
|
||||
* @param first_end The position where the token ends according to the token value
|
||||
* @returns
|
||||
*/
|
||||
function process_token_value_and_end(value: string, token_type: TokenType, first_end: number): [string, number] {
|
||||
function process_token_value_and_end(
|
||||
value: string,
|
||||
token_type: TokenType,
|
||||
first_end: number,
|
||||
): [string, number] {
|
||||
let token_value = value;
|
||||
let new_end = first_end;
|
||||
if (token_type === "MultilineComment") {
|
||||
@ -186,16 +226,49 @@ function process_token_value_and_end(value: string, token_type: TokenType, first
|
||||
// Escape html and return
|
||||
return [
|
||||
token_value.replaceAll(/</g, "<").replaceAll(/>/g, ">"),
|
||||
new_end
|
||||
new_end,
|
||||
];
|
||||
}
|
||||
|
||||
function translate_token_type(tt: TokenType, value: string): string {
|
||||
const keywords = ["throws", "extends", "constructor", "static", "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"];
|
||||
const keywords = [
|
||||
"throws",
|
||||
"extends",
|
||||
"constructor",
|
||||
"static",
|
||||
"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) {
|
||||
case "Datatype":
|
||||
@ -233,7 +306,8 @@ function translate_token_type(tt: TokenType, value: string): string {
|
||||
}
|
||||
}
|
||||
|
||||
const native_lex = (code: string, level: HighlightLevel) => new Promise<TokenizeResult>((resolve, reject) => {
|
||||
const native_lex = (code: string, level: HighlightLevel) =>
|
||||
new Promise<TokenizeResult>((resolve, reject) => {
|
||||
// Get binary path from .env
|
||||
const binary = import.meta.env.THP_BINARY;
|
||||
if (!binary) {
|
||||
@ -262,4 +336,4 @@ const native_lex = (code: string, level: HighlightLevel) => new Promise<Tokenize
|
||||
reject(new Error(error));
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
|
@ -1,13 +1,13 @@
|
||||
export type ReferenceItem = {
|
||||
symbol_start: number
|
||||
symbol_end: number
|
||||
reference: string
|
||||
}
|
||||
symbol_start: number;
|
||||
symbol_end: number;
|
||||
reference: string;
|
||||
};
|
||||
|
||||
export interface Token {
|
||||
token_type: TokenType
|
||||
value: string
|
||||
position: number
|
||||
token_type: TokenType;
|
||||
value: string;
|
||||
position: number;
|
||||
}
|
||||
|
||||
export type TokenType =
|
||||
@ -40,30 +40,29 @@ export type TokenType =
|
||||
| "IN"
|
||||
| "WHILE"
|
||||
| "MATCH"
|
||||
| "CASE"
|
||||
;
|
||||
| "CASE";
|
||||
|
||||
export interface MistiErr {
|
||||
error_code: number
|
||||
error_offset: number
|
||||
labels: Array<ErrorLabel>
|
||||
note: string | null,
|
||||
help: string | null,
|
||||
error_code: number;
|
||||
error_offset: number;
|
||||
labels: Array<ErrorLabel>;
|
||||
note: string | null;
|
||||
help: string | null;
|
||||
}
|
||||
|
||||
export interface ErrorLabel {
|
||||
message: string
|
||||
start: number
|
||||
end: number
|
||||
message: string;
|
||||
start: number;
|
||||
end: number;
|
||||
}
|
||||
|
||||
export interface TokenizeResult {
|
||||
/** All checks passed */
|
||||
Ok?: Array<Token>,
|
||||
Ok?: Array<Token>;
|
||||
/** A non lexic error was found */
|
||||
MixedErr?: [Array<Token>, MistiErr],
|
||||
MixedErr?: [Array<Token>, MistiErr];
|
||||
/** A lexic error was found */
|
||||
Err?: MistiErr,
|
||||
Err?: MistiErr;
|
||||
}
|
||||
|
||||
export enum HighlightLevel {
|
||||
|
@ -1,15 +1,15 @@
|
||||
export function is_digit(c: string): boolean {
|
||||
return c >= '0' && c <= '9';
|
||||
return c >= "0" && c <= "9";
|
||||
}
|
||||
|
||||
export function is_lowercase(c: string): boolean {
|
||||
return c >= 'a' && c <= 'z';
|
||||
return c >= "a" && c <= "z";
|
||||
}
|
||||
|
||||
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 {
|
||||
return is_lowercase(c) || is_uppercase(c) || is_digit(c) || c === '_';
|
||||
return is_lowercase(c) || is_uppercase(c) || is_digit(c) || c === "_";
|
||||
}
|
||||
|
@ -1,10 +1,11 @@
|
||||
---
|
||||
layout: ../../../layouts/ApiLayout.astro
|
||||
---
|
||||
import TwoColumn from "../../../components/TwoColumn.astro"
|
||||
import Code from "../../../components/Code.astro"
|
||||
import CodeMin from "../../../components/docs/CodeMin.astro"
|
||||
import Warning from "../../../components/docs/Warning.astro"
|
||||
|
||||
import TwoColumn from "../../../components/TwoColumn.astro";
|
||||
import Code from "../../../components/Code.astro";
|
||||
import CodeMin from "../../../components/docs/CodeMin.astro";
|
||||
import Warning from "../../../components/docs/Warning.astro";
|
||||
|
||||
# Array
|
||||
|
||||
@ -19,28 +20,32 @@ THP arrays are 0-indexed.
|
||||
|
||||
## Signature
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
type Array[T] = Map[Int, T]
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
Where `T` is the datatype that the Array stores. For example:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
Array[Int] // An array of integers
|
||||
Array[Float] // An array of floats
|
||||
Array[Array[String]] // A 2-dimensional array of strings
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
## PHP array internals
|
||||
|
||||
<Warning>
|
||||
TL;DR: **Never** assign to an array using an invalid index. If you do
|
||||
the program will not crash, instead it will not behaved as expected
|
||||
[(this is a common problem in PHP)](https://eev.ee/blog/2012/04/09/php-a-fractal-of-bad-design/).
|
||||
THP tries its best to solve such behavior.
|
||||
TL;DR: **Never** assign to an array using an invalid index. If you do the
|
||||
program will not crash, instead it will not behaved as expected [(this is a
|
||||
common problem in
|
||||
PHP)](https://eev.ee/blog/2012/04/09/php-a-fractal-of-bad-design/). THP tries
|
||||
its best to solve such behavior.
|
||||
</Warning>
|
||||
|
||||
|
||||
Since THP compiles down to PHP, it's important to understand how PHP
|
||||
represents arrays internally.
|
||||
PHP doesn't have arrays. Instead, PHP has ordered maps and syntax sugar
|
||||
@ -48,9 +53,11 @@ to make them look like arrays.
|
||||
|
||||
When declaring an array like:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
var arr = ["a", "b", "c"]
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
in reality what goes into memory is a map with numbers as keys:
|
||||
|
||||
@ -102,7 +109,7 @@ numbers[7] = "!" // Out of bounds assignment
|
||||
|
||||
// In this loop, values will be printed following when
|
||||
// they were inserted, not by order of the index.
|
||||
for #(_, value) in numbers
|
||||
for #(\_, value) in numbers
|
||||
{
|
||||
print("{value} ") // Will print: a b c ? !
|
||||
}
|
||||
@ -135,12 +142,9 @@ 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
|
||||
performance penalty.
|
||||
|
||||
|
||||
From now on, the documentation will continue to work with the Array
|
||||
abstraction, as if all indexes were valid.
|
||||
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
### Empty array
|
||||
@ -148,31 +152,36 @@ abstraction, as if all indexes were valid.
|
||||
To create an empty array use square brackets.
|
||||
If you create an empty array, you need to specify the datatype.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
Array[Int] empty = []
|
||||
`} />
|
||||
|
||||
`}
|
||||
/>
|
||||
|
||||
### Creation
|
||||
|
||||
To create an array use square brackets notation:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
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 declared over many lines, the last
|
||||
item should have a trailing comma:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
val colors = [
|
||||
"red",
|
||||
"blue",
|
||||
"green", // trailing comma
|
||||
]
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
If it doesn't, the code formatter will automatically
|
||||
insert one for you.
|
||||
@ -198,13 +207,14 @@ mutable[0] = 322
|
||||
|
||||
To append an element to an array, use the method `push()`:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
mutable.push(4)
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
Do not insert into an invalid index. See [PHP array internals](#php-array-internals) to learn why.
|
||||
|
||||
|
||||
### Iteration
|
||||
|
||||
Use a `for` loop to iterate over the elements of an array:
|
||||
@ -235,7 +245,6 @@ for c in colors
|
||||
}
|
||||
`} />
|
||||
|
||||
|
||||
You can also declare an index along with the value:
|
||||
|
||||
<Code thpcode={`
|
||||
@ -253,41 +262,37 @@ item 1: green
|
||||
item 2: blue
|
||||
```
|
||||
|
||||
|
||||
### Access
|
||||
|
||||
To access a value of the array use square brackets notation:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
print(colors[0])
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
Since the index might not exist, this will return a
|
||||
[nullable type](/learn/error-handling/null/) that you have to handle.
|
||||
|
||||
|
||||
### Destructuring
|
||||
|
||||
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.
|
||||
|
||||
|
||||
### Operators
|
||||
|
||||
While PHP allows using certain operators with arrays, THP disallows that.
|
||||
Methods that perform comparisons should be used instead.
|
||||
|
||||
|
||||
### Assignment
|
||||
|
||||
// TODO: Detail that assignment of arrays is copy on write
|
||||
|
||||
|
||||
## Methods
|
||||
|
||||
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">
|
||||
<thead>
|
||||
<td class="p-2">Method</td>
|
||||
@ -296,30 +301,34 @@ In the parameters, <code class="token keyword">self</code> is the array to opera
|
||||
<tbody>
|
||||
<tr class="dark:odd:bg-zinc-900 odd:bg-stone-200">
|
||||
<td class="px-2">
|
||||
<CodeMin href="./concat" thpcode={`
|
||||
<CodeMin
|
||||
href="./concat"
|
||||
thpcode={`
|
||||
fun concat[T](
|
||||
self,
|
||||
Array[T]... arrays,
|
||||
) -> Array[T]
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
</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>
|
||||
</tr>
|
||||
<tr class="dark:odd:bg-zinc-900 odd:bg-stone-200">
|
||||
<td class="px-2">
|
||||
<CodeMin thpcode={`
|
||||
<CodeMin
|
||||
thpcode={`
|
||||
fun filter[T](
|
||||
self,
|
||||
(T) -> (Bool) callback,
|
||||
) -> Array[T]
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
Filters elements using a callback function, and returns
|
||||
them in a new array.
|
||||
Filters elements using a callback function, and returns them in a new
|
||||
array.
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="dark:odd:bg-zinc-900 odd:bg-stone-200">
|
||||
@ -327,19 +336,15 @@ In the parameters, <code class="token keyword">self</code> is the array to opera
|
||||
<CodeMin thpcode="fun push[T](self, T... elements) -> Int" />
|
||||
</td>
|
||||
<td>
|
||||
Appends one or more elements to the end of the array.
|
||||
Returns the new length of the array.
|
||||
Appends one or more elements to the end of the array. Returns the new
|
||||
length of the array.
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="dark:odd:bg-zinc-900 odd:bg-stone-200">
|
||||
<td class="px-2">
|
||||
<CodeMin thpcode="fun pop[T](self) -> T" />
|
||||
</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>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
---
|
||||
layout: ../../../../layouts/ApiLayout.astro
|
||||
---
|
||||
import Code from "../../../../components/Code.astro"
|
||||
|
||||
import Code from "../../../../components/Code.astro";
|
||||
|
||||
# `Array.concat`
|
||||
|
||||
@ -9,12 +10,14 @@ Concatenate with other arrays, and return the result as a new array.
|
||||
|
||||
## Signature
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fun concat[T](
|
||||
self,
|
||||
Array[T]... arrays,
|
||||
) -> Array[T]
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
## Parameters
|
||||
|
||||
@ -25,7 +28,6 @@ Concatenate with other arrays, and return the result as a new array.
|
||||
|
||||
`Array[T]`: A new array that contains the elements from all arrays.
|
||||
|
||||
|
||||
## Description
|
||||
|
||||
Concatenates the elements of the callee and the elements of each array in the
|
||||
@ -49,6 +51,7 @@ Example concatenating 2 arrays:
|
||||
|
||||
val result = first.concat(second)
|
||||
assert_eq(result, [1, 2, 3, 4, 5, 6])
|
||||
|
||||
`} />
|
||||
|
||||
Example concatenating 3 arrays:
|
||||
@ -60,6 +63,7 @@ Example concatenating 3 arrays:
|
||||
|
||||
val result = first.concat(second, third)
|
||||
assert_eq(result, ["a", "b", "c", "d", "e", "f"])
|
||||
|
||||
`} />
|
||||
|
||||
Example concatenating without any other array. In this case
|
||||
@ -70,6 +74,7 @@ Example concatenating without any other array. In this case
|
||||
|
||||
val result = first.concat()
|
||||
assert_eq(result, [1, 2, 3])
|
||||
|
||||
`} />
|
||||
|
||||
Example concatenating an empty array with a filled array:
|
||||
@ -80,8 +85,8 @@ Example concatenating an empty array with a filled array:
|
||||
|
||||
val result = first.concat(second)
|
||||
assert_eq(result, [10, 20, 30])
|
||||
`} />
|
||||
|
||||
`} />
|
||||
|
||||
## PHP interop
|
||||
|
||||
@ -107,6 +112,3 @@ array(3) {
|
||||
[2]=> string(1) "c"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
---
|
||||
layout: ../../../../layouts/ApiLayout.astro
|
||||
---
|
||||
import Code from "../../../../components/Code.astro"
|
||||
|
||||
import Code from "../../../../components/Code.astro";
|
||||
|
||||
# `Array.fold`
|
||||
|
||||
@ -11,13 +12,15 @@ the left.
|
||||
|
||||
## Signature
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fun fold[A, B](
|
||||
self[A],
|
||||
B init,
|
||||
(B acc, A next) -> (B) transform_function,
|
||||
) -> B
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
## Parameters
|
||||
|
||||
@ -31,7 +34,6 @@ the left.
|
||||
- Otherwise, returns the result of applying `transform_function` to the accumulator `acc`,
|
||||
and the next element `next`.
|
||||
|
||||
|
||||
## Description
|
||||
|
||||
Fold allows you to transform an array of `A` into a single `B`, following
|
||||
@ -39,14 +41,15 @@ an arbitraty function.
|
||||
|
||||
For example, let's say that you have an array of numbers:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
val digits = [1, 9, 9, 5]
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
And you want to join all digits into a String like `"1985"`. You can
|
||||
achieve this with a fold.
|
||||
|
||||
|
||||
<Code thpcode={`
|
||||
val digits = [1, 9, 8, 5]
|
||||
val init = ""
|
||||
@ -93,10 +96,12 @@ The `transform_function` can be any function, and can operate over any type.
|
||||
For example, you could sum all numbers instead of concatenating
|
||||
like this:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
val digits = [1, 9, 8, 5]
|
||||
digits.fold(0, fun(acc, next) = acc + next) // 23
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
```php
|
||||
f( f( f( f(0, 1), 9), 8), 5)
|
||||
@ -118,8 +123,3 @@ val result = builder.build()
|
||||
`} />
|
||||
|
||||
Or anything.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
---
|
||||
layout: ../../../../layouts/ApiLayout.astro
|
||||
---
|
||||
import Code from "../../../../components/Code.astro"
|
||||
|
||||
import Code from "../../../../components/Code.astro";
|
||||
|
||||
# `Array.map`
|
||||
|
||||
@ -19,10 +20,9 @@ and returns their result in a new array.
|
||||
self[],
|
||||
(A) -> (B) map_function
|
||||
) -> Array[B]
|
||||
|
||||
`} />
|
||||
|
||||
|
||||
|
||||
<Code thpcode={`
|
||||
|
||||
fun multi_def
|
||||
@ -40,4 +40,3 @@ fun multi_def
|
||||
}
|
||||
|
||||
`} />
|
||||
|
||||
|
@ -1,8 +1,9 @@
|
||||
---
|
||||
layout: ../../../layouts/ApiLayout.astro
|
||||
---
|
||||
import TwoColumn from "../../../components/TwoColumn.astro"
|
||||
import Code from "../../../components/Code.astro"
|
||||
|
||||
import TwoColumn from "../../../components/TwoColumn.astro";
|
||||
import Code from "../../../components/Code.astro";
|
||||
|
||||
# module `std`
|
||||
|
||||
@ -28,12 +29,14 @@ if (str_contains("abc", "a")) {
|
||||
In THP there is no `str_contains` function. Instead, you'd call the
|
||||
`contains` method on the string:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
if "abc".contains("a")
|
||||
{
|
||||
// ...
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
## On naming
|
||||
|
||||
@ -56,10 +59,11 @@ If you need to use a PHP class with a lowercase name you can use the following s
|
||||
class animal {}
|
||||
```
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
val my_animal = 'animal()
|
||||
`} />
|
||||
|
||||
`}
|
||||
/>
|
||||
|
||||
## API: Datatypes
|
||||
|
||||
@ -96,5 +100,3 @@ A [IEEE 754](https://en.wikipedia.org/wiki/IEEE_754) double precision floating p
|
||||
Prints text into stdout.
|
||||
|
||||
</TwoColumn>
|
||||
|
||||
|
||||
|
@ -1,8 +1,9 @@
|
||||
---
|
||||
layout: ../../../layouts/ApiLayout.astro
|
||||
---
|
||||
import TwoColumn from "../../../components/TwoColumn.astro"
|
||||
import Code from "../../../components/Code.astro"
|
||||
|
||||
import TwoColumn from "../../../components/TwoColumn.astro";
|
||||
import Code from "../../../components/Code.astro";
|
||||
|
||||
# `print`
|
||||
|
||||
@ -10,9 +11,11 @@ Prints to stdout.
|
||||
|
||||
## Signature
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fun print(String value) {}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
## Description
|
||||
|
||||
@ -20,10 +23,8 @@ Prints a single `String` into stdout. Doesn't return anything.
|
||||
|
||||
## Examples
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
print("Hello world!")
|
||||
`} />
|
||||
|
||||
|
||||
|
||||
|
||||
`}
|
||||
/>
|
||||
|
@ -3,13 +3,13 @@ layout: "../_wrapper.astro"
|
||||
title: Comments
|
||||
order: 2
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Comments
|
||||
|
||||
THP supports single and multi line comments:
|
||||
|
||||
|
||||
## Single line
|
||||
|
||||
Begin with double slash `//` and continue until the end of the line.
|
||||
@ -21,33 +21,37 @@ print("hello!")
|
||||
print("the result is {5 + 5}") // This will print 10
|
||||
`} />
|
||||
|
||||
|
||||
## Multi line
|
||||
|
||||
These begin with `/*` and end with `*/`. Everything in between is ignored.
|
||||
|
||||
Multi line comments can be nested in THP.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
/*
|
||||
This is a
|
||||
multiline comment
|
||||
*/
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
/*
|
||||
Multiline comments
|
||||
can be /* nested */
|
||||
*/
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
## Documentation comments
|
||||
|
||||
Documentation comments use triple slashes `///`.
|
||||
These use [CommonMark Markdown](https://commonmark.org/) syntax.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
/// Transforms the format from A to B...
|
||||
///
|
||||
/// ## Errors
|
||||
@ -57,6 +61,5 @@ These use [CommonMark Markdown](https://commonmark.org/) syntax.
|
||||
fun transform() {
|
||||
// ...
|
||||
}
|
||||
`} />
|
||||
|
||||
|
||||
`}
|
||||
/>
|
||||
|
@ -3,7 +3,8 @@ layout: "../_wrapper.astro"
|
||||
title: Datatypes
|
||||
order: 4
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Datatypes
|
||||
|
||||
@ -34,44 +35,48 @@ Int not_octal = 032 // This is 32, not 26
|
||||
// TODO: Make it a compile error to have leading zeroes,
|
||||
and force users to use `0o` for octal
|
||||
|
||||
|
||||
## Float
|
||||
|
||||
Same as php float
|
||||
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
Float pi = 3.141592
|
||||
Float light = 2.99e+8
|
||||
`} />
|
||||
|
||||
`}
|
||||
/>
|
||||
|
||||
## String
|
||||
|
||||
THP strings use **only** double quotes. Single quotes are
|
||||
used elsewhere.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
String name = "Rose"
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
Strings have interpolation with `{}`.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
print("Hello, {name}") // Hello, Rose
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
Unlike PHP, THP strings are concatenated with `++`, not with `.`.
|
||||
This new operator implicitly converts any operator into a string.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
val name = "John" ++ " " ++ "Doe"
|
||||
val greeting = "My name is " ++ name ++ " and I'm " ++ 32 ++ " years old"
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
The plus operator `+` is reserved for numbers.
|
||||
|
||||
|
||||
## Bool
|
||||
|
||||
THP booleans are `true` and `false`. They are case sensitive,
|
||||
@ -84,4 +89,3 @@ Bool is_false = false
|
||||
// This is a compile error
|
||||
val invalid = TRUE
|
||||
`} />
|
||||
|
||||
|
@ -3,8 +3,9 @@ layout: "../_wrapper.astro"
|
||||
title: Hello world
|
||||
order: 1
|
||||
---
|
||||
|
||||
import InteractiveCode from "@/components/InteractiveCode.astro";
|
||||
import Code from "@/components/Code.astro"
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Hello, world!
|
||||
|
||||
@ -18,13 +19,14 @@ detailed later on.
|
||||
|
||||
To write a hello world program write the following code in a file:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
print("Hello, world!")
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
Then run `thp hello.thp` from your terminal.
|
||||
|
||||
|
||||
## Instruction separation
|
||||
|
||||
THP uses whitespace to determine when a statement is over. In short,
|
||||
@ -36,11 +38,12 @@ echo("B");
|
||||
echo("C");
|
||||
```
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
print("A")
|
||||
print("B")
|
||||
print("C")
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
As a consequence of this, there can only be 1 statement per line.
|
||||
|
||||
|
@ -3,11 +3,11 @@ layout: "../_wrapper.astro"
|
||||
title: Operators
|
||||
order: 5
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Operators
|
||||
|
||||
|
||||
Most of the PHP operators are present in THP.
|
||||
|
||||
## Numbers
|
||||
@ -17,13 +17,13 @@ var number = 322
|
||||
|
||||
number + 1
|
||||
number - 1
|
||||
number * 1
|
||||
number \* 1
|
||||
number / 1
|
||||
number % 2
|
||||
|
||||
number += 1
|
||||
number -= 1
|
||||
number *= 1
|
||||
number \*= 1
|
||||
number /= 1
|
||||
number %= 2
|
||||
`} />
|
||||
@ -44,62 +44,71 @@ number++ // This is a compile error
|
||||
These operators will not do implicit type conversion. They can
|
||||
only be used with same datatypes.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
v1 < v2
|
||||
v1 <= v2
|
||||
v1 > v2
|
||||
v1 >= v2
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
There is only `==` and `!=`. They are equivalent to `===` and `!==`.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
v1 == v2
|
||||
v1 != v2
|
||||
`} />
|
||||
|
||||
`}
|
||||
/>
|
||||
|
||||
### Bitwise
|
||||
|
||||
TBD
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
number and 1
|
||||
number or 2
|
||||
number xor 1
|
||||
number xand 1
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
## Strings
|
||||
|
||||
Strings **do not use `.`** for concatenation. They use `++`.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
"Hello " ++ "world."
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
This new operator **implicitly converts** types to string
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
"Hello " ++ 322 // 322 will be converted to "322"
|
||||
`} />
|
||||
|
||||
`}
|
||||
/>
|
||||
|
||||
## Boolean
|
||||
|
||||
These operators work **only with booleans**, they do not perform
|
||||
type coercion.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
c1 && c2
|
||||
c1 || c2
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
## Ternary
|
||||
|
||||
There is no ternary operator. See [Conditionals](/learn/flow-control/conditionals) for alternatives.
|
||||
|
||||
|
||||
## Null
|
||||
|
||||
These are detailed in their section: [Nullable types](/learn/error-handling/null)
|
||||
@ -115,6 +124,3 @@ if person? {
|
||||
person.name
|
||||
}
|
||||
`} />
|
||||
|
||||
|
||||
|
||||
|
@ -3,7 +3,8 @@ layout: "../_wrapper.astro"
|
||||
title: Variables
|
||||
order: 3
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Variables
|
||||
|
||||
@ -24,47 +25,59 @@ As a regex: `[a-z_][a-zA-Z0-9_]*`
|
||||
|
||||
Defined with `val`, followed by a variable name and a value.
|
||||
|
||||
<Code level={2} thpcode={`
|
||||
<Code
|
||||
level={2}
|
||||
thpcode={`
|
||||
val surname = "Doe"
|
||||
val year_of_birth = 1984
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
It's a compile error to attempt to modify it
|
||||
|
||||
<Code level={2} thpcode={`
|
||||
<Code
|
||||
level={2}
|
||||
thpcode={`
|
||||
val surname = "Doe"
|
||||
surname = "Dane" // Error
|
||||
`} />
|
||||
|
||||
|
||||
`}
|
||||
/>
|
||||
|
||||
### Datatype annotation
|
||||
|
||||
Written after the `val` keyword but before the variable name.
|
||||
|
||||
<Code level={2} thpcode={`
|
||||
<Code
|
||||
level={2}
|
||||
thpcode={`
|
||||
val String surname = "Doe"
|
||||
val Int year_of_birth = 1984
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
When annotating an immutable variable the `val` keyword is optional
|
||||
|
||||
<Code level={2} thpcode={`
|
||||
<Code
|
||||
level={2}
|
||||
thpcode={`
|
||||
// Equivalent to the previous code
|
||||
String surname = "Doe"
|
||||
Int year_of_birth = 1984
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
This means that if a variable only has a datatype, it is immutable.
|
||||
|
||||
It is a compile error to declare a variable of a datatype,
|
||||
but use another.
|
||||
|
||||
<Code level={2} thpcode={`
|
||||
<Code
|
||||
level={2}
|
||||
thpcode={`
|
||||
// Declare the variable as a String, but use a Float as its value
|
||||
String capital = 123.456
|
||||
`} />
|
||||
|
||||
`}
|
||||
/>
|
||||
|
||||
## Mutable variables
|
||||
|
||||
@ -81,18 +94,21 @@ age = 33
|
||||
|
||||
Written after the `var` keywords but before the variable name.
|
||||
|
||||
<Code level={2} thpcode={`
|
||||
<Code
|
||||
level={2}
|
||||
thpcode={`
|
||||
var String name = "John"
|
||||
var Int age = 32
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
When annotating a mutable variable the keyword `var` is still **required**.
|
||||
|
||||
<Code level={2} thpcode={`
|
||||
<Code
|
||||
level={2}
|
||||
thpcode={`
|
||||
// Equivalent to the previous code
|
||||
var String name = "John"
|
||||
var Int age = 32
|
||||
`} />
|
||||
|
||||
|
||||
|
||||
`}
|
||||
/>
|
||||
|
@ -2,7 +2,8 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Arrays
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Arrays
|
||||
|
||||
@ -27,19 +28,15 @@ numbers[3] = 5
|
||||
print(numbers[3]) // 5
|
||||
`} />
|
||||
|
||||
|
||||
## Type signature
|
||||
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
Array[String]
|
||||
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
|
||||
problems with the language's grammar.
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -2,7 +2,8 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Enums
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Enums
|
||||
|
||||
@ -33,7 +34,8 @@ val suit = Suit::Hearts
|
||||
Backed enums can have a scalar for each case. The scalar values can only be
|
||||
`String` or `Int`.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
enum Suit(String)
|
||||
{
|
||||
Hearts = "H",
|
||||
@ -41,8 +43,7 @@ enum Suit(String)
|
||||
Clubs = "C",
|
||||
Spades = "S",
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
All cases must explicitly define their value, there is no automatic generation.
|
||||
|
||||
|
||||
|
@ -2,7 +2,8 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Maps
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Maps
|
||||
|
||||
@ -41,41 +42,48 @@ var Person mary_jane = .{
|
||||
|
||||
To access the fields of a map we use square braces `[]`.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
mary_jane["age"] += 1
|
||||
print(mary_jane["name"]) // Mary
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
Or dot access `.` if the field's name is a valid identifier.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
mary_jane.age += 1
|
||||
print(mary_jane.name)
|
||||
`} />
|
||||
|
||||
`}
|
||||
/>
|
||||
|
||||
## Anonymous maps
|
||||
|
||||
An anonymous map allows us to store and retrieve any key of any datatype.
|
||||
They are declared as `Map`.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
val car = Map {
|
||||
brand: "Toyota",
|
||||
model: "Corolla",
|
||||
year: 2012,
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
Anonymous maps can also can have their type omitted.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
var car = .{
|
||||
brand: "Toyota",
|
||||
model: "Corolla",
|
||||
year: 2012,
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
If the compiler encounters a map without a type (that is, `.{}`)
|
||||
and doesn't expect a specific type, it will assume it is an
|
||||
@ -83,12 +91,14 @@ anonymous map.
|
||||
|
||||
We can freely assign fields to an anonymous map:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
// Modify an existing field
|
||||
car["year"] = 2015
|
||||
// Create a new field
|
||||
car["status"] = "used"
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
However, if we try to access a field of an anonymous map we'll get
|
||||
a nullable type, and we must annotate it.
|
||||
@ -104,9 +114,11 @@ var car_status = car["status"]
|
||||
Instead, we can use the `get` function of the map, which expects a
|
||||
datatype and returns that type as nullable
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
val car_status = car.get[String]("status")
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
Both ways to get a value will check that the key exists in the map,
|
||||
and that it has the correct datatype. If either the key doesn't exist
|
||||
|
@ -2,7 +2,8 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Tuples
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Tuples
|
||||
|
||||
@ -16,13 +17,10 @@ val person = #("John", "Doe", 32)
|
||||
val #(name, surname, age) = person
|
||||
`} />
|
||||
|
||||
|
||||
|
||||
## Signature
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
#(String, String, Int)
|
||||
`} />
|
||||
|
||||
|
||||
|
||||
`}
|
||||
/>
|
||||
|
@ -2,7 +2,8 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Tagged unions
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Tagged unions
|
||||
|
||||
@ -23,7 +24,8 @@ val rectangle1 = Shape::Rectangle(5, 15)
|
||||
|
||||
## Pattern matching
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
match shape_1
|
||||
case ::Square(side)
|
||||
{
|
||||
@ -33,7 +35,8 @@ case ::Rectangle(length, height)
|
||||
{
|
||||
print("Area of the rectangle: {length * height}")
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
## Internal representation
|
||||
|
||||
@ -42,7 +45,6 @@ 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
|
||||
are contained as part of an array.
|
||||
|
||||
|
||||
```php
|
||||
// The first snippet is compiled to:
|
||||
enum Shape
|
||||
@ -56,4 +58,3 @@ $dot = [Shape::Dot];
|
||||
$square1 = [Shape::Square, 10];
|
||||
$rectangle1 = [Shape::Rectangle, 5, 15]
|
||||
```
|
||||
|
||||
|
@ -2,7 +2,8 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Blocks
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Blocks
|
||||
|
||||
@ -17,8 +18,8 @@ val result = {
|
||||
val temp = 161
|
||||
|
||||
temp * 2 // This will be assigned to \`result\`
|
||||
|
||||
}
|
||||
|
||||
print(result) // 322
|
||||
`} />
|
||||
|
||||
|
@ -2,7 +2,8 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Conditionals
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Conditionals
|
||||
|
||||
@ -12,7 +13,6 @@ import Code from "@/components/Code.astro"
|
||||
- Paretheses for the condition are not required.
|
||||
- There's no ternary operator
|
||||
|
||||
|
||||
<Code thpcode={`
|
||||
if condition {
|
||||
// code
|
||||
@ -24,22 +24,20 @@ else {
|
||||
// even more code
|
||||
}
|
||||
|
||||
|
||||
val result = if condition { value1 } else { value2 }
|
||||
`} />
|
||||
|
||||
|
||||
|
||||
## Check for datatypes
|
||||
|
||||
TBD
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
if variable is Datatype {
|
||||
// code using variable
|
||||
}
|
||||
`} />
|
||||
|
||||
`}
|
||||
/>
|
||||
|
||||
## If variable is of enum
|
||||
|
||||
@ -56,6 +54,3 @@ if Some(user_id) = user_id && user_id > 0 {
|
||||
print("user_id is greater than 0: {user_id}")
|
||||
}
|
||||
`} />
|
||||
|
||||
|
||||
|
||||
|
@ -2,7 +2,8 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Loops
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Loops
|
||||
|
||||
@ -34,7 +35,6 @@ for value in dict {
|
||||
}
|
||||
`} />
|
||||
|
||||
|
||||
### Loop over keys and values
|
||||
|
||||
<Code thpcode={`
|
||||
@ -57,7 +57,6 @@ for key, value in dict {
|
||||
}
|
||||
`} />
|
||||
|
||||
|
||||
## While loop
|
||||
|
||||
<Code thpcode={`
|
||||
@ -70,20 +69,19 @@ while index < colors.size() {
|
||||
}
|
||||
`} />
|
||||
|
||||
|
||||
## Labelled loops
|
||||
|
||||
TBD
|
||||
|
||||
You can give labels to loops, allowing you to `break` and `continue` in nested loops.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
:top for i in values_1 {
|
||||
for j in values_2 {
|
||||
// ...
|
||||
break :top
|
||||
}
|
||||
}
|
||||
`} />
|
||||
|
||||
|
||||
`}
|
||||
/>
|
||||
|
@ -2,7 +2,8 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Match
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Match
|
||||
|
||||
@ -13,7 +14,6 @@ Braces are **required**.
|
||||
<Code thpcode={`
|
||||
val user_id = POST::get("user_id")
|
||||
|
||||
|
||||
match user_id
|
||||
case Some(id) { print("user_id exists: {id}") }
|
||||
case None { print("user_id doesn't exist") }
|
||||
@ -26,7 +26,6 @@ case None {
|
||||
print("user_id doesn't exist")
|
||||
}
|
||||
|
||||
|
||||
match user_id
|
||||
case Some(id) if id > 0 {
|
||||
print("user_id exists: {id}")
|
||||
@ -43,15 +42,14 @@ else {
|
||||
print("hello dear")
|
||||
}
|
||||
|
||||
match customer_id
|
||||
match customer*id
|
||||
| 1 | 2 | 3 {
|
||||
print("ehhhh")
|
||||
}
|
||||
| 4 | 5 {
|
||||
print("ohhh")
|
||||
}
|
||||
| _ {
|
||||
| * {
|
||||
print("???")
|
||||
}
|
||||
`} />
|
||||
|
||||
|
@ -2,7 +2,8 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Pipes
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Pipes
|
||||
|
||||
@ -11,24 +12,30 @@ expression as input to another.
|
||||
|
||||
For example, instead of writing:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
val result = third_function(second_function(first_function(my_value)))
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
You can use pipes:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
val result = my_value
|
||||
|> first_function
|
||||
|> second_function
|
||||
|> third_function
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
Or use it to group expressions:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
print <| (2 + 3 * 4) / 7
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
TBD: How to handle piping to functions with more than 1 param
|
||||
|
||||
@ -40,7 +47,7 @@ fun add_one(x: Int) -> Int {
|
||||
}
|
||||
|
||||
fun times_two(x: Int) -> Int {
|
||||
x * 2
|
||||
x \* 2
|
||||
}
|
||||
|
||||
// (Int) -> (Int)
|
||||
@ -48,5 +55,3 @@ val plus_one_times_2 = add_one >> times_two
|
||||
|
||||
print(plus_one_times_2(5)) // 12
|
||||
`} />
|
||||
|
||||
|
||||
|
@ -2,7 +2,8 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Declaration
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Declaration
|
||||
|
||||
@ -10,7 +11,6 @@ Functions in THP have a different syntax than PHP.
|
||||
|
||||
Function names **must** begin with a lowercase letter.
|
||||
|
||||
|
||||
## Minimal function
|
||||
|
||||
The following code shows a function without parameters
|
||||
@ -27,14 +27,12 @@ say_hello()
|
||||
|
||||
Functions are called the same way as in PHP.
|
||||
|
||||
|
||||
## With return type
|
||||
|
||||
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:
|
||||
|
||||
|
||||
<Code thpcode={`
|
||||
fun get_random_number() -> Int
|
||||
{
|
||||
@ -44,38 +42,38 @@ fun get_random_number() -> Int
|
||||
val number = get_random_number()
|
||||
`} />
|
||||
|
||||
|
||||
It's an error to return from a function that doesn't declare
|
||||
a return type:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fun get_random_number()
|
||||
{
|
||||
// Error: the function does not define a return type
|
||||
return Random::get(0, 35_222)
|
||||
}
|
||||
`} />
|
||||
|
||||
`}
|
||||
/>
|
||||
|
||||
You can omit the `return` keyword if it's placed on the last
|
||||
expression on the function:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fun get_random_number() -> Int
|
||||
{
|
||||
// The last expression of a function is
|
||||
// automatically returned
|
||||
Random::get(0, 35_222)
|
||||
}
|
||||
`} />
|
||||
|
||||
`}
|
||||
/>
|
||||
|
||||
## Function parameters
|
||||
|
||||
Parameters are declared like C-style languages:
|
||||
`Type name`, separated by commas.
|
||||
|
||||
|
||||
<Code thpcode={`
|
||||
fun add(Int a, Int b) -> Int
|
||||
{
|
||||
@ -89,8 +87,6 @@ THP has different semantics on parameters concerning
|
||||
pass by value, pass by reference and copy on write.
|
||||
This is detailed in another chapter.
|
||||
|
||||
|
||||
|
||||
## Generic types
|
||||
|
||||
Functions can declare generic types by using the syntax
|
||||
@ -99,7 +95,6 @@ Functions can declare generic types by using the syntax
|
||||
The following example declares a generic `T` and uses it
|
||||
in the parameters and return type:
|
||||
|
||||
|
||||
<Code thpcode={`
|
||||
fun get_first_item[T](Array[T] array) -> T
|
||||
{
|
||||
@ -112,27 +107,24 @@ val first = get_first_item[Int](numbers)
|
||||
val first = get_first_item(numbers)
|
||||
`} />
|
||||
|
||||
|
||||
## Default arguments
|
||||
|
||||
TBD
|
||||
|
||||
|
||||
## Variadic arguments
|
||||
|
||||
TBD
|
||||
|
||||
|
||||
## Signature
|
||||
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
() -> ()
|
||||
() -> (Int)
|
||||
(Int, Int) -> (Int)
|
||||
[T](Array[T]) -> (T)
|
||||
`} />
|
||||
|
||||
`}
|
||||
/>
|
||||
|
||||
## Named arguments
|
||||
|
||||
@ -165,8 +157,3 @@ fun greet(
|
||||
|
||||
greet("John", from: "LA")
|
||||
`} />
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -2,11 +2,11 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Higher Order Functions
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Higher Order functions
|
||||
|
||||
|
||||
## Function as parameters
|
||||
|
||||
<Code thpcode={`
|
||||
@ -28,10 +28,6 @@ fun generate_generator() -> () -> Int
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val generator = generate_generator() // A function
|
||||
val value = generate_generator()() // An Int
|
||||
`} />
|
||||
|
||||
|
||||
|
||||
|
@ -2,40 +2,45 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Lambdas
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Lambdas / Anonymous functions
|
||||
|
||||
|
||||
## Anonymous function
|
||||
|
||||
An anonymous function is declared with `fn`.
|
||||
Other than not having a name, it can declare parameter
|
||||
and return types.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fn(Int x, Int y) -> Int {
|
||||
x + y
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
Anonymous function can omit declaring those types as well:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
numbers.map(fun(x) {
|
||||
x * 2
|
||||
})
|
||||
`} />
|
||||
|
||||
`}
|
||||
/>
|
||||
|
||||
## Anonymous function short form
|
||||
|
||||
If you need an anonymous function that returns a single
|
||||
expression you can write it like this:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fun(x, y) = x + y
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
It uses an equal `=` instead of an arrow. It can contain
|
||||
only a single expression.
|
||||
@ -43,20 +48,18 @@ only a single expression.
|
||||
This is common when declaring functions that immediately
|
||||
return a map.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fn(value) = .{
|
||||
value
|
||||
}
|
||||
`} />
|
||||
|
||||
|
||||
|
||||
`}
|
||||
/>
|
||||
|
||||
## Closure types
|
||||
|
||||
By default closures **always** capture variables as **references**.
|
||||
|
||||
|
||||
<Code thpcode={`
|
||||
var x = 20
|
||||
|
||||
@ -70,14 +73,15 @@ x = 30
|
||||
f() // 30
|
||||
`} />
|
||||
|
||||
|
||||
You can force a closure to capture variables by value.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fun(parameters) clone(variables) {
|
||||
// code
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
<Code thpcode={`
|
||||
var x = 20
|
||||
@ -92,7 +96,6 @@ x = 30
|
||||
f() // 20
|
||||
`} />
|
||||
|
||||
|
||||
## Lambdas
|
||||
|
||||
Lambdas are a short form of anonymous functions. They are declared with `#{}`.
|
||||
@ -109,9 +112,6 @@ numbers.map() #{
|
||||
// the above lambda is equivalent to:
|
||||
|
||||
numbers.map(fun(param1) {
|
||||
$1 * 2
|
||||
$1 \* 2
|
||||
})
|
||||
`} />
|
||||
|
||||
|
||||
|
||||
|
@ -2,18 +2,20 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Function parameters
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Function parameters
|
||||
|
||||
|
||||
## Immutable reference
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fun add_25(Array[Int] numbers) {
|
||||
numbers.push(25) // Error: \`numbers\` is immutable
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
When using a regular type as a parameter, only it's immutable
|
||||
properties can be used
|
||||
@ -23,19 +25,21 @@ fun count(Array[Int] numbers) -> Int {
|
||||
val items_count = numbers.size() // Ok, \`size\` is pure
|
||||
|
||||
items_count
|
||||
|
||||
}
|
||||
`} />
|
||||
|
||||
To use immutable properties you must use a mutable reference.
|
||||
|
||||
|
||||
## Mutable reference
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fun push_25(mut Array[Int] numbers) {
|
||||
numbers.push(25) // Ok, will also mutate the original array
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
Placing a `mut` before the type makes the parameter a mutable
|
||||
reference. Mutable methods can be used, and the original
|
||||
@ -52,22 +56,21 @@ push_25(mut numbers) // Pass \`numbers\` as reference.
|
||||
print(numbers(4)) // \`Some(25)\`
|
||||
`} />
|
||||
|
||||
|
||||
|
||||
## Clone
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fun add_25(clone Array[Int] numbers) {
|
||||
numbers.push(25) // Ok, the original array is unaffected
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
Using the `clone` keyword before the type creates a mutable copy
|
||||
of the parameter (CoW). The original data will **not** be mutated.
|
||||
|
||||
The caller must also use `clone` when calling the function.
|
||||
|
||||
|
||||
<Code thpcode={`
|
||||
val numbers = Array(1, 2, 3, 4)
|
||||
|
||||
@ -75,5 +78,3 @@ add_25(clone numbers) // Pass \`numbers\` as clone.
|
||||
|
||||
print(numbers(4)) // None
|
||||
`} />
|
||||
|
||||
|
||||
|
@ -2,7 +2,8 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Nullable types
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Nullable types
|
||||
|
||||
@ -13,9 +14,11 @@ by the question mark `?` character.
|
||||
For instance, a POST request may have a `username` parameter,
|
||||
or it may not. This can be represented with an `?String`.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
?String new_username = POST::get("username")
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
When we have a `?Type` we cannot use it directly. We must first
|
||||
check if the value is null, and then use it.
|
||||
@ -71,7 +74,6 @@ val name = person?.name
|
||||
- If `person` is null, `person?.name` will return `null`
|
||||
- If `person` is not null, `person?.name` will return `name`
|
||||
|
||||
|
||||
## Null unboxing
|
||||
|
||||
The `!!` operator transforms a `?Type` into `Type`.
|
||||
@ -90,16 +92,17 @@ String s = lastname!!
|
||||
|
||||
You can use it to chain access:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
val children_lastname = person!!.child!!.lastname
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
However, if at runtime you use `!!` on a null value,
|
||||
the null value will be returned and your program will
|
||||
blow up later. So make sure to use this operator
|
||||
only when you are sure a value cannot be null.
|
||||
|
||||
|
||||
## Elvis operator
|
||||
|
||||
The Elvis operator `??` is used to give a default value in case a `null` is found.
|
||||
@ -114,13 +117,12 @@ val test_score = get_score() ?? 0
|
||||
For the above code:
|
||||
|
||||
- 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
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
val username = get_username() ?? return
|
||||
`} />
|
||||
|
||||
|
||||
|
||||
`}
|
||||
/>
|
||||
|
@ -2,8 +2,9 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Try/Exceptions
|
||||
---
|
||||
|
||||
import InteractiveCode from "@/components/InteractiveCode.astro";
|
||||
import Code from "@/components/Code.astro"
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Try/exceptions
|
||||
|
||||
@ -27,6 +28,7 @@ fun invert(Int number) -> DivisionByZero!Int
|
||||
}
|
||||
|
||||
return 1 / number
|
||||
|
||||
}
|
||||
`} />
|
||||
|
||||
@ -36,7 +38,6 @@ or an `Int`.
|
||||
|
||||
There is no `throw` keyword, errors are just returned.
|
||||
|
||||
|
||||
### Multiple error returns
|
||||
|
||||
TODO: properly define syntax, how this interacts with type unions.
|
||||
@ -44,12 +45,12 @@ TODO: properly define syntax, how this interacts with type unions.
|
||||
Multiple errors are chained with `!`. The last one is always
|
||||
the success value.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fun sample() -> Error1!Error2!Error3!Int
|
||||
{ /* ... */}
|
||||
`} />
|
||||
|
||||
|
||||
`}
|
||||
/>
|
||||
|
||||
## Error handling
|
||||
|
||||
@ -110,9 +111,10 @@ Use a naked `try` when you want to rethrow an error, if there is any.
|
||||
unset " = = ="
|
||||
set " dangerous()" "Int 50"
|
||||
}
|
||||
`}
|
||||
></InteractiveCode>
|
||||
|
||||
`}
|
||||
|
||||
> </InteractiveCode>
|
||||
|
||||
In the previous example:
|
||||
|
||||
@ -121,7 +123,6 @@ In the previous example:
|
||||
- If `dangerous()` succeedes, its value is assigned
|
||||
to `result`, and the function continues executing.
|
||||
|
||||
|
||||
### Try/return
|
||||
|
||||
Try/return will return a new value if an expression fails,
|
||||
@ -136,6 +137,7 @@ fun run() -> Int
|
||||
val result = try dangerous() return 0
|
||||
|
||||
// ...
|
||||
|
||||
}
|
||||
`} />
|
||||
|
||||
@ -146,7 +148,6 @@ In the previous example:
|
||||
- If `dangerous()` succeedes, its value will be assigned to `result`,
|
||||
and the function continues executing.
|
||||
|
||||
|
||||
### Try/else
|
||||
|
||||
Try/else will assign a new value if an expression fails.
|
||||
@ -210,7 +211,9 @@ Try/else will assign a new value if an expression fails.
|
||||
}
|
||||
step{line 0}
|
||||
`}
|
||||
|
||||
>
|
||||
|
||||
</InteractiveCode>
|
||||
|
||||
- If `possible_value` is an error, the value `666` is used.
|
||||
@ -218,12 +221,10 @@ Try/else will assign a new value if an expression fails.
|
||||
|
||||
Either way, the function will continue executing.
|
||||
|
||||
|
||||
### Try/catch
|
||||
|
||||
Try/catch allows the error to be manually used & handled.
|
||||
|
||||
|
||||
<Code thpcode={`
|
||||
fun run()
|
||||
{
|
||||
@ -239,12 +240,14 @@ fun run()
|
||||
// Return a new value to be assigned to \`result\`
|
||||
0
|
||||
}
|
||||
|
||||
}
|
||||
`} />
|
||||
|
||||
A try/catch may have many `catch` clauses:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
try dangerous()
|
||||
catch Exception1 e
|
||||
{...}
|
||||
@ -252,6 +255,5 @@ catch Exception2 e
|
||||
{...}
|
||||
catch Exception3 e
|
||||
{...}
|
||||
`} />
|
||||
|
||||
|
||||
`}
|
||||
/>
|
||||
|
@ -2,8 +2,7 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Abstract
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Abstract
|
||||
|
||||
|
||||
|
@ -2,11 +2,11 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Anonymous classes
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Anonymous classes
|
||||
|
||||
|
||||
<Code thpcode={`
|
||||
class Logger {
|
||||
pub fun log(String msg) {
|
||||
@ -25,10 +25,12 @@ setLogger(class {
|
||||
})
|
||||
`} />
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
setLogger(class(Int param1) -> SomeClass(param1), SomeInterface {
|
||||
pub fun method() {
|
||||
// code
|
||||
}
|
||||
})
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
@ -2,11 +2,11 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Constructor/Destructor
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Constructor/Destructor
|
||||
|
||||
|
||||
## Constructor
|
||||
|
||||
The constructor syntax in THP is inspired by Kotlin.
|
||||
@ -37,7 +37,6 @@ If you want to declare a constructor as protected
|
||||
or private you need to add the `constructor`
|
||||
keyword, after the visibility modifier:
|
||||
|
||||
|
||||
<Code thpcode={`
|
||||
// Cow has a protected constructor
|
||||
class Cow
|
||||
@ -48,7 +47,6 @@ class Bat
|
||||
private constructor(Int height)
|
||||
`} />
|
||||
|
||||
|
||||
### Init block
|
||||
|
||||
The `init` block allow us to run code during the
|
||||
@ -62,6 +60,7 @@ class Dog(String name) {
|
||||
init {
|
||||
print("Dog created: {name}")
|
||||
}
|
||||
|
||||
}
|
||||
`} />
|
||||
|
||||
@ -80,15 +79,13 @@ class Dog {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
### Constructor promotion
|
||||
|
||||
Constructor parameters can serve as class properties.
|
||||
This is done by adding a modifier and `var`/`val`.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
class Parrot(
|
||||
// A public property
|
||||
pub val String name,
|
||||
@ -97,15 +94,14 @@ class Parrot(
|
||||
// A private property
|
||||
var String last_name,
|
||||
)
|
||||
`} />
|
||||
|
||||
`}
|
||||
/>
|
||||
|
||||
By using this syntax you are declaring properties and assigning them
|
||||
at the same time.
|
||||
|
||||
The contructor parameters can also have default values.
|
||||
|
||||
|
||||
### Derived properties
|
||||
|
||||
You can declare properties whose values depend on values
|
||||
@ -125,40 +121,37 @@ val a2 = Animal("Doa")
|
||||
print(a2.name_length) //: 3
|
||||
`} />
|
||||
|
||||
|
||||
### Constructor that may fail
|
||||
|
||||
A constructor may only fail if there is code
|
||||
that can fail on the `init` block.
|
||||
|
||||
|
||||
|
||||
TBD
|
||||
|
||||
Proposal 1:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
class PrayingMantis(String name)
|
||||
throws Error {
|
||||
init -> Error! {
|
||||
// Initialization code that may fail
|
||||
}
|
||||
}
|
||||
`} />
|
||||
|
||||
`}
|
||||
/>
|
||||
|
||||
## Destructor
|
||||
|
||||
The destructor in THP is the same as PHP.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
class Owl() {
|
||||
pub fun __destruct() {
|
||||
// Cleanup
|
||||
print("Destroying Owl...")
|
||||
}
|
||||
}
|
||||
`} />
|
||||
|
||||
|
||||
|
||||
`}
|
||||
/>
|
||||
|
@ -2,7 +2,8 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Basics
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Classes
|
||||
|
||||
@ -14,25 +15,27 @@ Classes in THP are significantly different than PHP.
|
||||
|
||||
A class is defined as follows:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
class Animal() {}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
The name of the class **MUST** begin with an uppercase letter.
|
||||
|
||||
Classes have a parameter list even if they have no parameters
|
||||
for consistency sake.
|
||||
|
||||
|
||||
## Instanciation
|
||||
|
||||
To create an instance of a class call it as if it was a function,
|
||||
without `new`.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
val animal = Animal()
|
||||
`} />
|
||||
|
||||
`}
|
||||
/>
|
||||
|
||||
## Properties
|
||||
|
||||
@ -50,6 +53,7 @@ class Person() {
|
||||
|
||||
// This is also okay
|
||||
String name = "Jane Doe"
|
||||
|
||||
}
|
||||
`} />
|
||||
|
||||
@ -62,6 +66,7 @@ class Person() {
|
||||
|
||||
// To make a property public use \`pub\`
|
||||
pub var Int age = 30
|
||||
|
||||
}
|
||||
|
||||
val p = Person()
|
||||
@ -79,19 +84,19 @@ Readonly properties are explained in the Readonly page.
|
||||
The interaction between properties and the constructor is explained
|
||||
in the Constructor page.
|
||||
|
||||
|
||||
|
||||
## Methods
|
||||
|
||||
Methods are declared with `fun`, as regular functions.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
class Person() {
|
||||
fun greet() {
|
||||
print("Hello")
|
||||
}
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
**Methods are private by default**, and are made public with `pub`.
|
||||
|
||||
@ -106,6 +111,7 @@ class Person() {
|
||||
pub fun greet() {
|
||||
print("Hello from greet")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
val p = Person()
|
||||
@ -125,10 +131,10 @@ class Person() {
|
||||
fun name() -> String {
|
||||
"Rose"
|
||||
}
|
||||
|
||||
}
|
||||
`} />
|
||||
|
||||
|
||||
## This
|
||||
|
||||
THP uses the dollar sign `$` as this inside classes.
|
||||
@ -146,21 +152,23 @@ class Person() {
|
||||
val person_name = $get_name()
|
||||
print("Hello, I'm {person_name}")
|
||||
}
|
||||
|
||||
}
|
||||
`} />
|
||||
|
||||
|
||||
## Mutable methods
|
||||
|
||||
By default methods cannot mutate the state of the object.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
class Animal(var String name) {
|
||||
pub fun set_name(String new_name) {
|
||||
$name = new_name // Error: Cannot mutate $name
|
||||
}
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
To do so the method must be annotated. The caller must also
|
||||
declare a mutable variable.
|
||||
@ -175,4 +183,3 @@ class Animal(var String name) {
|
||||
var michi = Animal("Michifu")
|
||||
michi.set_name("Garfield")
|
||||
`} />
|
||||
|
||||
|
@ -2,7 +2,8 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Inheritance
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Inheritance
|
||||
|
||||
@ -28,12 +29,14 @@ Cat("Michi", 9).say_name()
|
||||
The call to the parent constructor is done right there, after
|
||||
the parent class name.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
class Dog(String name)
|
||||
extends Animal(name) {}
|
||||
// |----|
|
||||
// This is the call to the parent constructor
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
You must always call super, even if the parent doesn't
|
||||
define any parameters:
|
||||
@ -44,10 +47,3 @@ class Parent() {}
|
||||
class Child()
|
||||
extends Parent() {}
|
||||
`} />
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -2,12 +2,11 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Interfaces
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Interfaces
|
||||
|
||||
|
||||
|
||||
<Code thpcode={`
|
||||
interface Serializable
|
||||
{
|
||||
@ -15,8 +14,6 @@ interface Serializable
|
||||
fun serialize() -> String
|
||||
}
|
||||
|
||||
|
||||
|
||||
class Cat -> Serializable
|
||||
{
|
||||
pub fun Serializable() -> String
|
||||
@ -28,4 +25,3 @@ class Cat -> Serializable
|
||||
`} />
|
||||
|
||||
No interface inheritance.
|
||||
|
||||
|
@ -2,7 +2,8 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Magic methods
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Magic methods
|
||||
|
||||
@ -12,7 +13,7 @@ Don't get special treatment.
|
||||
|
||||
class Cat
|
||||
{
|
||||
pub fun __sleep() -> Array[String]
|
||||
pub fun \_\_sleep() -> Array[String]
|
||||
{
|
||||
// logic
|
||||
}
|
||||
@ -20,7 +21,6 @@ class Cat
|
||||
|
||||
`} />
|
||||
|
||||
|
||||
<Code thpcode={`
|
||||
val option = Some("GAAA")
|
||||
val Some(value) = option
|
||||
|
@ -2,14 +2,15 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Readonly
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Readonly
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
class Caño
|
||||
{
|
||||
}
|
||||
`} />
|
||||
|
||||
|
||||
`}
|
||||
/>
|
||||
|
@ -2,11 +2,11 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Static
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Static in classes
|
||||
|
||||
|
||||
## Class constants
|
||||
|
||||
<Code thpcode={`
|
||||
@ -23,12 +23,10 @@ static Cat
|
||||
print(Cat::CONSTANT)
|
||||
`} />
|
||||
|
||||
|
||||
## Static methods
|
||||
|
||||
aka. plain, old functions
|
||||
|
||||
|
||||
<Code thpcode={`
|
||||
static Cat
|
||||
{
|
||||
@ -41,12 +39,10 @@ static Cat
|
||||
Cat::static_method()
|
||||
`} />
|
||||
|
||||
|
||||
## Static properties
|
||||
|
||||
aka. global variables
|
||||
|
||||
|
||||
<Code thpcode={`
|
||||
static Cat
|
||||
{
|
||||
@ -57,7 +53,3 @@ print(Cat::access_count) // 0
|
||||
Cat::access_count += 1
|
||||
print(Cat::access_count) // 1
|
||||
`} />
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Visibility
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Visibility
|
||||
|
@ -2,8 +2,9 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Components
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
import Info from "@/components/docs/Info.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
import Info from "@/components/docs/Info.astro";
|
||||
|
||||
# Components
|
||||
|
||||
@ -14,14 +15,17 @@ Like React, a component is any function that returns `HTML`.
|
||||
will be uppercase or not.
|
||||
|
||||
For now they will be uppercase.
|
||||
|
||||
</Info>
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fun MyComponent() -> HTML
|
||||
{
|
||||
<p>Hello templates!</p>
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
Inside the HTML tags you can (mostly) write the HTML you already
|
||||
know.
|
||||
@ -37,6 +41,7 @@ fun MyComponent() -> HTML
|
||||
val name = "John"
|
||||
|
||||
<p>Hello {name}!</p>
|
||||
|
||||
}
|
||||
`} />
|
||||
|
||||
@ -57,6 +62,7 @@ fun MyComponent() -> HTML
|
||||
{if is_vip {"Welcome"} else {"Hi"}}
|
||||
{name}!
|
||||
</p>
|
||||
|
||||
}
|
||||
`} />
|
||||
|
||||
@ -74,8 +80,10 @@ fun MyComponent() -> HTML
|
||||
val user_input = "<b>BOLD</b>"
|
||||
|
||||
<p>answer: {user_input}</p>
|
||||
|
||||
}
|
||||
`} />
|
||||
|
||||
```html
|
||||
<p>answer: <b>BOLD</b></p>
|
||||
```
|
||||
@ -88,12 +96,15 @@ fun MyComponent() -> HTML
|
||||
val user_input = "<b>BOLD</b>"
|
||||
|
||||
<p>answer: <span raw-html={user_input}></span></p>
|
||||
|
||||
}
|
||||
`} />
|
||||
```html
|
||||
<p>answer: <span><b>BOLD</b></span></p>
|
||||
```
|
||||
|
||||
```html
|
||||
<p>
|
||||
answer: <span><b>BOLD</b></span>
|
||||
</p>
|
||||
```
|
||||
|
||||
## Dynamic attributes
|
||||
|
||||
@ -101,12 +112,14 @@ TODO: boolean attributes
|
||||
|
||||
Normal attributes (plain strings) work as you'd expect:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fun MyComponent() -> HTML
|
||||
{
|
||||
<button class="red">hello</button>
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
```html
|
||||
<button class="red">hello</button>
|
||||
```
|
||||
@ -121,29 +134,30 @@ fun MyComponent() -> HTML
|
||||
|
||||
// Note the braces
|
||||
<button class={button_class}>hello</button>
|
||||
|
||||
}
|
||||
`} />
|
||||
|
||||
```html
|
||||
<button class="blue">hello</button>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Fragments
|
||||
|
||||
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.
|
||||
|
||||
|
||||
The following code doesn't work as you would expect:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fun MyComponent() -> HTML
|
||||
{
|
||||
<p>hello</p> // This is an error, an ignored expression
|
||||
<p>world</p>
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
Each `<p>` is a single expression, they are not grouped.
|
||||
And since we cannot have unused expressions, the code will not compile.
|
||||
@ -151,7 +165,8 @@ 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:
|
||||
`<></>`
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fun MyComponent() -> HTML
|
||||
{
|
||||
// This is the root "element"
|
||||
@ -160,7 +175,8 @@ fun MyComponent() -> HTML
|
||||
<p>world</p>
|
||||
</>
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
```html
|
||||
<p>hello</p>
|
||||
<p>world</p>
|
||||
@ -179,16 +195,14 @@ fun User() -> HTML
|
||||
|
||||
fun MyComponent() -> HTML
|
||||
{
|
||||
|
||||
<>
|
||||
<p>status</p>
|
||||
<User /> // Here we are using the other component
|
||||
</>
|
||||
}
|
||||
`} />
|
||||
} `} />
|
||||
|
||||
```html
|
||||
<p>status</p>
|
||||
<span>world</span>
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
@ -2,8 +2,9 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Control flow
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
import Info from "@/components/docs/Info.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
import Info from "@/components/docs/Info.astro";
|
||||
|
||||
# Control flow
|
||||
|
||||
@ -12,7 +13,8 @@ import Info from "@/components/docs/Info.astro"
|
||||
Use `@if`, `@else if` and `@else` to branch during the template
|
||||
creation.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fun User(User user) -> HTML
|
||||
{
|
||||
<div>
|
||||
@ -26,8 +28,8 @@ fun User(User user) -> HTML
|
||||
}
|
||||
</div>
|
||||
}
|
||||
`} />
|
||||
|
||||
`}
|
||||
/>
|
||||
|
||||
## Loops
|
||||
|
||||
@ -44,15 +46,16 @@ fun Users() -> HTML
|
||||
<User user={user} />
|
||||
}
|
||||
</div>
|
||||
|
||||
}
|
||||
`} />
|
||||
|
||||
|
||||
## Match
|
||||
|
||||
Use `@match` for pattern matching:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fun UserDetail(User user) -> HTML
|
||||
{
|
||||
<div>
|
||||
@ -67,8 +70,5 @@ fun UserDetail(User user) -> HTML
|
||||
}
|
||||
</div>
|
||||
}
|
||||
`} />
|
||||
|
||||
|
||||
|
||||
|
||||
`}
|
||||
/>
|
||||
|
@ -2,8 +2,9 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Introduction
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
import Info from "@/components/docs/Info.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
import Info from "@/components/docs/Info.astro";
|
||||
|
||||
# THP templating
|
||||
|
||||
@ -62,18 +63,19 @@ and compose them.
|
||||
|
||||
The following would be the equivalent in THP:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fun Button(String name) -> HTML {
|
||||
<button class="some tailwind classes">
|
||||
Hello {name}!
|
||||
</button>
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
It is very similar to React. The HTML is inside the THP code, not the other
|
||||
way around, so you can have arbitrary logic in the component.
|
||||
|
||||
|
||||
<Code thpcode={`
|
||||
fun User(String name) -> HTML {
|
||||
// Get info from the database
|
||||
@ -92,15 +94,15 @@ fun User(String name) -> HTML {
|
||||
<TransactionItem t={t} />
|
||||
}
|
||||
</div>
|
||||
|
||||
}
|
||||
|
||||
fun TransactionItem(Transaction t) -> HTML {
|
||||
|
||||
<li>
|
||||
{t.date} - {t.name} ({t.price})
|
||||
</li>
|
||||
}
|
||||
`} />
|
||||
|
||||
} `} />
|
||||
|
||||
## Is this a JavaScript Front-End Framework?
|
||||
|
||||
@ -114,19 +116,14 @@ 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/)
|
||||
and [hyperscript](https://hyperscript.org/).
|
||||
|
||||
|
||||
|
||||
## Styling
|
||||
|
||||
We don't provide any syntax or facility for styling.
|
||||
However, this component model is good to use with TailwindCSS.
|
||||
|
||||
|
||||
## 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
|
||||
those will be then composed.
|
||||
@ -145,6 +142,7 @@ fun Sample(String name) -> HTML {
|
||||
Not logged in
|
||||
}
|
||||
</button>
|
||||
|
||||
}
|
||||
`} />
|
||||
|
||||
@ -163,6 +161,3 @@ function Sample(name) {
|
||||
return "<button>{$__expr_1}</button>"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
@ -2,8 +2,9 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Props
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
import Info from "@/components/docs/Info.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
import Info from "@/components/docs/Info.astro";
|
||||
|
||||
# Props
|
||||
|
||||
@ -15,23 +16,27 @@ and they are defined as normal parameters.
|
||||
For example, to receive a `String` declare it as a
|
||||
parameter:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fun Greeter(String name) -> HTML
|
||||
{
|
||||
<p>Hello {name}!</p>
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
And to send its value type the parameter name as an attribute:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fun Home() -> HTML
|
||||
{
|
||||
<div>
|
||||
<Greeter name="Rose" />
|
||||
</div>
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
```html
|
||||
<div><p>Hello Rose</p></div>
|
||||
@ -39,18 +44,21 @@ fun Home() -> HTML
|
||||
|
||||
You can have as many props as you'd like, of any datatype:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fun Greeter(String name, Int age, Array[String] friends) -> HTML
|
||||
{
|
||||
// ...
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
## Static props
|
||||
|
||||
If the prop has a type `String` you can use a normal attribute.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fun Home() -> HTML
|
||||
{
|
||||
<div>
|
||||
@ -58,8 +66,8 @@ fun Home() -> HTML
|
||||
<Greeter name="Rose" />
|
||||
</div>
|
||||
}
|
||||
`} />
|
||||
|
||||
`}
|
||||
/>
|
||||
|
||||
## Dynamic props
|
||||
|
||||
@ -80,16 +88,17 @@ fun Home() -> HTML
|
||||
<div>
|
||||
<Sample cat={my_cat} />
|
||||
</div>
|
||||
|
||||
}
|
||||
`} />
|
||||
|
||||
|
||||
## Components as props
|
||||
|
||||
If for some reason you want to use a component as a prop
|
||||
use the `HTML` datatype:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
// The parameter can have any name, not only \`child\`
|
||||
fun Sample(HTML child) -> HTML
|
||||
{
|
||||
@ -98,19 +107,22 @@ fun Sample(HTML child) -> HTML
|
||||
{child}
|
||||
</>
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
This, however, means that your prop component must be declared
|
||||
as an attribute:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fun Home() -> HTML
|
||||
{
|
||||
<div>
|
||||
<Sample child={<span>I am the child</span>} />
|
||||
</div>
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
```html
|
||||
<div>
|
||||
@ -136,21 +148,16 @@ fun MyButton() -> HTML
|
||||
|
||||
fun Home() -> HTML
|
||||
{
|
||||
|
||||
<div>
|
||||
<MyButton>
|
||||
buy <b>now!</b>
|
||||
</MyButton>
|
||||
</div>
|
||||
}
|
||||
`} />
|
||||
} `} />
|
||||
|
||||
```html
|
||||
<div>
|
||||
<button>
|
||||
buy <b>now!</b>
|
||||
</button>
|
||||
<button>buy <b>now!</b></button>
|
||||
</div>
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -4,10 +4,13 @@ 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>;
|
||||
const posts = (await Astro.glob(
|
||||
"./**/*.{md,mdx}",
|
||||
)) as unknown as Array<AstroFile>;
|
||||
|
||||
// The base of every URL under this glob
|
||||
const base_url = "/en/latest/learn"
|
||||
const base_url = "/en/latest/learn";
|
||||
const version = "latest";
|
||||
---
|
||||
|
||||
<NewDocsLayout
|
||||
@ -15,6 +18,7 @@ const base_url = "/en/latest/learn"
|
||||
frontmatter={frontmatter}
|
||||
headings={headings}
|
||||
posts={posts}
|
||||
version={version}
|
||||
>
|
||||
<slot />
|
||||
</NewPagesLayout>
|
||||
</NewDocsLayout>
|
||||
|
@ -3,9 +3,9 @@ layout: "./_wrapper.astro"
|
||||
title: Welcome
|
||||
order: 1
|
||||
---
|
||||
import InteractiveCode from "@/components/InteractiveCode.astro";
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import InteractiveCode from "@/components/InteractiveCode.astro";
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Welcome
|
||||
|
||||
@ -15,11 +15,9 @@ THP is a new programming language that compiles to PHP.
|
||||
|
||||
![Accurate visual description of THP](/img/desc_thp.jpg)
|
||||
|
||||
|
||||
This page details the main design desitions of the language,
|
||||
if you want to install THP go to the [installation guide](install)
|
||||
|
||||
|
||||
## Why?
|
||||
|
||||
PHP is an old language. It has been growing since 1995, adopting a
|
||||
@ -71,10 +69,6 @@ essential today: A good, unified LSP, type definitions,
|
||||
accesible documentation, an opinionated code formatter
|
||||
and plugins for major editors like VSCode and Neovim.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Goals
|
||||
|
||||
- Bring static typing to PHP: Generics, type checks at compile and runtime, etc.
|
||||
@ -93,7 +87,6 @@ and plugins for major editors like VSCode and Neovim.
|
||||
|
||||
![Friendship ended with Rust, now Zig is my best friend.](/img/mudasir.jpg)
|
||||
|
||||
|
||||
## Not goals
|
||||
|
||||
These are **not** things that THP wants to solve or implement
|
||||
@ -104,7 +97,6 @@ These are **not** things that THP wants to solve or implement
|
||||
THP **_intentionally_** uses a different syntax from PHP to signal
|
||||
that it is a different language, and has different semantics.
|
||||
|
||||
|
||||
## Some differences with PHP
|
||||
|
||||
```php
|
||||
@ -113,11 +105,13 @@ $has_key = str_contains($haystack, 'needle');
|
||||
print("has key? " . $has_key);
|
||||
```
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
// THP
|
||||
val has_key = haystack.contains("needle")
|
||||
print("has key? " + has_key)
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
- Explicit variable declaration
|
||||
- No `$` for variable names (and thus no `$$variable`, use a map instead)
|
||||
@ -126,7 +120,6 @@ print("has key? " + has_key)
|
||||
- Strings use only double quotes
|
||||
- String concatenation with `+`
|
||||
|
||||
|
||||
<br />
|
||||
<br />
|
||||
|
||||
@ -139,14 +132,16 @@ $obj = [
|
||||
]
|
||||
```
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
// THP
|
||||
val obj = .{
|
||||
names: #("Toni", "Stark"), // Tuple
|
||||
age: 33,
|
||||
numbers: [32, 64, 128]
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
- Tuples, Arrays, Sets, Maps are clearly different
|
||||
- JSON-like object syntax
|
||||
@ -160,12 +155,13 @@ $cat = new Cat("Michifu", 7);
|
||||
$cat->meow();
|
||||
```
|
||||
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
// THP
|
||||
val cat = Cat("Michifu", 7)
|
||||
cat.meow()
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
- Instantiate classes without `new`
|
||||
- Use dot `.` instead of arrow `->` syntax
|
||||
@ -179,11 +175,12 @@ use \Some\Deeply\Nested\Class
|
||||
use \Some\Deeply\Nested\Interface
|
||||
```
|
||||
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
// THP
|
||||
use Some::Deeply::Nested::{Class, Interface}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
- Different module syntax
|
||||
- Explicit module declaration
|
||||
@ -193,20 +190,19 @@ use Some::Deeply::Nested::{Class, Interface}
|
||||
<br />
|
||||
<br />
|
||||
|
||||
|
||||
Other things:
|
||||
|
||||
- Pattern matching
|
||||
- ADTs
|
||||
|
||||
|
||||
## Runtime changes
|
||||
|
||||
Where possible THP will compile to available PHP functions/classes/methods/etc.
|
||||
|
||||
For example:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
// This expression
|
||||
val greeting =
|
||||
match get_person()
|
||||
@ -222,7 +218,8 @@ val greeting =
|
||||
{
|
||||
"Nobody is here"
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
```php
|
||||
// Would compile to:
|
||||
@ -243,5 +240,3 @@ else {
|
||||
|
||||
However, more advanced datatypes & helper functions will require a sort of
|
||||
runtime (new classes/functions/etc) or abuse the language's syntax/semantics.
|
||||
|
||||
|
||||
|
24
src/pages/en/v0.0.1/learn/_wrapper.astro
Normal file
24
src/pages/en/v0.0.1/learn/_wrapper.astro
Normal file
@ -0,0 +1,24 @@
|
||||
---
|
||||
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>
|
12
src/pages/en/v0.0.1/learn/index.mdx
Normal file
12
src/pages/en/v0.0.1/learn/index.mdx
Normal file
@ -0,0 +1,12 @@
|
||||
---
|
||||
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
|
@ -45,8 +45,8 @@ const [thp_html] = await native_highlighter(
|
||||
compiled to PHP
|
||||
</h1>
|
||||
<p class="text-c-text text-lg pt-6 lg:pr-12">
|
||||
Inspired by Rust, Zig and Kotlin, THP has a modern syntax,
|
||||
semantics, type system and stdlib.
|
||||
Inspired by Rust, Zig and Kotlin, THP has a modern syntax, semantics,
|
||||
type system and stdlib.
|
||||
</p>
|
||||
<div class="text-center pb-4">
|
||||
<a
|
||||
@ -85,11 +85,7 @@ const [thp_html] = await native_highlighter(
|
||||
height="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
|
||||
cx="6"
|
||||
cy="6"
|
||||
@ -144,10 +140,10 @@ const [thp_html] = await native_highlighter(
|
||||
val result = mock() as String
|
||||
`}
|
||||
>
|
||||
Type safety is enforced at compile time. Everything
|
||||
has a specific type. There is no `mixed`.
|
||||
<br>
|
||||
<br>
|
||||
Type safety is enforced at compile time. Everything has a specific type.
|
||||
There is no `mixed`.
|
||||
<br />
|
||||
<br />
|
||||
You can use generics where neccesary.
|
||||
</HeroSection>
|
||||
|
||||
@ -199,9 +195,9 @@ const [thp_html] = await native_highlighter(
|
||||
}
|
||||
`}
|
||||
>
|
||||
Nulls are explicit and require handling. Types can be nullable,
|
||||
and they must be checked before usage.
|
||||
<br>
|
||||
Nulls are explicit and require handling. Types can be nullable, and they
|
||||
must be checked before usage.
|
||||
<br />
|
||||
The stdlib makes extensive use of them.
|
||||
</HeroSection>
|
||||
|
||||
@ -216,11 +212,9 @@ const [thp_html] = await native_highlighter(
|
||||
}
|
||||
`}
|
||||
>
|
||||
Exceptions are values and don't disrupt
|
||||
control flow.
|
||||
<br>
|
||||
<br>
|
||||
Errors cannot be ignored, and we have
|
||||
syntax sugar to ease them.
|
||||
Exceptions are values and don't disrupt control flow.
|
||||
<br />
|
||||
<br />
|
||||
Errors cannot be ignored, and we have syntax sugar to ease them.
|
||||
</HeroSection>
|
||||
</BaseLayout>
|
||||
|
@ -4,10 +4,12 @@ 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>;
|
||||
const posts = (await Astro.glob(
|
||||
"./**/*.{md,mdx}",
|
||||
)) as unknown as Array<AstroFile>;
|
||||
|
||||
// The base of every URL under this glob
|
||||
const base_url = "/spec"
|
||||
const base_url = "/spec";
|
||||
---
|
||||
|
||||
<NewDocsLayout
|
||||
@ -17,5 +19,4 @@ const base_url = "/spec"
|
||||
posts={posts}
|
||||
>
|
||||
<slot />
|
||||
</NewPagesLayout>
|
||||
|
||||
</NewDocsLayout>
|
||||
|
@ -69,7 +69,6 @@ BlockMember = Statement
|
||||
| Expression
|
||||
```
|
||||
|
||||
|
||||
## Assignment
|
||||
|
||||
The target of an assignment can only be an identifier for now.
|
||||
@ -87,5 +86,3 @@ AssignmentOperator = "="
|
||||
| "/="
|
||||
| "%="
|
||||
```
|
||||
|
||||
|
||||
|
@ -8,14 +8,12 @@ title: Expression
|
||||
The expression parser effectively implements a precedence table.
|
||||
|
||||
| Operator | Precedence |
|
||||
|------------|------------|
|
||||
| --------- | ---------- |
|
||||
| == != | 5 |
|
||||
| > >= < <= | 4 |
|
||||
| - + ++ | 3 |
|
||||
| . ?. !. | 2 |
|
||||
| / * % | 1 |
|
||||
|
||||
|
||||
| / \* % | 1 |
|
||||
|
||||
```ebnf
|
||||
Expression = Equality
|
||||
@ -29,7 +27,6 @@ Unary = ("!" | "-"), Expression
|
||||
| CallExpression
|
||||
```
|
||||
|
||||
|
||||
## CallExpression
|
||||
|
||||
It's so hard to properly name these constructions.
|
||||
@ -40,6 +37,3 @@ CallExpression = primary, "(", (arguments list)?, ")"
|
||||
| primary, "[", (expression, (comma, expression)*, comma?)? "]"
|
||||
| primary
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
@ -2,8 +2,8 @@
|
||||
layout: "./_wrapper.astro"
|
||||
title: Welcome
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# The THP Language Specification
|
||||
|
||||
@ -42,14 +42,11 @@ The compiler consists of 5 common phases:
|
||||
- **IR**: Transforms the THP AST into a PHP AST
|
||||
- **Codegen**: Generates PHP source code from the PHP AST
|
||||
|
||||
|
||||
|
||||
## Source Code representation
|
||||
|
||||
Source code is encoded in UTF-8, and a single UTF-8 codepoint is
|
||||
a single character.
|
||||
|
||||
|
||||
## Basic characters
|
||||
|
||||
Although the source code must be encoded in UTF-8, most of the actual
|
||||
@ -68,7 +65,6 @@ lowercase_letter = "a".."z"
|
||||
uppercase_letter = "A".."Z"
|
||||
```
|
||||
|
||||
|
||||
## Whitespace & Automatic semicolon insertion
|
||||
|
||||
This section is being reworked on the Zig rewrite of the compiler.
|
||||
@ -85,10 +81,12 @@ parenthesis, square brackets, etc.
|
||||
Other statements require a explicit terminator. For example,
|
||||
the assignment statement:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
val computation = 123 + 456 // how to detect if the statement ends here
|
||||
* 789 // or extends up to here?
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
In other languages a semicolon would be used to signal the end of the
|
||||
statement:
|
||||
@ -106,21 +104,25 @@ to the rule:
|
||||
No matter the indentation, whitespace or others, every statement ends
|
||||
with a newline.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
val compute = 1 + 2 * 3 / 4
|
||||
// statement ends here ↑
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
As mentioned before, this does not affect statements that have clear delimiters.
|
||||
For example, the following code will work as expected:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
val compute = my_function(
|
||||
param1,
|
||||
param2,
|
||||
) / 64
|
||||
// ↑ statement ends here
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
In a way, the parenthesis will "disable" the rule.
|
||||
|
||||
@ -133,11 +135,13 @@ continues.
|
||||
|
||||
For example:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
val computation = 123 + 456
|
||||
* 789
|
||||
// ↑ statement ends here, and there is a single statement
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
This is so no matter the indentation:
|
||||
|
||||
@ -145,25 +149,26 @@ This is so no matter the indentation:
|
||||
// weird indentation:
|
||||
|
||||
val computation = 123 + 456
|
||||
* 789
|
||||
|
||||
- 789
|
||||
// ↑ statement still ends here
|
||||
`} />
|
||||
|
||||
What is important is that an operator begins the new line.
|
||||
If the operator is left on the previous line, this will not work:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
// statement ends here ↓, and now there is a syntax error (dangling operator)
|
||||
val computation = 123 + 456 *
|
||||
789
|
||||
// ↑ this is a different statement
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
For this the parser must do look-ahead of 1 token. This is the only place the parser
|
||||
does so.
|
||||
|
||||
|
||||
|
||||
## Old Whitespace rules
|
||||
|
||||
THP is partially whitespace sensitive. It uses the following tokens: Indent, Dedent & NewLine
|
||||
@ -174,32 +179,33 @@ 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
|
||||
if it's the same it does nothing.
|
||||
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
1 + 2
|
||||
+ 3
|
||||
+ 4
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
The previous code would emit the following tokens: `1` `+` `2` `NewLine` `Indent` `+` `3` `NewLine`
|
||||
`+` `4` `Dedent`
|
||||
|
||||
|
||||
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
|
||||
doesn't match a previous level.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
if true { // 0 indentation
|
||||
print() // 4 indentation
|
||||
print() // 2 indentation. Error. There is no 2-indentation level
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
All productions of the grammar ignore whitespace/indentation, except those involved in
|
||||
semicolon inference.
|
||||
|
||||
|
||||
## Statement termination / Semicolon inference
|
||||
|
||||
**Only inside a block of code** whitespace is used to determine where a statement ends
|
||||
@ -207,18 +213,19 @@ and a new one begins. Everywhere else whitespace is ignored.
|
||||
|
||||
Statements in THP end when a new line is encountered:
|
||||
|
||||
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
// The statement ends | here, on the newline
|
||||
val value = (123 + 456) * 0.75
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
<Code thpcode={`
|
||||
// Each line contains a different statement. They all end on their new lines
|
||||
|
||||
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:
|
||||
@ -230,18 +237,19 @@ var a = 1 + 2 + // This is now a compile error, there is a hanging `+`
|
||||
3 // This is still a different statement
|
||||
`} />
|
||||
|
||||
|
||||
### Parenthesis
|
||||
|
||||
Exception 1: When a parenthesis is open, all following whitespace is ignored
|
||||
until the closing parenthesis.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
// open parenthesis found, all whitespace is ignored until the closing
|
||||
name.contains(
|
||||
"weird"
|
||||
)
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
However, for a parenthesis to begin to act, it needs to be open on the same line.
|
||||
|
||||
@ -264,17 +272,21 @@ Exception 2:
|
||||
|
||||
- When a binary operator is followed by indentation:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
val sum = 1 + 2 + // The line ends with a binary operator
|
||||
3 // There is indentation
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
- Or when indentation is followed by a binary operator:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
val sum = 1 + 2
|
||||
+ 3 // Indentation and a binary operator
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
In theses cases, all whitespace will be ignored
|
||||
until the indentation returns to the initial level.
|
||||
@ -291,5 +303,3 @@ val person = PersonBuilder()
|
||||
// Here indentation returns, and a new statement begins
|
||||
print(person)
|
||||
`} />
|
||||
|
||||
|
||||
|
@ -2,7 +2,8 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Comment
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Comment
|
||||
|
||||
@ -10,8 +11,10 @@ import Code from "@/components/Code.astro"
|
||||
Comment = "//", any_except_new_line
|
||||
```
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
// This is a comment
|
||||
//
|
||||
// Another // comment
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
@ -2,7 +2,8 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Identifiers & Datatypes
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Identifiers & Datatypes
|
||||
|
||||
@ -19,14 +20,15 @@ Identifier = (underscore | lowercase_letter), identifier_letter*
|
||||
identifier_letter = underscore | lowercase_letter | uppercase_letter | decimal_digit
|
||||
```
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
identifier
|
||||
_identifier
|
||||
_123
|
||||
_many_letters
|
||||
camelCase
|
||||
`} />
|
||||
|
||||
`}
|
||||
/>
|
||||
|
||||
## Datatype
|
||||
|
||||
@ -34,22 +36,23 @@ camelCase
|
||||
Datatype = uppercase_letter, indentifier_letter*
|
||||
```
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
Datatype
|
||||
PDO
|
||||
WEIRD_DATATYPE
|
||||
`} />
|
||||
|
||||
`}
|
||||
/>
|
||||
|
||||
## Keywords
|
||||
|
||||
The following are (currently) THP keywords:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
val var fun
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
Keywords are scanned first as identifiers, then transformed
|
||||
to their respective tokens.
|
||||
|
||||
|
||||
|
@ -2,7 +2,8 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Numbers
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Numbers
|
||||
|
||||
@ -20,7 +21,8 @@ binary_number = "0", ("b" | "B"), binary_digit+
|
||||
decimal_number = "1".."9", decimal_digit*
|
||||
```
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
12345
|
||||
01234 // This is an error, a decimal number cant have
|
||||
// leading zeroes
|
||||
@ -28,12 +30,11 @@ decimal_number = "1".."9", decimal_digit*
|
||||
0b0110
|
||||
0xff25
|
||||
0XFfaA
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
`TODO`: Allow underscores `_` between any number: `1_000_000`.
|
||||
|
||||
|
||||
|
||||
## Float
|
||||
|
||||
```ebnf
|
||||
@ -52,12 +53,7 @@ scientific_notation = "e", ("+" | "-"), decimal_digit+
|
||||
123e-3
|
||||
`} />
|
||||
|
||||
|
||||
All floating point numbers must start with at least 1 digit.
|
||||
`.5` is not a valid floating point number.
|
||||
|
||||
|
||||
`TODO`: Allow scientific notation to omit the `+`/`-`: `10e4`.
|
||||
|
||||
|
||||
|
||||
|
@ -2,11 +2,11 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Operator
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Operator
|
||||
|
||||
|
||||
```ebnf
|
||||
Operator = operator_char+
|
||||
|
||||
@ -15,9 +15,11 @@ operator_char = "+" | "-" | "=" | "*" | "!" | "/" | "|"
|
||||
| "<" | ">" | "^" | "." | ":"
|
||||
```
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
+ - / * % < > <= >= -> =>
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
These are all the characters that can make an operator.
|
||||
|
||||
|
@ -2,7 +2,8 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: String
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# String
|
||||
|
||||
@ -20,11 +21,13 @@ escape_seq = "\n"
|
||||
string_char = any_unicode_except_newline_and_double_quote
|
||||
```
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
"hello"
|
||||
""
|
||||
"it's me"
|
||||
"\\"Mario\\""
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
`TODO`: String interpolation
|
||||
|
@ -34,5 +34,3 @@ pub enum TokenType {
|
||||
```
|
||||
|
||||
Every keyword has its own token.
|
||||
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: ['./src/**/*.{astro,html,js,jsx,md,mdx,ts,tsx}'],
|
||||
content: ["./src/**/*.{astro,html,js,jsx,md,mdx,ts,tsx}"],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
@ -17,48 +17,48 @@ export default {
|
||||
"c-primary": "var(--c-primary)",
|
||||
"c-secondary": "var(--c-secondary)",
|
||||
"c-nav-bg": "var(--c-nav-bg)",
|
||||
}
|
||||
},
|
||||
},
|
||||
fontFamily: {
|
||||
"mono": "var(--font-code)",
|
||||
"display": "var(--font-display)",
|
||||
"body": "var(--font-body)",
|
||||
mono: "var(--font-code)",
|
||||
display: "var(--font-display)",
|
||||
body: "var(--font-body)",
|
||||
},
|
||||
},
|
||||
corePlugins: {
|
||||
container: false
|
||||
container: false,
|
||||
},
|
||||
plugins: [
|
||||
function ({ addComponents }) {
|
||||
addComponents({
|
||||
'.container': {
|
||||
width: '98%',
|
||||
'@screen sm': {
|
||||
maxWidth: '640px',
|
||||
".container": {
|
||||
width: "98%",
|
||||
"@screen sm": {
|
||||
maxWidth: "640px",
|
||||
},
|
||||
'@screen md': {
|
||||
maxWidth: '768px',
|
||||
"@screen md": {
|
||||
maxWidth: "768px",
|
||||
},
|
||||
'@screen lg': {
|
||||
maxWidth: '1024px',
|
||||
"@screen lg": {
|
||||
maxWidth: "1024px",
|
||||
},
|
||||
'@screen xl': {
|
||||
maxWidth: '1400px',
|
||||
"@screen xl": {
|
||||
maxWidth: "1400px",
|
||||
},
|
||||
},
|
||||
'.small-container': {
|
||||
width: '98%',
|
||||
'@screen sm': {
|
||||
maxWidth: '640px',
|
||||
".small-container": {
|
||||
width: "98%",
|
||||
"@screen sm": {
|
||||
maxWidth: "640px",
|
||||
},
|
||||
'@screen md': {
|
||||
maxWidth: '768px',
|
||||
"@screen md": {
|
||||
maxWidth: "768px",
|
||||
},
|
||||
'@screen lg': {
|
||||
maxWidth: 'inherit',
|
||||
"@screen lg": {
|
||||
maxWidth: "inherit",
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
],
|
||||
}
|
||||
};
|
||||
|
@ -3,7 +3,7 @@
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["src/*"],
|
||||
"@/*": ["src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user