Compare commits

..

No commits in common. "ea7889726cd93180bec259f01fb1fe1b8da14525" and "253e4bbb48c29fb2781c9453f86bdaa3686296fe" have entirely different histories.

12 changed files with 22 additions and 322 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,22 +1,3 @@
/* 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 {
--c-bg: #121212;
@ -68,7 +49,7 @@ body {
}
pre, code {
font-family: "Iosevka Fixed Web", "Iosevka Nerd Font", Iosevka, monospace;
font-family: Iosevka, 'Fira Code', monospace;
}
.button-effect-receiver {

View File

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

View File

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

View File

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

View File

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

View File

@ -2,7 +2,6 @@
layout: ../../../layouts/PagesLayout.astro
title: Try/Exceptions
---
import InteractiveCode from "../../../components/InteractiveCode.astro";
# Try/exceptions
@ -63,62 +62,21 @@ via try expressions:
### Naked try
Use a naked `try` when you want to rethrow an error, if there is any.
Use a naked `try` when you want to rethrow an error, if any.
<InteractiveCode
code={`
fun dangerous() -> Int!Exception
{ // May throw randomly
return if Math.random() < 0.5 { 50 }
else { Exception("Unlucky") }
}
```thp
// May return an Int or an Exception
fun dangerous() -> Int!Exception
{...}
fun run() -> !Exception
{ // If \`dangerous()\` throws, the function exits with the same error.
// Otherwise, continues
fun run() -> !Exception
{
// Use a naked `try` to rethrow if there's an error
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>
// Here result is `Int`
print(result)
}
```
In the previous example:

View File

@ -46,9 +46,8 @@ pagesLayout:
- path: interfaces
- path: anonymous
- path: magic
---
import InteractiveCode from "../../components/InteractiveCode.astro";
---
# Welcome
@ -58,49 +57,12 @@ THP is a new programming language that compiles to PHP.
![Accurate visual description of THP](/img/desc_thp.jpg)
<br>
This page discusses some of the design decitions of the language,
if you want to install THP go to the [installation guide](install)
## 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!
If you want to learn the language, go to the learn section.
## Goals

View File

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