summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--static/js/lang.js1320
-rw-r--r--static/js/main.js250
2 files changed, 822 insertions, 748 deletions
diff --git a/static/js/lang.js b/static/js/lang.js
index 50fe098..ef318ce 100644
--- a/static/js/lang.js
+++ b/static/js/lang.js
@@ -6,532 +6,551 @@
* Reads in char or word chunks
*/
class Reader {
- constructor(str, base = '') {
- this.base = base;
- this.i = 0;
- this.str = str;
+ 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();
}
- 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}`);
- }
+ 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;
+ 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();
+ while (this.reader.hasNext()) {
+ const next = this.reader.next();
+ switch (next) {
+ case "(": {
+ this.tokens.push("(");
+ break;
}
- 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);
+ case ")": {
+ this.tokens.push(")");
+ break;
+ }
+ case ",": {
+ this.tokens.push(",");
+ break;
}
- this.reader.next(); // throw away closing char
- this.tokens.push('"' + acc);
+ 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'),
-}
+ 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,
+ T.IsActually,
+ T.And,
+ T.Or,
+ T.Plus,
+ T.Minus,
+ T.Times,
+ T.DividedBy,
+ T.Modulo,
+ T.Beats,
+ T.SmallerThan,
];
function tokenize(prog) {
- const reader = new Reader(new Wordifier(prog).wordify(), []);
- const tokens = [];
+ 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);
- }
- }
+ 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;
+ }
+ 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'),
-}
+ 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"),
+};
class Parser {
- constructor(tokens) {
- this.tokens = new Reader(tokens, []);
+ 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());
}
- /**
- * 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;
+ if (nodes[nodes.length - 1].type !== N.ProgEndExpr) {
+ throw new Error(
+ "Parsing error: A Tabloid program MUST end with PLEASE LIKE AND SUBSCRIBE"
+ );
}
- expectIdentString() {
- const ident = this.tokens.next();
- if (typeof ident === 'string' && !ident.startsWith('"')) {
- return ident;
- }
- throw new Error(`Parsing error: expected identifier, got ${ident.toString()}`);
+
+ return nodes;
+ }
+ expectIdentString() {
+ const ident = this.tokens.next();
+ if (typeof ident === "string" && !ident.startsWith('"')) {
+ return ident;
}
- 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 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());
}
-
- throw new Error(`Parsing error: expected ident, literal, or block, got ${
- next.toString()
- } before ${this.tokens.peek().toString()}`);
+ 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,
+ };
}
- 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(),
- }
- }
+ 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();
- 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,
- }
- }
+ 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(),
+ };
+ }
- return atom;
+ 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,
+ };
}
- 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,
- }
+
+ 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());
}
+ // if (this.tokens.peek() === T.Of) {
+ // const x = this.fnCall(fnNode);
+ // return {
+ // type: N.FnCall,
+ // fn: x,
+ // args: args,
+ // };
+ // }
+
+ const x = {
+ type: N.FnCall,
+ fn: fnNode,
+ args: args,
+ };
+ // console.log("Top", x);
+ return x;
+ }
}
/* executor (tree walk) */
@@ -540,164 +559,199 @@ class Parser {
* Abused (slightly) to easily return values upstack
*/
class ReturnError {
- constructor(value) {
- this.value = value;
- }
- unwrap() {
- return this.value;
- }
+ constructor(value) {
+ this.value = value;
+ }
+ unwrap() {
+ return this.value;
+ }
}
class Environment {
- constructor(runtime) {
- /**
- * Runtime contains the following functions:
- * - print(s)
- * - input(s)
- */
- this.runtime = runtime;
- this.scopes = [{}]; // begin with global scope
+ 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);
}
- run(nodes) {
- let rv;
- for (const node of nodes) {
- rv = this.eval(node);
- }
- return rv;
+ return rv;
+ }
+
+ putClosureOnAllSubNodes(node, closure) {
+ if (Array.isArray(node)) {
+ for (let i = 0; i < node.length; ++i) {
+ node[i] = this.putClosureOnAllSubNodes(node[i], closure);
+ }
+ } else if (typeof node === "object") {
+ for (let key of Object.keys(node)) {
+ node[key] = this.putClosureOnAllSubNodes(node[key], closure);
+ }
+ node.closure = { ...closure };
}
- eval(node) {
- const scope = this.scopes[this.scopes.length - 1];
+ return node;
+ }
- switch (node.type) {
- case N.NumberLiteral:
- case N.StringLiteral:
- case N.BoolLiteral:
- return node.val;
- case N.FnDecl: {
- scope[node.name] = node;
- return node;
- }
- case N.FnCall: {
- const fn = this.eval(node.fn);
- const args = node.args.map(arg => this.eval(arg));
+ eval(node) {
+ const scope = this.scopes[this.scopes.length - 1];
- const calleeScope = {};
- fn.args.forEach((argName, i) => {
- calleeScope[argName] = args[i];
- });
+ switch (node.type) {
+ case N.NumberLiteral:
+ case N.StringLiteral:
+ case N.BoolLiteral:
+ return node.val;
+ case N.FnDecl: {
+ console.log("DECL", node, scope);
+ node.closure = { ...scope };
+ if (node.closure) {
+ node.closure = { ...node.closure, ...scope };
+ }
+ scope[node.name] = node;
+ return node;
+ }
+ case N.FnCall: {
+ const fn = this.eval(node.fn);
- this.scopes.push(calleeScope);
- let rv;
- try {
- this.eval(fn.body);
- } catch (maybeReturnErr) {
- if (maybeReturnErr instanceof ReturnError) {
- rv = maybeReturnErr.unwrap();
- } else {
- // re-throw
- throw maybeReturnErr;
- }
- }
- this.scopes.pop();
+ console.log("fn - closure", node);
+ fn.body = this.putClosureOnAllSubNodes(fn.body, fn.closure);
+ console.log("fn", fn);
- 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');
- }
+ const args = node.args.map((arg) => this.eval(arg));
+ const calleeScope = {};
+ fn.args.forEach((argName, i) => {
+ calleeScope[argName] = args[i];
+ });
- 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)}`);
+ this.scopes.push(calleeScope);
+ let rv;
+ try {
+ this.eval(fn.body);
+ } catch (maybeReturnErr) {
+ if (maybeReturnErr instanceof ReturnError) {
+ rv = maybeReturnErr.unwrap();
+ } else {
+ // re-throw
+ throw maybeReturnErr;
+ }
}
+ 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--;
+ }
+ if (node.closure && node.closure.hasOwnProperty(node.val)) {
+ return node.closure[node.val];
+ }
+ throw new Error(`Runtime error: Undefined variable "${node.val}"`);
+ }
+ case N.Assignment: {
+ scope[node.name] = this.eval(node.val);
+ console.log("assn", node);
+ 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
+ )}`
+ );
}
+ }
}
-
diff --git a/static/js/main.js b/static/js/main.js
index e14b8a6..62c14a9 100644
--- a/static/js/main.js
+++ b/static/js/main.js
@@ -1,131 +1,139 @@
-const PROG_FACTORIAL = `YOU WON'T WANT TO MISS 'Hello, World!'
-
-DISCOVER HOW TO factorial WITH n
+const PROG_FIBONACCI = `
+DISCOVER HOW TO link WITH a
RUMOR HAS IT
- WHAT IF n IS ACTUALLY 0
- SHOCKING DEVELOPMENT 1
- LIES!
- SHOCKING DEVELOPMENT
- n TIMES factorial OF n MINUS 1
+ DISCOVER HOW TO query WITH first
+ RUMOR HAS IT
+ WHAT IF first IS ACTUALLY 1
+ SHOCKING DEVELOPMENT a
+ LIES!
+ SHOCKING DEVELOPMENT 2
+ END OF STORY
+ SHOCKING DEVELOPMENT query
END OF STORY
-EXPERTS CLAIM result TO BE factorial OF 10
-YOU WON'T WANT TO MISS 'Result is'
-YOU WON'T WANT TO MISS result
+EXPERTS CLAIM something TO BE link OF 3
+YOU WON'T WANT TO MISS something OF 1
-PLEASE LIKE AND SUBSCRIBE`;
+PLEASE LIKE AND SUBSCRIBE
+
+`;
-const PROG_FIBONACCI = `DISCOVER HOW TO fibonacci WITH a, b, n
+const PROG_FACTORIAL = `DISCOVER HOW TO link WITH a
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
+ DISCOVER HOW TO query WITH first
+ RUMOR HAS IT
+ WHAT IF first IS ACTUALLY 1
+ SHOCKING DEVELOPMENT 1
+ LIES!
+ SHOCKING DEVELOPMENT 2
END OF STORY
+ SHOCKING DEVELOPMENT query
END OF STORY
-EXPERTS CLAIM limit TO BE 10
-YOU WON'T WANT TO MISS 'First 10 Fibonacci numbers'
-EXPERTS CLAIM nothing TO BE fibonacci OF 0, 1, limit
+YOU WON'T WANT TO MISS link OF 1 OF 2
PLEASE LIKE AND SUBSCRIBE`;
const PROG_DEFAULT = PROG_FIBONACCI;
const HEADLINES = [
- `You Won't Believe What This Programming Language Can Do!`,
- `The Best Programming Language You Haven't Heard Of (It Will Surprise You!)`,
- `Shocking New Programming Language Bewilders Programmers at Google and Facebook!`,
- `Programmer Who Made Everything Now Predicts the Next Big Language!`,
- `The Secret Programming Language Every 10x Programmer Recommends!`,
- `Programmers at Microsoft Hate This One Trick to Get Good at that Code Thing!`,
- `How To Lose Brain Fat With This Programming Language!`,
- `Your Friends Will Be Jealous About This New Programming Language!`,
- `You Can Earn Millions With This Programming Language!`,
- `The Cure For Cancer Could Be Found With The Programming Language!`
+ `You Won't Believe What This Programming Language Can Do!`,
+ `The Best Programming Language You Haven't Heard Of (It Will Surprise You!)`,
+ `Shocking New Programming Language Bewilders Programmers at Google and Facebook!`,
+ `Programmer Who Made Everything Now Predicts the Next Big Language!`,
+ `The Secret Programming Language Every 10x Programmer Recommends!`,
+ `Programmers at Microsoft Hate This One Trick to Get Good at that Code Thing!`,
+ `How To Lose Brain Fat With This Programming Language!`,
+ `Your Friends Will Be Jealous About This New Programming Language!`,
+ `You Can Earn Millions With This Programming Language!`,
+ `The Cure For Cancer Could Be Found With The Programming Language!`,
];
function randomHeadline() {
- return HEADLINES[~~(Math.random() * HEADLINES.length)];
+ return HEADLINES[~~(Math.random() * HEADLINES.length)];
}
-const {
- Component,
-} = window.Torus;
+const { Component } = window.Torus;
class Editor extends Component {
- init() {
- this.prog = PROG_DEFAULT;
- // script appends to it
- this.output = '';
- this.errors = '';
+ init() {
+ this.prog = PROG_DEFAULT;
+ // script appends to it
+ this.output = "";
+ this.errors = "";
- this.handleRun = () => this.eval();
- this.handleInput = evt => {
- this.prog = evt.target.value;
- this.render();
- }
- this.handleKeydown = evt => {
- if (evt.key === 'Tab') {
- evt.preventDefault();
- const idx = evt.target.selectionStart;
- if (idx !== null) {
- const front = this.prog.substr(0, idx);
- const back = this.prog.substr(idx);
- this.prog = front + ' ' + back;
- this.render();
- evt.target.setSelectionRange(idx + 4, idx + 4);
- }
- }
- }
- this.setFactorial = () => {
- this.prog = PROG_FACTORIAL;
- this.output = this.errors = '';
- this.render();
+ this.handleRun = () => this.eval();
+ this.handleInput = (evt) => {
+ this.prog = evt.target.value;
+ this.render();
+ };
+ this.handleKeydown = (evt) => {
+ if (evt.key === "Tab") {
+ evt.preventDefault();
+ const idx = evt.target.selectionStart;
+ if (idx !== null) {
+ const front = this.prog.substr(0, idx);
+ const back = this.prog.substr(idx);
+ this.prog = front + " " + back;
+ this.render();
+ evt.target.setSelectionRange(idx + 4, idx + 4);
}
- this.setFibonacci= () => {
- this.prog = PROG_FIBONACCI;
- this.output = this.errors = '';
- this.render();
- }
- }
- eval() {
- this.output = '';
- this.errors = '';
- try {
- const tokens = tokenize(this.prog);
- const nodes = new Parser(tokens).parse();
- const env = new Environment({
- print: s => {
- this.output += s.toString().toUpperCase() + '!\n';
- this.render();
- },
- input: s => {
- return prompt(s);
- },
- });
- env.run(nodes);
- } catch (e) {
- this.errors = e.toString();
- }
- this.render();
+ }
+ };
+ this.setFactorial = () => {
+ this.prog = PROG_FACTORIAL;
+ this.output = this.errors = "";
+ this.render();
+ };
+ this.setFibonacci = () => {
+ this.prog = PROG_FIBONACCI;
+ this.output = this.errors = "";
+ this.render();
+ };
+ }
+ eval() {
+ this.output = "";
+ this.errors = "";
+ try {
+ const tokens = tokenize(this.prog);
+ const nodes = new Parser(tokens).parse();
+ const env = new Environment({
+ print: (s) => {
+ console.log(s);
+ this.output += s.toString().toUpperCase() + "!\n";
+ this.render();
+ },
+ input: (s) => {
+ return prompt(s);
+ },
+ });
+ env.run(nodes);
+ } catch (e) {
+ this.errors = e.toString();
}
- compose() {
- return jdom`<div class="editor fixed block">
+ this.render();
+ }
+ compose() {
+ return jdom`<div class="editor fixed block">
<div class="controls">
<button class="block"
- onclick=${this.setFibonacci}>Fibonacci <span class="desktop">sample</span></button>
+ onclick=${
+ this.setFibonacci
+ }>Fibonacci <span class="desktop">sample</span></button>
<button class="block"
- onclick=${this.setFactorial}>Factorial <span class="desktop">sample</span></button>
+ onclick=${
+ this.setFactorial
+ }>Factorial <span class="desktop">sample</span></button>
<button class="accent block"
- onclick=${this.handleRun}>Run<span class="desktop"> this</span>!</button>
+ onclick=${
+ this.handleRun
+ }>Run<span class="desktop"> this</span>!</button>
</div>
<div class="code">
<div class="filler">
- ${this.prog.split('\n')
- .map(line => jdom`<p>${line.trim() ? line : '-'}</p>`)}
+ ${this.prog
+ .split("\n")
+ .map((line) => jdom`<p>${line.trim() ? line : "-"}</p>`)}
</div>
<textarea class="editor-input" cols="30" rows="10"
value=${this.prog}
@@ -134,32 +142,44 @@ class Editor extends Component {
</textarea>
</div>
<div class="output">
- ${this.output ? this.output
- .split('\n')
- .map(line => jdom`<code class="output-line">${line}</code>`)
- : jdom`<code class="no-output">No output.</code>`}
+ ${
+ this.output
+ ? this.output
+ .split("\n")
+ .map(
+ (line) =>
+ jdom`<code class="output-line">${line}</code>`
+ )
+ : jdom`<code class="no-output">No output.</code>`
+ }
</div>
- ${this.errors ? jdom`<div class="errors">
- ${this.errors.split('\n').map(line => jdom`<code>${line}</code>`)}
- </div>` : null}
+ ${
+ this.errors
+ ? jdom`<div class="errors">
+ ${this.errors
+ .split("\n")
+ .map((line) => jdom`<code>${line}</code>`)}
+ </div>`
+ : null
+ }
</div>`;
- }
+ }
}
class App extends Component {
- init() {
- this.editor = new Editor();
- }
- compose() {
- return jdom`<main>
+ init() {
+ this.editor = new Editor();
+ }
+ compose() {
+ return jdom`<main>
<header>
<h1>${randomHeadline()}</h1>
<nav>
<a href="https://github.com/thesephist/tabloid"
target="_blank" noopener noreferer>GitHub</a>
- <a href="#" onclick=${evt => {
- evt.preventDefault();
- this.render();
+ <a href="#" onclick=${(evt) => {
+ evt.preventDefault();
+ this.render();
}}>NEW headline!</a>
<a href="https://github.com/thesephist/tabloid/blob/master/README.md#language-overview"
target="_blank" noopener noreferer>Tutorial</a>
@@ -176,7 +196,7 @@ class App extends Component {
headlines.
</p>
<p>
- Here are <strike>a few things</strike>${' '}<strong>the Top Five
+ Here are <strike>a few things</strike>${" "}<strong>the Top Five
Most Popular Quirks and Features</strong> of the Tabloid
programming language <strong>(Number Four Will Shock You!)</strong>
</p>
@@ -242,7 +262,7 @@ class App extends Component {
</p>
<p>
Before making Tabloid, I also created a more <strike>useful and
- well-designed</strike>${' '}<strong>boring and unpopular</strong>
+ well-designed</strike>${" "}<strong>boring and unpopular</strong>
programming language, called <a href="https://dotink.co/"
target="_blank">Ink</a>.
</p>
@@ -331,7 +351,7 @@ class App extends Component {
</p>
</footer>
</main>`;
- }
+ }
}
const app = new App();