summaryrefslogtreecommitdiff
path: root/src/toys/tabloid
diff options
context:
space:
mode:
Diffstat (limited to 'src/toys/tabloid')
-rw-r--r--src/toys/tabloid/index.html53
-rw-r--r--src/toys/tabloid/js/main.js5
-rw-r--r--src/toys/tabloid/js/playground.js120
-rw-r--r--src/toys/tabloid/js/samples.js308
-rw-r--r--src/toys/tabloid/js/tabloid.js707
5 files changed, 0 insertions, 1193 deletions
diff --git a/src/toys/tabloid/index.html b/src/toys/tabloid/index.html
deleted file mode 100644
index 61ec937..0000000
--- a/src/toys/tabloid/index.html
+++ /dev/null
@@ -1,53 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <link rel="icon" href="https://adelie.liz.coffee/img/favicon.ico" />
- <link rel="stylesheet" href="https://adelie.liz.coffee/bundle.css">
- <title>Tabloid</title>
-</head>
-<body>
- <main>
- <article>
- <h3>Tabloid</h3>
- <p>Forgotten how to write Tabloid since your schooldays? Here's <a target="_blank" href="https://github.com/thesephist/tabloid/blob/master/README.md#language-overview">a refresher</a>.</p>
-
- <div>
- <label for="program-select">Sample:</label>
- <select id="program-select" aria-label="Sample Tabloid programs"></select>
- </div>
-
- <section class="mt-md">
- <div
- id="code-editor"
- class="code-editor-container"
- aria-label="Tabloid source code"
- spellcheck="false"
- autocomplete="off"
- autocorrect="off"
- ></div>
- </section>
-
- <div class="mt-sm">
- <button id="run-button" class="primary">Run (Ctrl + Enter)</button>
- <button id="clear-button">Clear Output</button>
- </div>
-
- <section class="mt-md">
- <h4>stdout</h4>
- <pre id="stdout-content" aria-live="polite"></pre>
- </section>
-
- <section class="mt-md">
- <h4>stderr</h4>
- <pre class="text-error" id="error-content" aria-live="polite"></pre>
- </section>
- </article>
- </main>
-
- <script src="https://adelie.liz.coffee/bundle.js"></script>
- <script src="https://adelie.liz.coffee/adelie-editor.js"></script>
- <script type="module" src="./js/main.js"></script>
-</body>
-</html>
diff --git a/src/toys/tabloid/js/main.js b/src/toys/tabloid/js/main.js
deleted file mode 100644
index 1f0b5dd..0000000
--- a/src/toys/tabloid/js/main.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import { TabloidPlayground } from "./playground.js";
-
-document.addEventListener("DOMContentLoaded", () => {
- new TabloidPlayground();
-});
diff --git a/src/toys/tabloid/js/playground.js b/src/toys/tabloid/js/playground.js
deleted file mode 100644
index d6ff71a..0000000
--- a/src/toys/tabloid/js/playground.js
+++ /dev/null
@@ -1,120 +0,0 @@
-import { tokenize, Parser, Environment } from "./tabloid.js";
-import { SAMPLE_PROGRAMS } from "./samples.js";
-
-class TabloidRunner {
- run(code) {
- const stdout = [];
- const stderr = [];
-
- if (!code) {
- stderr.push("No code to execute.");
- return { stdout, stderr };
- }
-
- try {
- const tokens = tokenize(code);
- const nodes = new Parser(tokens).parse();
- const env = new Environment({
- print: (msg) => stdout.push(String(msg)),
- input: (promptText) => window.prompt(promptText) ?? ""
- });
- env.run(nodes);
- } catch (error) {
- stderr.push(error.message || error.toString());
- }
-
- return { stdout, stderr };
- }
-}
-
-export class TabloidPlayground {
- constructor() {
- this.runner = new TabloidRunner();
- this.programLookup = new Map(SAMPLE_PROGRAMS.map((program) => [program.id, program]));
- this.activeProgramId = this.defaultProgramId = SAMPLE_PROGRAMS[0].id;
-
- this.stdoutElement = document.getElementById("stdout-content");
- this.errorElement = document.getElementById("error-content");
- this.programSelect = document.getElementById("program-select");
- this.runButton = document.getElementById("run-button");
- this.clearButton = document.getElementById("clear-button");
-
- this.editor = adelieEditor.init("#code-editor", {
- language: "tabloid"
- });
-
- this.init();
- }
-
- init() {
- this.populateProgramSelect();
- this.registerEvents();
- this.loadProgram(this.activeProgramId);
- }
-
- populateProgramSelect() {
- if (!this.programSelect) {
- return;
- }
-
- this.programSelect.innerHTML = "";
- SAMPLE_PROGRAMS.forEach((program) => {
- const option = document.createElement("option");
- option.value = program.id;
- option.textContent = program.label;
- this.programSelect.appendChild(option);
- });
-
- this.programSelect.value = this.activeProgramId;
- }
-
- registerEvents() {
- this.runButton.addEventListener("click", () => this.runCode());
- this.clearButton.addEventListener("click", () => this.resetOutput());
-
- this.programSelect.addEventListener("change", () => {
- this.loadProgram(this.programSelect.value);
- });
-
- document.addEventListener("keydown", (event) => {
- if (event.ctrlKey && event.key === "Enter") {
- event.preventDefault();
- this.runCode();
- }
- });
- }
-
- setEditorContent(content) {
- const length = this.editor.state.doc.toString().length;
- this.editor.dispatch({
- changes: { from: 0, to: length, insert: content }
- });
- }
-
- loadProgram(programId = this.defaultProgramId) {
- const selectedProgram = this.programLookup.get(programId);
- if (!selectedProgram) {
- return;
- }
-
- this.activeProgramId = selectedProgram.id;
- this.setEditorContent(selectedProgram.code);
- this.resetOutput();
-
- if (this.programSelect) {
- this.programSelect.value = this.activeProgramId;
- }
- }
-
- resetOutput() {
- this.stdoutElement.textContent = "";
- this.errorElement.textContent = "";
- }
-
- runCode() {
- const code = this.editor.state.doc.toString();
- const result = this.runner.run(code);
- this.stdoutElement.textContent = result.stdout.join("\n");
- this.errorElement.textContent = result.stderr.join("\n");
- }
-}
diff --git a/src/toys/tabloid/js/samples.js b/src/toys/tabloid/js/samples.js
deleted file mode 100644
index dc7bd58..0000000
--- a/src/toys/tabloid/js/samples.js
+++ /dev/null
@@ -1,308 +0,0 @@
-const CONS_SNIPPET = [
- "DISCOVER HOW TO cons WITH a, b",
- "RUMOR HAS IT",
- " DISCOVER HOW TO retrieve WITH is_first",
- " RUMOR HAS IT",
- " WHAT IF is_first IS ACTUALLY TOTALLY RIGHT",
- " SHOCKING DEVELOPMENT a",
- " LIES!",
- " SHOCKING DEVELOPMENT b",
- " END OF STORY",
- " SHOCKING DEVELOPMENT retrieve",
- "END OF STORY",
-].join('\n');
-
-const BINARY_INORDER_TRAVERSAL_PROGRAM = [
- CONS_SNIPPET,
- '',
- "DISCOVER HOW TO in_order_traverse WITH node, is_dual_ptr",
- "RUMOR HAS IT",
- " EXPERTS CLAIM left TO BE node OF TOTALLY RIGHT",
- " EXPERTS CLAIM right TO BE node OF COMPLETELY WRONG",
- '',
- " WHAT IF is_dual_ptr IS ACTUALLY COMPLETELY WRONG",
- " RUMOR HAS IT",
- " YOU WON'T WANT TO MISS left",
- " WHAT IF right IS ACTUALLY COMPLETELY WRONG",
- " 1",
- " LIES!",
- " in_order_traverse OF right, TOTALLY RIGHT",
- " END OF STORY",
- " LIES!",
- " RUMOR HAS IT",
- " WHAT IF left IS ACTUALLY COMPLETELY WRONG",
- " 1",
- " LIES!",
- " in_order_traverse OF left, COMPLETELY WRONG",
- '',
- " WHAT IF right IS ACTUALLY COMPLETELY WRONG",
- " 1",
- " LIES!",
- " in_order_traverse OF right, COMPLETELY WRONG",
- " END OF STORY",
- "END OF STORY",
- '',
- "EXPERTS CLAIM l TO BE cons OF 1, COMPLETELY WRONG",
- "EXPERTS CLAIM r TO BE cons OF 3, COMPLETELY WRONG",
- "EXPERTS CLAIM root TO BE cons OF l, r",
- "EXPERTS CLAIM head TO BE cons OF 2, root",
- '',
- "in_order_traverse OF head, COMPLETELY WRONG",
- '',
- "PLEASE LIKE AND SUBSCRIBE",
-].join('\n');
-
-const MERGE_SORT_PROGRAM = [
- CONS_SNIPPET,
- '',
- "DISCOVER HOW TO print WITH x",
- "RUMOR HAS IT",
- " YOU WON'T WANT TO MISS x",
- "END OF STORY",
- '',
- "DISCOVER HOW TO map WITH fn, list",
- "RUMOR HAS IT",
- " WHAT IF list IS ACTUALLY COMPLETELY WRONG",
- " SHOCKING DEVELOPMENT COMPLETELY WRONG",
- " LIES!",
- " RUMOR HAS IT",
- " EXPERTS CLAIM car TO BE list OF TOTALLY RIGHT",
- " EXPERTS CLAIM cdr TO BE list OF COMPLETELY WRONG",
- " EXPERTS CLAIM new_car TO BE fn OF car",
- " EXPERTS CLAIM rest_mapped TO BE map OF fn, cdr",
- '',
- " SHOCKING DEVELOPMENT cons OF new_car, rest_mapped",
- " END OF STORY",
- "END OF STORY",
- '',
- "DISCOVER HOW TO reduce WITH fn, list, accumulator",
- "RUMOR HAS IT",
- " WHAT IF list IS ACTUALLY COMPLETELY WRONG",
- " SHOCKING DEVELOPMENT accumulator",
- " LIES!",
- " RUMOR HAS IT",
- " EXPERTS CLAIM car TO BE list OF TOTALLY RIGHT",
- " EXPERTS CLAIM cdr TO BE list OF COMPLETELY WRONG",
- " EXPERTS CLAIM added_accumulator TO BE fn OF car, accumulator",
- '',
- " SHOCKING DEVELOPMENT reduce OF fn, cdr, added_accumulator",
- " END OF STORY",
- "END OF STORY",
- '',
- "DISCOVER HOW TO str_join_reducer WITH element, accumulator",
- "RUMOR HAS IT",
- " EXPERTS CLAIM added_comma TO BE element PLUS ', '",
- " SHOCKING DEVELOPMENT added_comma PLUS accumulator",
- "END OF STORY",
- '',
- "DISCOVER HOW TO join WITH list",
- "RUMOR HAS IT",
- " SHOCKING DEVELOPMENT reduce OF str_join_reducer, list, ''",
- "END OF STORY",
- '',
- "DISCOVER HOW TO append WITH n, m",
- "RUMOR HAS IT",
- " WHAT IF n IS ACTUALLY COMPLETELY WRONG",
- " SHOCKING DEVELOPMENT m",
- " LIES!",
- " RUMOR HAS IT",
- " EXPERTS CLAIM car_n TO BE n OF TOTALLY RIGHT",
- " EXPERTS CLAIM cdr_n TO BE n OF COMPLETELY WRONG",
- " EXPERTS CLAIM appended TO BE append OF cdr_n, m",
- '',
- " SHOCKING DEVELOPMENT cons OF car_n, appended",
- " END OF STORY",
- "END OF STORY",
- '',
- "DISCOVER HOW TO reverse WITH l",
- "RUMOR HAS IT",
- " WHAT IF l IS ACTUALLY COMPLETELY WRONG",
- " SHOCKING DEVELOPMENT COMPLETELY WRONG",
- " LIES!",
- " 1",
- '',
- " EXPERTS CLAIM car TO BE l OF TOTALLY RIGHT",
- " EXPERTS CLAIM cdr TO BE l OF COMPLETELY WRONG",
- " EXPERTS CLAIM reversed_cdr TO BE reverse OF cdr",
- " EXPERTS CLAIM car_cons TO BE cons OF car, COMPLETELY WRONG",
- '',
- " SHOCKING DEVELOPMENT append OF reversed_cdr, car_cons",
- "END OF STORY",
- '',
- "DISCOVER HOW TO merge WITH x, y",
- "RUMOR HAS IT",
- " WHAT IF x IS ACTUALLY COMPLETELY WRONG",
- " SHOCKING DEVELOPMENT y",
- " LIES!",
- " 1",
- '',
- " WHAT IF y IS ACTUALLY COMPLETELY WRONG",
- " RUMOR HAS IT",
- " WHAT IF x IS ACTUALLY COMPLETELY WRONG",
- " SHOCKING DEVELOPMENT COMPLETELY WRONG",
- " LIES!",
- " SHOCKING DEVELOPMENT x",
- " END OF STORY",
- " LIES!",
- " 1",
- '',
- " EXPERTS CLAIM car_x TO BE x OF TOTALLY RIGHT",
- " EXPERTS CLAIM car_y TO BE y OF TOTALLY RIGHT",
- " EXPERTS CLAIM cdr_x TO BE x OF COMPLETELY WRONG",
- " EXPERTS CLAIM cdr_y TO BE y OF COMPLETELY WRONG",
- '',
- " EXPERTS CLAIM x_gt_y TO BE car_x BEATS car_y",
- '',
- " WHAT IF x_gt_y IS ACTUALLY TOTALLY RIGHT",
- " RUMOR HAS IT",
- " EXPERTS CLAIM rest_x_merge_y TO BE merge OF cdr_x, y",
- " SHOCKING DEVELOPMENT cons OF car_x, rest_x_merge_y",
- " END OF STORY",
- " LIES!",
- " RUMOR HAS IT",
- " EXPERTS CLAIM x_merge_rest_y TO BE merge OF x, cdr_y",
- " SHOCKING DEVELOPMENT cons OF car_y, x_merge_rest_y",
- " END OF STORY",
- "END OF STORY",
- '',
- "DISCOVER HOW TO split_middle_helper WITH slow, fast, mid_to_head",
- "RUMOR HAS IT",
- " WHAT IF fast IS ACTUALLY COMPLETELY WRONG",
- " RUMOR HAS IT",
- " EXPERTS CLAIM head_to_mid TO BE reverse OF mid_to_head",
- " SHOCKING DEVELOPMENT cons OF head_to_mid, slow",
- " END OF STORY",
- " LIES!",
- " 1",
- '',
- " EXPERTS CLAIM fast_cdr TO BE fast OF COMPLETELY WRONG",
- " EXPERTS CLAIM slow_car TO BE slow OF TOTALLY RIGHT",
- " EXPERTS CLAIM slow_cdr TO BE slow OF COMPLETELY WRONG",
- '',
- " WHAT IF fast_cdr IS ACTUALLY COMPLETELY WRONG",
- " RUMOR HAS IT",
- " EXPERTS CLAIM mid_to_head_plus_slow TO BE cons OF slow_car, mid_to_head",
- " EXPERTS CLAIM head_to_mid_plus_slow TO BE reverse OF mid_to_head_plus_slow",
- '',
- " SHOCKING DEVELOPMENT cons OF head_to_mid_plus_slow, slow_cdr",
- " END OF STORY",
- " LIES!",
- " 1",
- '',
- " EXPERTS CLAIM fast_cddr TO BE fast_cdr OF COMPLETELY WRONG",
- " EXPERTS CLAIM slow_car_mid_to_head TO BE cons OF slow_car, mid_to_head",
- '',
- " SHOCKING DEVELOPMENT split_middle_helper OF slow_cdr, fast_cddr, slow_car_mid_to_head",
- "END OF STORY",
- '',
- "DISCOVER HOW TO split_middle WITH start",
- "RUMOR HAS IT",
- " EXPERTS CLAIM cdr TO BE start OF COMPLETELY WRONG",
- '',
- " SHOCKING DEVELOPMENT split_middle_helper OF start, cdr, COMPLETELY WRONG",
- "END OF STORY",
- '',
- "DISCOVER HOW TO sort WITH root",
- "RUMOR HAS IT",
- " WHAT IF root IS ACTUALLY COMPLETELY WRONG",
- " SHOCKING DEVELOPMENT root",
- " LIES!",
- " 1",
- '',
- " EXPERTS CLAIM root_cdr TO BE root OF COMPLETELY WRONG",
- " WHAT IF root_cdr IS ACTUALLY COMPLETELY WRONG",
- " SHOCKING DEVELOPMENT root",
- " LIES!",
- " 1",
- '',
- " EXPERTS CLAIM left_right_cons_cell TO BE split_middle OF root",
- " EXPERTS CLAIM left TO BE left_right_cons_cell OF TOTALLY RIGHT",
- " EXPERTS CLAIM right TO BE left_right_cons_cell OF COMPLETELY WRONG",
- " EXPERTS CLAIM left_s TO BE sort OF left",
- " EXPERTS CLAIM right_s TO BE sort OF right",
- '',
- " SHOCKING DEVELOPMENT merge OF left_s, right_s",
- "END OF STORY",
- '',
- "EXPERTS CLAIM a_3 TO BE cons OF 3, COMPLETELY WRONG",
- "EXPERTS CLAIM a_2 TO BE cons OF 1, a_3",
- "EXPERTS CLAIM a_1 TO BE cons OF -2, a_2",
- "EXPERTS CLAIM a_0 TO BE cons OF 5, a_1",
- "EXPERTS CLAIM b_3 TO BE cons OF 2, a_0",
- "EXPERTS CLAIM b_2 TO BE cons OF 7, b_3",
- "EXPERTS CLAIM b_1 TO BE cons OF 3, b_2",
- "EXPERTS CLAIM b_0 TO BE cons OF -1, b_1",
- '',
- "EXPERTS CLAIM b_sorted TO BE sort OF b_0",
- '',
- "YOU WON'T WANT TO MISS join OF b_sorted",
- '',
- "PLEASE LIKE AND SUBSCRIBE",
-].join('\n');
-
-export const SAMPLE_PROGRAMS = [
- {
- id: 'fibonacci',
- label: 'Fibonacci',
- code: [
- "DISCOVER HOW TO fibonacci WITH a, b, n",
- "RUMOR HAS IT",
- " WHAT IF n SMALLER THAN 1",
- " SHOCKING DEVELOPMENT b",
- " LIES! RUMOR HAS IT",
- " YOU WON'T WANT TO MISS b",
- " SHOCKING DEVELOPMENT",
- " fibonacci OF b, a PLUS b, n MINUS 1",
- " END OF STORY",
- "END OF STORY",
- "",
- "EXPERTS CLAIM limit TO BE 10",
- "",
- "fibonacci OF 0, 1, limit",
- "",
- "PLEASE LIKE AND SUBSCRIBE",
- ].join('\n'),
- },
- {
- id: 'countdown',
- label: 'Countdown',
- code: [
- "EXPERTS CLAIM start TO BE 4",
- "YOU WON'T WANT TO MISS 't minus 5...'",
- "",
- "DISCOVER HOW TO countdown WITH current",
- "RUMOR HAS IT",
- " WHAT IF current SMALLER THAN 1 RUMOR HAS IT",
- " SHOCKING DEVELOPMENT 'Blastoff!'",
- " END OF STORY",
- " LIES! RUMOR HAS IT",
- " YOU WON'T WANT TO MISS current",
- " SHOCKING DEVELOPMENT countdown OF current MINUS 1",
- " END OF STORY",
- "END OF STORY",
- "",
- "YOU WON'T WANT TO MISS countdown OF start",
- "",
- "PLEASE LIKE AND SUBSCRIBE",
- ].join('\n'),
- },
- {
- id: 'hello',
- label: 'Hello!',
- code: [
- "YOU WON'T WANT TO MISS ('Hello, ' PLUS (LATEST NEWS ON 'What is your name?')) PLUS '!'",
- "",
- "PLEASE LIKE AND SUBSCRIBE",
- ].join('\n'),
- },
- {
- id: 'binary-inorder-traversal',
- label: 'Binary tree in-order traversal',
- code: BINARY_INORDER_TRAVERSAL_PROGRAM,
- },
- {
- id: 'merge-sort',
- label: 'Merge sort linked list',
- code: MERGE_SORT_PROGRAM,
- },
-];
diff --git a/src/toys/tabloid/js/tabloid.js b/src/toys/tabloid/js/tabloid.js
deleted file mode 100644
index 468ee9a..0000000
--- a/src/toys/tabloid/js/tabloid.js
+++ /dev/null
@@ -1,707 +0,0 @@
-/* Tabloid: the clickbait headline programming language */
-
-/* tokenizer */
-
-/**
- * Reads in char or word chunks
- */
-class Reader {
- constructor(str, base = '') {
- this.base = base;
- this.i = 0;
- this.str = str;
- }
- peek() {
- return this.str[this.i];
- }
- next() {
- return this.str[this.i++];
- }
- hasNext() {
- return this.str[this.i] !== undefined;
- }
- backstep() {
- this.i--;
- }
- readUntil(pred) {
- let result = this.base.slice();
- while (this.hasNext() && !pred(this.peek())) {
- result += this.next();
- }
- return result;
- }
- dropWhitespace() {
- this.readUntil(c => !!c.trim());
- }
- expect(tok) {
- const next = this.next();
- if (next !== tok) {
- throw new Error(`Parsing error: expected ${tok}, got ${next}`);
- }
- }
-}
-
-/**
- * Split into words for easier tokenization
- * with keywords.
- */
-class Wordifier {
- constructor(str) {
- this.reader = new Reader(str.trim());
- this.tokens = [];
- }
- wordify() {
- if (this.tokens.length) return this.tokens;
-
- while (this.reader.hasNext()) {
- const next = this.reader.next();
- switch (next) {
- case '(': {
- this.tokens.push('(');
- break;
- }
- case ')': {
- this.tokens.push(')');
- break;
- }
- case ',': {
- this.tokens.push(',');
- break;
- }
- case '"':
- case "'": {
- this.wordifyString(next);
- break;
- }
- default: {
- // read until WS
- this.reader.backstep();
- this.tokens.push(this.reader.readUntil(c => {
- return !c.trim() || ['(', ')', ','].includes(c)
- }));
- }
- }
- this.reader.dropWhitespace();
- }
- return this.tokens;
- }
- wordifyString(endChar) {
- let acc = '';
- acc += this.reader.readUntil(c => c == endChar);
- while (acc.endsWith('\\') || !this.reader.hasNext()) {
- acc = acc.substr(0, acc.length - 1);
- this.reader.next(); // endChar
- acc += endChar + this.reader.readUntil(c => c == endChar);
- }
- this.reader.next(); // throw away closing char
- this.tokens.push('"' + acc);
- }
-}
-
-const T = {
- LParen: Symbol('LParen'),
- RParen: Symbol('RParen'),
- Comma: Symbol('Comma'),
- DiscoverHowTo: Symbol('DiscoverHowTo'),
- With: Symbol('With'),
- Of: Symbol('Of'),
- RumorHasIt: Symbol('RumorHasIt'),
- WhatIf: Symbol('WhatIf'),
- LiesBang: Symbol('LiesBang'),
- EndOfStory: Symbol('EndOfStory'),
- ExpertsClaim: Symbol('ExpertsClaim'),
- ToBe: Symbol('ToBe'),
- YouWontWantToMiss: Symbol('YouWontWantToMiss'),
- LatestNewsOn: Symbol('LatestNewsOn'),
- TotallyRight: Symbol('TotallyRight'),
- CompletelyWrong: Symbol('CompletelyWrong'),
- IsActually: Symbol('IsActually'),
- And: Symbol('And'),
- Or: Symbol('Or'),
- Plus: Symbol('Plus'),
- Minus: Symbol('Minus'),
- Times: Symbol('Times'),
- DividedBy: Symbol('DividedBy'),
- Modulo: Symbol('Modulo'),
- Beats: Symbol('Beats'), // >
- SmallerThan: Symbol('SmallerThan'), // <
- ShockingDevelopment: Symbol('ShockingDevelopment'),
- PleaseLikeAndSubscribe: Symbol('PleaseLikeAndSubscribe'),
-}
-
-const BINARY_OPS = [
- T.IsActually,
- T.And,
- T.Or,
- T.Plus,
- T.Minus,
- T.Times,
- T.DividedBy,
- T.Modulo,
- T.Beats,
- T.SmallerThan,
-];
-
-export function tokenize(prog) {
- const reader = new Reader(new Wordifier(prog).wordify(), []);
- const tokens = [];
-
- while (reader.hasNext()) {
- const next = reader.next();
- switch (next) {
- case 'DISCOVER': {
- reader.expect('HOW');
- reader.expect('TO');
- tokens.push(T.DiscoverHowTo);
- break;
- }
- case 'WITH': {
- tokens.push(T.With);
- break;
- }
- case 'OF': {
- tokens.push(T.Of);
- break;
- }
- case 'RUMOR': {
- reader.expect('HAS');
- reader.expect('IT');
- tokens.push(T.RumorHasIt);
- break;
- }
- case 'WHAT': {
- reader.expect('IF');
- tokens.push(T.WhatIf);
- break;
- }
- case 'LIES!': {
- tokens.push(T.LiesBang);
- break;
- }
- case 'END': {
- reader.expect('OF');
- reader.expect('STORY');
- tokens.push(T.EndOfStory);
- break;
- }
- case 'EXPERTS': {
- reader.expect('CLAIM');
- tokens.push(T.ExpertsClaim);
- break;
- }
- case 'TO': {
- reader.expect('BE');
- tokens.push(T.ToBe);
- break;
- }
- case 'YOU': {
- reader.expect('WON\'T');
- reader.expect('WANT');
- reader.expect('TO');
- reader.expect('MISS');
- tokens.push(T.YouWontWantToMiss);
- break;
- }
- case 'LATEST': {
- reader.expect('NEWS');
- reader.expect('ON');
- tokens.push(T.LatestNewsOn);
- break;
- }
- case 'IS': {
- reader.expect('ACTUALLY');
- tokens.push(T.IsActually);
- break;
- }
- case 'AND': {
- tokens.push(T.And);
- break;
- }
- case 'OR': {
- tokens.push(T.Or);
- break;
- }
- case 'PLUS': {
- tokens.push(T.Plus);
- break;
- }
- case 'MINUS': {
- tokens.push(T.Minus);
- break;
- }
- case 'TIMES': {
- tokens.push(T.Times);
- break;
- }
- case 'DIVIDED': {
- reader.expect('BY');
- tokens.push(T.DividedBy);
- break;
- }
- case 'MODULO': {
- tokens.push(T.Modulo);
- break;
- }
- case 'BEATS': {
- tokens.push(T.Beats);
- break;
- }
- case 'SMALLER': {
- reader.expect('THAN');
- tokens.push(T.SmallerThan);
- break;
- }
- case 'SHOCKING': {
- reader.expect('DEVELOPMENT');
- tokens.push(T.ShockingDevelopment);
- break;
- }
- case 'PLEASE': {
- reader.expect('LIKE');
- reader.expect('AND');
- reader.expect('SUBSCRIBE');
- tokens.push(T.PleaseLikeAndSubscribe);
- break;
- }
- case 'TOTALLY': {
- reader.expect('RIGHT');
- tokens.push(T.TotallyRight);
- break;
- }
- case 'COMPLETELY': {
- reader.expect('WRONG');
- tokens.push(T.CompletelyWrong);
- break;
- }
- case '(': {
- tokens.push(T.LParen);
- break;
- }
- case ')': {
- tokens.push(T.RParen);
- break;
- }
- case ',': {
- tokens.push(T.Comma);
- break;
- }
- default: {
- if (!isNaN(parseFloat(next))) {
- // number literal
- tokens.push(parseFloat(next));
- } else {
- // string or varname
- tokens.push(next);
- }
- }
- }
- }
- return tokens;
-}
-
-/* parser */
-
-const N = {
- NumberLiteral: Symbol('NumberLiteral'),
- StringLiteral: Symbol('StringLiteral'),
- BoolLiteral: Symbol('BoolLiteral'),
- FnDecl: Symbol('FnDecl'),
- FnCall: Symbol('FnCall'),
- Ident: Symbol('Ident'),
- Assignment: Symbol('Assignment'),
- BinaryOp: Symbol('BinaryOp'),
- IfExpr: Symbol('IfExpr'),
- ExprGroup: Symbol('ExprGroup'),
- ReturnExpr: Symbol('ReturnExpr'),
- ProgEndExpr: Symbol('ProgEndExpr'),
- PrintExpr: Symbol('PrintExpr'),
- InputExpr: Symbol('InputExpr'),
-}
-
-export class Parser {
- constructor(tokens) {
- this.tokens = new Reader(tokens, []);
- }
- /**
- * Atom
- * Ident
- * NumberLiteral
- * StringLiteral
- * BoolLiteral
- * FnCall
- * FnDecl
- * ExprGroup
- *
- * Expression:
- * (begins with atom)
- * BinaryOp
- * Atom
- * (begins with keyword)
- * IfExpr
- * Assignment
- * ReturnExpr
- * ProgEndExpr
- * PrintExpr
- * InputExpr
- *
- */
- parse() {
- const nodes = [];
- while (this.tokens.hasNext()) {
- nodes.push(this.expr());
- }
-
- if (nodes[nodes.length - 1].type !== N.ProgEndExpr) {
- throw new Error('Parsing error: A Tabloid program MUST end with PLEASE LIKE AND SUBSCRIBE');
- }
-
- return nodes;
- }
- expectIdentString() {
- const ident = this.tokens.next();
- if (typeof ident === 'string' && !ident.startsWith('"')) {
- return ident;
- }
- throw new Error(`Parsing error: expected identifier, got ${ident.toString()}`);
- }
- atom() {
- const next = this.tokens.next();
- if (typeof next === 'number') {
- return {
- type: N.NumberLiteral,
- val: next,
- }
- } else if (typeof next === 'string') {
- if (next.startsWith('"')) {
- return {
- type: N.StringLiteral,
- val: next.substr(1),
- }
- }
- const ident = {
- type: N.Ident,
- val: next,
- }
- if (this.tokens.peek() === T.Of) {
- return this.fnCall(ident);
- }
- return ident;
- } else if (next === T.TotallyRight) {
- return {
- type: N.BoolLiteral,
- val: true,
- }
- } else if (next === T.CompletelyWrong) {
- return {
- type: N.BoolLiteral,
- val: false,
- }
- } else if (next === T.DiscoverHowTo) {
- // fn literal
- const fnName = this.tokens.next();
- if (this.tokens.peek(T.With)) {
- this.tokens.next(); // with
- // with args
- const args = [this.expectIdentString()];
- while (this.tokens.peek() === T.Comma) {
- this.tokens.next(); // comma
- args.push(this.expectIdentString());
- }
- return {
- type: N.FnDecl,
- name: fnName,
- args: args,
- body: this.expr(),
- }
- } else {
- return {
- type: N.FnDecl,
- name: fnName,
- args: [],
- body: this.expr(),
- }
- }
- } else if (next === T.RumorHasIt) {
- // block
- const exprs = [];
- while (this.tokens.hasNext() && this.tokens.peek() !== T.EndOfStory) {
- exprs.push(this.expr());
- }
- this.tokens.expect(T.EndOfStory);
- return {
- type: N.ExprGroup,
- exprs: exprs,
- };
- } else if (next === T.LParen) {
- // block, but guarded by parens, for binary exprs
- const exprs = [];
- while (this.tokens.hasNext() && this.tokens.peek() !== T.RParen) {
- exprs.push(this.expr());
- }
- this.tokens.expect(T.RParen);
- return {
- type: N.ExprGroup,
- exprs: exprs,
- };
- }
-
- throw new Error(`Parsing error: expected ident, literal, or block, got ${
- next.toString()
- } before ${this.tokens.peek().toString()}`);
- }
- expr() {
- const next = this.tokens.next();
- if (next === T.WhatIf) {
- // if expr
- const cond = this.expr();
- const ifBody = this.expr();
-
- let elseBody = null;
- if (this.tokens.peek() == T.LiesBang) {
- this.tokens.next(); // LiesBang
- elseBody = this.expr();
- }
- return {
- type: N.IfExpr,
- cond: cond,
- ifBody: ifBody,
- elseBody: elseBody,
- }
- } else if (next === T.ExpertsClaim) {
- // assignment
- const name = this.expectIdentString();
- this.tokens.expect(T.ToBe);
- const val = this.expr();
- return {
- type: N.Assignment,
- name,
- val,
- }
- } else if (next === T.ShockingDevelopment) {
- // return
- return {
- type: N.ReturnExpr,
- val: this.expr(),
- }
- } else if (next === T.PleaseLikeAndSubscribe) {
- // prog end
- return {
- type: N.ProgEndExpr,
- }
- } else if (next === T.YouWontWantToMiss) {
- // print expr
- return {
- type: N.PrintExpr,
- val: this.expr(),
- }
- } else if (next === T.LatestNewsOn) {
- // input expr
- return {
- type: N.InputExpr,
- val: this.expr(),
- }
- }
-
- this.tokens.backstep();
- const atom = this.atom();
- if (BINARY_OPS.includes(this.tokens.peek())) {
- // infix binary ops
- const left = atom;
- const op = this.tokens.next();
- const right = this.atom();
- return {
- type: N.BinaryOp,
- op,
- left,
- right,
- }
- }
-
- return atom;
- }
- fnCall(fnNode) {
- this.tokens.expect(T.Of);
- const args = [this.expr()];
- while (this.tokens.peek() === T.Comma) {
- this.tokens.next(); // comma
- args.push(this.expr());
- }
- return {
- type: N.FnCall,
- fn: fnNode,
- args: args,
- }
- }
-}
-
-/* executor (tree walk) */
-
-/**
- * Abused (slightly) to easily return values upstack
- */
-class ReturnError {
- constructor(value) {
- this.value = value;
- }
- unwrap() {
- return this.value;
- }
-}
-
-export class Environment {
- constructor(runtime) {
- /**
- * Runtime contains the following functions:
- * - print(s)
- * - input(s)
- */
- this.runtime = runtime;
- this.scopes = [{}]; // begin with global scope
- }
- run(nodes) {
- let rv;
- for (const node of nodes) {
- rv = this.eval(node);
- }
- return rv;
- }
- eval(node) {
- const scope = this.scopes[this.scopes.length - 1];
-
- switch (node.type) {
- case N.NumberLiteral:
- case N.StringLiteral:
- case N.BoolLiteral:
- return node.val;
- case N.FnDecl: {
- const fnValue = {
- node: node,
- closure: { ...scope }
- };
- scope[node.name] = fnValue;
- return fnValue;
- }
- case N.FnCall: {
- const fn = this.eval(node.fn);
- const args = node.args.map(arg => this.eval(arg));
-
- const calleeScope = {};
- fn.node.args.forEach((argName, i) => {
- calleeScope[argName] = args[i];
- });
-
- this.scopes.push(fn.closure);
- this.scopes.push(calleeScope);
- let rv;
- try {
- this.eval(fn.node.body);
- } catch (maybeReturnErr) {
- if (maybeReturnErr instanceof ReturnError) {
- rv = maybeReturnErr.unwrap();
- } else {
- throw maybeReturnErr;
- }
- }
- this.scopes.pop();
- this.scopes.pop();
-
- return rv;
- }
- case N.Ident: {
- let i = this.scopes.length - 1;
- while (i >= 0) {
- if (node.val in this.scopes[i]) {
- return this.scopes[i][node.val];
- }
- i --;
- }
- throw new Error(`Runtime error: Undefined variable "${node.val}"`);
- }
- case N.Assignment: {
- scope[node.name] = this.eval(node.val);
- return scope[node.name];
- }
- case N.BinaryOp: {
- const left = this.eval(node.left);
- const right = this.eval(node.right);
- switch (node.op) {
- case T.IsActually:
- return left === right;
- case T.And:
- return left && right;
- case T.Or:
- return left || right;
- case T.Plus:
- return left + right;
- case T.Minus:
- return left - right;
- case T.Times:
- return left * right;
- case T.DividedBy:
- return left / right;
- case T.Modulo:
- return left % right;
- case T.Beats:
- return left > right;
- case T.SmallerThan:
- return left < right;
- default:
- throw new Error(`Runtime error: Unknown binary op ${node.op.toString()}`);
- }
- }
- case N.IfExpr: {
- if (this.eval(node.cond)) {
- return this.eval(node.ifBody);
- }
- if (node.elseBody != null) {
- return this.eval(node.elseBody);
- }
- }
- case N.ExprGroup: {
- if (!node.exprs.length) {
- throw new Error('Runtime error: Empty expression group with no expressions');
- }
-
- let rv;
- for (const expr of node.exprs) {
- rv = this.eval(expr);
- }
- return rv;
- }
- case N.ReturnExpr: {
- const rv = this.eval(node.val);
- throw new ReturnError(rv);
- }
- case N.ProgEndExpr: {
- // do nothing
- break;
- }
- case N.PrintExpr: {
- let val = this.eval(node.val);
- // shim for boolean to-string's
- if (val === true) {
- val = 'TOTALLY RIGHT';
- } else if (val === false) {
- val = 'COMPLETELY WRONG';
- }
- this.runtime.print(val);
- return val;
- }
- case N.InputExpr: {
- let val = this.eval(node.val);
- // shim for boolean to-string's
- if (val === true) {
- val = 'TOTALLY RIGHT';
- } else if (val === false) {
- val = 'COMPLETELY WRONG';
- }
- return this.runtime.input(val);
- }
- default:
- console.log(JSON.stringify(node, null, 2));
- throw new Error(`Runtime error: Unknown AST Node of type ${
- node.type.toString()
- }:\n${JSON.stringify(node, null, 2)}`);
- }
- }
-}