Compare commits

..

No commits in common. "957921b793d34f3d6b1ee511bbc01223f6e395d0" and "c7b429105d06cae90a4dc595b105e28dfe8d9021" have entirely different histories.

6 changed files with 133 additions and 301 deletions

View File

@ -1,7 +1,5 @@
--- ---
import { lex } from "../lexer/lexer"; import { lex } from "../lexer/lexer";
import type { Instruction } from "../thp_machine/machine_parser";
import { parse_str } from "../thp_machine/machine_parser";
const {code, steps} = Astro.props; const {code, steps} = Astro.props;
/** /**
@ -99,15 +97,6 @@ function highlightCode(lines: Array<string>): string {
} }
const codeHtml = highlightCode(trimAndDedent(code)); const codeHtml = highlightCode(trimAndDedent(code));
let instructionSet: Array<Array<Instruction>>;
try {
instructionSet = parse_str(steps);
} catch (e) {
console.error(Astro.url);
throw e;
}
const serialized_inst = JSON.stringify(instructionSet);
--- ---
<div class="bg-black text-white rounded px-1" <div class="bg-black text-white rounded px-1"
@ -115,7 +104,7 @@ const serialized_inst = JSON.stringify(instructionSet);
line: 0, line: 0,
stdout: "", stdout: "",
ip: 0, ip: 0,
inst: ${serialized_inst}, inst: ${steps},
done: false, done: false,
state: {}, state: {},
}`} }`}
@ -131,7 +120,7 @@ const serialized_inst = JSON.stringify(instructionSet);
</div> </div>
<div> <div>
<div class="p-1 border-b border-white">state</div> <div class="p-1 border-b border-white">state</div>
<div class="h-24 p-1 overflow-y-scroll"> <div class="h-24 p-1">
<template x-for="(value, key) in state"> <template x-for="(value, key) in state">
<div x-text="key.replaceAll(' ', '&nbsp;') + ' = ' + value"></div> <div x-text="key.replaceAll(' ', '&nbsp;') + ' = ' + value"></div>
</template> </template>
@ -139,23 +128,22 @@ const serialized_inst = JSON.stringify(instructionSet);
</div> </div>
</div> </div>
<div class="border-t border-white p-1"> <div class="border-t border-white p-1">
<button class="font-mono px-1 rounded bg-pink-200 dark:bg-pink-950 text-black dark:text-white disabled:opacity-50 disabled:cursor-not-allowed" @click="alpineNext($data)" :disabled="done && 'true'"> <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> Step: <span x-text="ip"></span>
</button> </button>
<button class="font-mono px-1 rounded bg-pink-200 dark:bg-pink-950 text-black dark:text-white" @click="alpineReset($data)"> <button class="font-mono px-1 rounded bg-pink-950" @click="alpineReset($data)">
Reset Reset
</button> </button>
</div> </div>
</div> </div>
<script> <script>
import { InstructionType, type Instruction } from "../thp_machine/machine_parser"; type Instruction = "line" | "out" | "set" | "unset";
type AlpineState = { type AlpineState = {
line: number, line: number,
stdout: string, stdout: string,
ip: number, ip: number,
inst: Array<Array<Instruction>> inst: Array<Array<[Instruction, string, string | undefined]>>
done: boolean, done: boolean,
state: {[key: string]: string}, state: {[key: string]: string},
} }
@ -168,25 +156,22 @@ const serialized_inst = JSON.stringify(instructionSet);
const instructions = data.inst[ip]!; const instructions = data.inst[ip]!;
for (const instructionSet of instructions) { for (const instructionSet of instructions) {
const i = instructionSet; const instructionArr = instructionSet;
switch (instructionArr[0]) {
switch (i.t) { case "line": {
case InstructionType.Line: { data.line = Number(instructionArr[1]);
data.line = Number(i.v0);
break; break;
} }
case InstructionType.Out: { case "out": {
data.stdout += i.v0 data.stdout += String(instructionArr[1])
break; break;
} }
case InstructionType.Set: { case "set": {
const i_key = i.v0.slice(1, -1); data.state[String(instructionArr[1])] = String(instructionArr[2]);
const i_value = i.v1!.slice(1, -1);
data.state[i_key] = i_value;
break; break;
} }
case InstructionType.Unset: { case "unset": {
delete data.state[i.v0]; delete data.state[String(instructionArr[1])];
break; break;
} }
} }

View File

@ -10,11 +10,11 @@ import { is_identifier_char } from "./utils";
* @param is_datatype whether the identifier is a datatype * @param is_datatype whether the identifier is a datatype
*/ */
export function scan_identifier(input: string, starting_position: number, is_datatype = false): [Token, number] { export function scan_identifier(input: string, starting_position: number, is_datatype = false): [Token, number] {
let value = input[starting_position]!; let value = input[starting_position];
let pos = starting_position + 1; let pos = starting_position + 1;
while (pos < input.length) { while (pos < input.length) {
const c = input[pos]!; const c = input[pos];
if (is_identifier_char(c)) { if (is_identifier_char(c)) {
pos += 1; pos += 1;

View File

@ -34,7 +34,7 @@ export function lex(code: string): Array<Token> {
let current_default_token = ""; let current_default_token = "";
while (current_pos < code_len) { while (current_pos < code_len) {
const c = code[current_pos]!; const c = code[current_pos];
// try to scan a number // try to scan a number
if (is_digit(c)) { if (is_digit(c)) {

View File

@ -83,36 +83,101 @@ Use a naked `try` when you want to rethrow an error, if there is any.
val res1 = run() // First, without error val res1 = run() // First, without error
val res2 = run() // Then, an example with error val res2 = run() // Then, an example with error
`} `}
steps={` steps={`[
step { line 14 } [["line", 14]],
step { line 7 } [["line", 7]],
step { [
line 8 ["line", 8],
set "= = =" "run() = = = =" ["set", "= = =", "run() = = = ="]
} ],
step { [
line 10 ["line", 10],
set " dangerous()" "..." ["set", " dangerous()", "..."],
} ],
step {line 1} [["line", 1]],
step { [
line 2 ["line", 2],
set " = = =" "dangerous() = = = =" ["set", " = = =", "dangerous() = = = ="]
} ],
step {line 3} [["line", 3]],
step {set " Math.random()" "0.2"} [
step { ["set", " Math.random()", "0.2"]
unset " Math.random()" ],
set " return" "Int 50" [
} ["unset", " Math.random()"],
step {line 5} ["set", " return", "Int 50"]
step { ],
line 10 [["line", 5]],
unset " return" [
unset " = = =" ["line", 10],
set " dangerous()" "Int 50" ["unset", " return"],
} ["unset", " = = ="],
`} ["set", " dangerous()", "Int 50"]
],
[
["unset", " dangerous()"],
["set", " Int result", "Int 50"]
],
[["line", 11]],
[
["line", 12],
["out", "The result is 50\\n"],
],
[
["line", 14],
["unset", " Int result"],
["unset", "= = ="],
],
[
["line", 15],
["set", "!Exception res1", "<empty>"],
],
[["line", 7]],
[
["line", 8],
["set", "= = =", "run() = = = ="]
],
[
["line", 10],
["set", " dangerous()", "..."],
],
[["line", 1]],
[
["line", 2],
["set", " = = =", "dangerous() = = = ="],
],
[["line", 3]],
[
["set", " Math.random()", "0.9"],
],
[
["line", "4"],
["unset", " Math.random()"],
["set", " return", "Exception(\\"Unlucky\\")"],
],
[["line", 5]],
[
["line", 10],
["unset", " return"],
["unset", " = = ="],
["set", " dangerous()", "Exception \\"Unlucky\\""],
],
[
["line", 12],
["unset", " dangerous()"],
["set", " return", "Exception(\\"Unlucky\\")"],
],
[
["line", 15],
["unset", " return"],
["unset", "= = ="],
],
[
["line", 16],
["set", "!Exception res2", "Exception(\\"Unlucky\\")"],
],
]`}
></InteractiveCode> ></InteractiveCode>

View File

@ -78,25 +78,25 @@ The documentation contains snippets of interactive THP code, like this:
f(x, y) f(x, y)
`} `}
steps={` steps={`[
step {line 1} [["line", 1]],
step { [
line 2 ["line", 2],
set "String x" "\\"android\\"" ["set", "String x", "\\"android\\""]
} ],
step { [
line 8 ["line", 8],
set "Int y" "17" ["set", "Int y", "17"]
} ],
step {line 4} [["line", 4]],
step {line 5} [["line", 5]],
step { [
out "hello, android 17" ["out", "hello, android 17"],
line 6 ["line", 6]
} ],
step {line 8} [["line", 8]],
step {line 0} [["line", 0]]
`} ]`}
></InteractiveCode> ></InteractiveCode>
Use the `Step` and `Reset` buttons to emulate the execution of a Use the `Step` and `Reset` buttons to emulate the execution of a

View File

@ -1,218 +0,0 @@
/*
step {
line 1
set "a" "b"
unset "a"
}
*/
import { scan_number } from "../lexer/number_lexer";
import { scan_string } from "../lexer/string_lexer";
import { is_digit, is_lowercase, is_uppercase } from "../lexer/utils";
enum TokenType {
Step,
Line,
Set,
Out,
Number,
String,
Unset,
BraceOpen,
BraceClose,
};
type Token = [TokenType, string | undefined];
// Creates a stream of tokens
function lex(input: string): Array<Token> {
const characters = input.split("");
const characters_len = characters.length;
let next_p = 0;
const tokens: Array<Token> = [];
while (next_p < characters_len)
{
const c = characters[next_p]!;
// word
if (is_lowercase(c) || is_uppercase(c))
{
const [token, next] = lex_word(characters, next_p);
tokens.push(token);
next_p = next;
}
// number
else if (is_digit(c))
{
const [token, next] = scan_number(input, next_p);
tokens.push([TokenType.Number, token.v]);
next_p = next;
}
// string
else if (c === "\"")
{
const [token, next] = scan_string(input, next_p);
tokens.push([TokenType.String, token.v]);
next_p = next;
}
else if (c === "{")
{
tokens.push([TokenType.BraceOpen, undefined]);
next_p += 1;
}
else if (c === "}")
{
tokens.push([TokenType.BraceClose, undefined]);
next_p += 1;
}
else if (c === " " || c === "\n")
{
next_p += 1;
}
else
{
throw new Error(`Invalid character: \`${c}\``);
}
}
return tokens;
}
function lex_word(input: Array<string>, pos: number): [Token, number] {
let next_p = pos;
let value = "";
let c = input[next_p];
while (c !== undefined && (is_lowercase(c) || is_uppercase(c) || is_digit(c) || c === "_"))
{
value += c;
next_p += 1;
c = input[next_p];
}
let token_type;
if (value === "step") { token_type = TokenType.Step; }
else if (value === "line") { token_type = TokenType.Line; }
else if (value === "set") { token_type = TokenType.Set; }
else if (value === "unset"){ token_type = TokenType.Unset; }
else if (value === "out") { token_type = TokenType.Out; }
else
{
throw new Error(`Invalid word: ${value}`);
}
return [[token_type, value], next_p]
}
export enum InstructionType {
Line,
Set,
Unset,
Out,
}
export type Instruction = {
t: InstructionType,
v0: string,
v1?: string,
}
export function parse_str(input: string): Array<Array<Instruction>> {
return parse(lex(input));
}
// Parses the tokens into a instruction set
function parse(tokens: Array<Token>): Array<Array<Instruction>> {
let pos = 0;
let max = tokens.length;
const ret = [];
while (pos < max) {
const [steps, next_pos] = parse_step(tokens, pos);
pos = next_pos;
ret.push(steps);
}
return ret;
}
function parse_step(tokens: Array<Token>, _pos: number): [Array<Instruction>, number] {
let pos = _pos;
expect(tokens, pos, TokenType.Step, "expected step");
pos += 1;
expect(tokens, pos, TokenType.BraceOpen, "expected opening brace");
pos += 1;
const instructions = [];
while (true) {
const [inst, next] = parse_instruction(tokens, pos);
if (inst === null) {
break;
}
instructions.push(inst);
pos = next;
}
expect(tokens, pos, TokenType.BraceClose, "expected closing brace");
pos += 1
return [instructions, pos];
}
function parse_instruction(tokens: Array<Token>, _pos: number): [Instruction|null, number] {
let pos = _pos;
let instruction_type = tokens[pos]![0];
if (instruction_type === TokenType.Line) {
pos += 1;
expect(tokens, pos, TokenType.Number, "expected a number after the `line` instruction");
return [{
t: InstructionType.Line,
v0: tokens[pos]![1]!,
}, pos + 1]
}
else if (instruction_type === TokenType.Set) {
pos += 1;
expect(tokens, pos, TokenType.String, "expected a string after the `set` instruction");
pos += 1;
expect(tokens, pos, TokenType.String, "expected a second string after the `set` instruction");
return [{
t: InstructionType.Set,
v0: tokens[pos - 1]![1]!,
v1: tokens[pos]![1]!,
}, pos + 1]
}
else if (instruction_type === TokenType.Unset) {
expect(tokens, pos + 1, TokenType.String, "expected a a string after the `unset` instruction");
return [{
t: InstructionType.Unset,
v0: tokens[pos + 1]![1]!,
}, pos + 2]
}
else if (instruction_type === TokenType.Out) {
expect(tokens, pos + 1, TokenType.String, "expected a a string after the `unset` instruction");
return [{
t: InstructionType.Out,
v0: tokens[pos + 1]![1]!,
}, pos + 2]
}
return [null, pos];
}
function expect(t: Array<Token>, pos: number, type: TokenType, err: string) {
const [t_type] = t[pos]!;
if (t_type !== type) {
console.error("`" + String(t[pos]) + "`");
throw new Error(err + " , got " + t[pos]);
}
}