Compare commits

...

10 Commits

12 changed files with 322 additions and 22 deletions

BIN
public/Iosevka/Bold.woff2 Normal file

Binary file not shown.

BIN
public/Iosevka/Heavy.woff2 Normal file

Binary file not shown.

Binary file not shown.

View File

@ -1,3 +1,22 @@
/* Iosevka web */
@font-face {
font-family: "Iosevka Fixed Web";
font-display: swap;
font-weight: 400;
font-stretch: normal;
font-style: normal;
src: url("/Iosevka/Regular.woff2") format("woff2");
}
@font-face {
font-family: 'Iosevka Fixed Web';
font-display: swap;
font-weight: 700;
font-stretch: normal;
font-style: normal;
src: url('/Iosevka/Bold.woff2') format('woff2');
}
:root { :root {
--c-bg: #121212; --c-bg: #121212;
@ -49,7 +68,7 @@ body {
} }
pre, code { pre, code {
font-family: Iosevka, 'Fira Code', monospace; font-family: "Iosevka Fixed Web", "Iosevka Nerd Font", Iosevka, monospace;
} }
.button-effect-receiver { .button-effect-receiver {

View File

@ -42,7 +42,7 @@
padding: 0.5rem 0; padding: 0.5rem 0;
} }
.markdown pre { .markdown > pre {
margin: 0.5em 0; margin: 0.5em 0;
padding: 0.75em 0.75em; padding: 0.75em 0.75em;
color: var(--code-theme-color); color: var(--code-theme-color);

View File

@ -0,0 +1,193 @@
---
import { lex } from "../lexer/lexer";
const {code, steps} = Astro.props;
/**
* Performs the following:
* - Removes the first & last line, if they are empty
* - Picks the indentation level from the first non-white line
* - Dedents the following lines
*/
function trimAndDedent(input: string): Array<string> {
let lines = input.split("\n");
// Remove empty lines at the start
while (lines[0] === "") {
lines = lines.slice(1);
}
// Get indentation level
let indentationLevel = 0;
for (const char of lines[0]!) {
if (char === " ") {
indentationLevel += 1;
} else {
break;
}
}
// Enforce indentation, or trim
for (let i = 0; i < lines.length; i += 1) {
// If the line is empty, continue
const characters = lines[i]!.split("");
if (characters.length === 0) {
continue;
}
// If all characters are whitespace, append just a newline
const nonWhitespace = characters.find((x) => x !== " ");
if (nonWhitespace === undefined) {
lines[i] = "";
continue;
}
// Enforce indentation
if (characters.length < indentationLevel) {
throw new Error("Invalid indentation while parsing THP code: " + lines[i]);
}
let currentIndentation = 0;
for (const c of characters) {
if (c === " ") { currentIndentation += 1 }
else {break;}
}
if (currentIndentation < indentationLevel) {
throw new Error("Invalid indentation while parsing THP code: " + lines[i]);
}
lines[i] = characters.slice(4).join("");
}
// Remove empty lines at the end
let endPosition = lines.length - 1;
while (true) {
if (lines[endPosition] === "") {
lines = lines.slice(0, -1);
endPosition -= 1;
} else {
break;
}
}
return lines;
}
function highlightCode(lines: Array<string>): string {
let outLines: Array<string> = [];
for (const [idx, line] of lines.entries()) {
const tokens = lex(line);
const lineArray = [
`<div class=\"inline-block w-full\" :class=\"line === ${idx + 1}? 'bg-green-200 dark:bg-green-900': ''\">`
];
for (const token of tokens) {
if (token.token_type !== "") {
lineArray.push(`<span class="token ${token.token_type}">${token.v}</span>`);
} else {
lineArray.push(token.v);
}
}
lineArray.push("</div>");
outLines.push(lineArray.join(""));
}
return outLines.join("\n");
}
const codeHtml = highlightCode(trimAndDedent(code));
---
<div class="bg-black text-white rounded px-1"
x-data={`{
line: 0,
stdout: "",
ip: 0,
inst: ${steps},
done: false,
state: {},
}`}
>
<span class="inline-block bg-[var(--code-theme-bg-acolor)] px-2 rounded-tl rounded-tr font-mono text-sm">thp code</span>
<pre class="language-thp" style="margin: 0;" data-disabled><code set:html={codeHtml}></code></pre>
<div class="grid grid-cols-2 font-mono text-sm">
<div>
<div class="p-1 border-b border-r border-white">stdout</div>
<div class="h-24 p-1 border-r border-white">
<pre><code class="bg-black" x-text="stdout"></code></pre>
</div>
</div>
<div>
<div class="p-1 border-b border-white">state</div>
<div class="h-24 p-1">
<template x-for="(value, key) in state">
<div x-text="key + ' = ' + value"></div>
</template>
</div>
</div>
</div>
<div class="border-t border-white p-1">
<button class="font-mono px-1 rounded bg-pink-950 disabled:opacity-50 disabled:cursor-not-allowed" @click="alpineNext($data)" :disabled="done && 'true'">
Step: <span x-text="ip"></span>
</button>
<button class="font-mono px-1 rounded bg-pink-950" @click="alpineReset($data)">
Reset
</button>
</div>
</div>
<script>
type Instruction = "line" | "out" | "set";
type AlpineState = {
line: number,
stdout: string,
ip: number,
inst: Array<Array<[Instruction, string, string | undefined]>>
done: boolean,
state: {[key: string]: string},
}
/// Executes the instruction following the state of the machine.
function alpineNext(data: AlpineState) {
const len = data.inst.length;
const ip = data.ip;
data.ip += 1;
const instructions = data.inst[ip]!;
for (const instructionSet of instructions) {
const instructionArr = instructionSet;
switch (instructionArr[0]) {
case "line": {
data.line = Number(instructionArr[1]);
break;
}
case "out": {
data.stdout += String(instructionArr[1])
break;
}
case "set": {
data.state[String(instructionArr[1])] = String(instructionArr[2]);
break;
}
}
}
if (data.ip >= len) {
data.done = true;
return;
}
}
// @ts-ignore
window.alpineNext = alpineNext;
function alpineReset(data: AlpineState) {
data.line = 0;
data.stdout = "";
data.ip = 0;
data.done = false;
data.state = {};
}
// @ts-ignore
window.alpineReset = alpineReset;
</script>

View File

@ -35,5 +35,7 @@ const { title } = Astro.props;
<body class="bg-c-bg text-c-text"> <body class="bg-c-bg text-c-text">
<slot /> <slot />
<script src="//unpkg.com/alpinejs" defer></script>
</body> </body>
</html> </html>

View File

@ -6,10 +6,10 @@ import Sidebar from "../components/Sidebar.astro";
const { frontmatter, headings } = Astro.props; const { frontmatter, headings } = Astro.props;
const posts = await Astro.glob("../pages/learn/**/*.md"); const posts = await Astro.glob("../pages/learn/**/*.{md,mdx}");
// The index.md page must have a `pagesLayout` frontmatter, which declares the order of all the pages. // The index.md page must have a `pagesLayout` frontmatter, which declares the order of all the pages.
const indexSubpath = `/learn/index.md`; const indexSubpath = `/learn/index.mdx`;
const indexPage = posts.find((post) => post.file.endsWith(indexSubpath)); const indexPage = posts.find((post) => post.file.endsWith(indexSubpath));
@ -35,7 +35,7 @@ function validateEntry(entry: PageEntry, basePath: string) {
if (!entry.children) { if (!entry.children) {
// Attempt to get the page title from frontmatter // Attempt to get the page title from frontmatter
const pageData = posts.find((post) => const pageData = posts.find((post) =>
post.file.endsWith(entry.path + ".md"), post.file.endsWith(entry.path + ".md") || post.file.endsWith(entry.path + ".mdx"),
); );
if (pageData === undefined) { if (pageData === undefined) {
@ -116,6 +116,10 @@ for (const entry of pagesIndex) {
for (const e of [...code_elements]) { for (const e of [...code_elements]) {
const el = e as HTMLElement; const el = e as HTMLElement;
// Some elements with .language-thp don't want to be processed
if (e.getAttribute("data-disabled") !== null) {
continue;
}
const pre_parent = el.parentElement!; const pre_parent = el.parentElement!;
const new_div = document.createElement("div"); const new_div = document.createElement("div");
const code = el.innerText; const code = el.innerText;

View File

@ -2,6 +2,7 @@
layout: ../../../layouts/PagesLayout.astro layout: ../../../layouts/PagesLayout.astro
title: Hello world title: Hello world
--- ---
import InteractiveCode from "../../../components/InteractiveCode.astro";
# Hello, world! # Hello, world!
@ -12,3 +13,4 @@ print("Hello, world!")
``` ```
Then run `thp hello.thp` Then run `thp hello.thp`

View File

@ -2,6 +2,7 @@
layout: ../../../layouts/PagesLayout.astro layout: ../../../layouts/PagesLayout.astro
title: Try/Exceptions title: Try/Exceptions
--- ---
import InteractiveCode from "../../../components/InteractiveCode.astro";
# Try/exceptions # Try/exceptions
@ -62,21 +63,62 @@ via try expressions:
### Naked try ### Naked try
Use a naked `try` when you want to rethrow an error, if any. Use a naked `try` when you want to rethrow an error, if there is any.
```thp <InteractiveCode
// May return an Int or an Exception code={`
fun dangerous() -> Int!Exception fun dangerous() -> Int!Exception
{...} { // May throw randomly
return if Math.random() < 0.5 { 50 }
else { Exception("Unlucky") }
}
fun run() -> !Exception
{ // If \`dangerous()\` throws, the function exits with the same error.
// Otherwise, continues
val result = try dangerous()
print("The result is {result}")
}
val res1 = run() // First, without error
val res2 = run() // Then, an example with error
`}
steps={`[
[["line", 14]],
[["line", 7]],
[["line", 10]],
[["line", 1]],
[["line", 3]],
[
["line", 10],
["set", "result", 50]
],
[["line", 11]],
[
["line", 14],
["out", "The result is 50\\n"],
],
[
["line", 15],
["set", "res1", "<empty>"],
],
[["line", 7]],
[["line", 10]],
[["line", 1]],
[["line", 3]],
[["line", 4]],
[
["line", 10],
["set", "result", "Exception(\\"Unlucky\\")"]
],
[["line", 15]],
[
["line", 0,],
["set", "res2", "Exception(\\"Unlucky\\")"],
]
]`}
></InteractiveCode>
fun run() -> !Exception
{
// Use a naked `try` to rethrow if there's an error
val result = try dangerous()
// Here result is `Int`
print(result)
}
```
In the previous example: In the previous example:

View File

@ -46,8 +46,9 @@ pagesLayout:
- path: interfaces - path: interfaces
- path: anonymous - path: anonymous
- path: magic - path: magic
--- ---
import InteractiveCode from "../../components/InteractiveCode.astro";
# Welcome # Welcome
@ -57,12 +58,49 @@ THP is a new programming language that compiles to PHP.
![Accurate visual description of THP](/img/desc_thp.jpg) ![Accurate visual description of THP](/img/desc_thp.jpg)
<br>
This page discusses some of the design decitions of the language, This page discusses some of the design decitions of the language,
if you want to install THP go to the [installation guide](install) if you want to install THP go to the [installation guide](install)
If you want to learn the language, go to the learn section.
## Interactive execution
The documentation contains snippets of interactive THP code, like this:
<InteractiveCode
code={`
val x = "android"
var y = 17
fun f(Int a, Int b) {
print("hello, {a} {b}")
}
f(x, y)
`}
steps={`[
[["line", 1]],
[
["line", 2],
["set", "String x", "\\"android\\""]
],
[
["line", 8],
["set", "Int y", "17"]
],
[["line", 4]],
[["line", 5]],
[
["out", "hello, android 17"],
["line", 6]
],
[["line", 8]],
[["line", 0]]
]`}
></InteractiveCode>
Use the `Step` and `Reset` buttons to emulate the execution of a
THP program!
## Goals ## Goals

View File

@ -19,7 +19,7 @@ export default {
} }
}, },
fontFamily: { fontFamily: {
"mono": ["Iosevka", "'Iosevka Nerd Font'", "'Fira Code'", "monospace"], "mono": ["'Iosevka Fixed Web'", "Iosevka", "'Iosevka Nerd Font'", "monospace"],
"display": ["Inter", "'Josefin Sans'", "'Fugaz One'", "sans-serif"], "display": ["Inter", "'Josefin Sans'", "'Fugaz One'", "sans-serif"],
"body": ["Inter", "sans-serif"], "body": ["Inter", "sans-serif"],
}, },