This commit is contained in:
Elizabeth Hunt 2023-04-05 09:52:07 -06:00
parent 21a0f4df60
commit 5719d7e118
Signed by: simponic
GPG Key ID: 52B3774857EB24B1
2 changed files with 833 additions and 759 deletions

View File

@ -6,7 +6,7 @@
* Reads in char or word chunks * Reads in char or word chunks
*/ */
class Reader { class Reader {
constructor(str, base = '') { constructor(str, base = "") {
this.base = base; this.base = base;
this.i = 0; this.i = 0;
this.str = str; this.str = str;
@ -31,7 +31,7 @@ class Reader {
return result; return result;
} }
dropWhitespace() { dropWhitespace() {
this.readUntil(c => !!c.trim()); this.readUntil((c) => !!c.trim());
} }
expect(tok) { expect(tok) {
const next = this.next(); const next = this.next();
@ -56,16 +56,16 @@ class Wordifier {
while (this.reader.hasNext()) { while (this.reader.hasNext()) {
const next = this.reader.next(); const next = this.reader.next();
switch (next) { switch (next) {
case '(': { case "(": {
this.tokens.push('('); this.tokens.push("(");
break; break;
} }
case ')': { case ")": {
this.tokens.push(')'); this.tokens.push(")");
break; break;
} }
case ',': { case ",": {
this.tokens.push(','); this.tokens.push(",");
break; break;
} }
case '"': case '"':
@ -76,9 +76,11 @@ class Wordifier {
default: { default: {
// read until WS // read until WS
this.reader.backstep(); this.reader.backstep();
this.tokens.push(this.reader.readUntil(c => { this.tokens.push(
return !c.trim() || ['(', ')', ','].includes(c) this.reader.readUntil((c) => {
})); return !c.trim() || ["(", ")", ","].includes(c);
})
);
} }
} }
this.reader.dropWhitespace(); this.reader.dropWhitespace();
@ -86,12 +88,12 @@ class Wordifier {
return this.tokens; return this.tokens;
} }
wordifyString(endChar) { wordifyString(endChar) {
let acc = ''; let acc = "";
acc += this.reader.readUntil(c => c == endChar); acc += this.reader.readUntil((c) => c == endChar);
while (acc.endsWith('\\') || !this.reader.hasNext()) { while (acc.endsWith("\\") || !this.reader.hasNext()) {
acc = acc.substr(0, acc.length - 1); acc = acc.substr(0, acc.length - 1);
this.reader.next(); // endChar this.reader.next(); // endChar
acc += endChar + this.reader.readUntil(c => c == endChar); acc += endChar + this.reader.readUntil((c) => c == endChar);
} }
this.reader.next(); // throw away closing char this.reader.next(); // throw away closing char
this.tokens.push('"' + acc); this.tokens.push('"' + acc);
@ -99,35 +101,35 @@ class Wordifier {
} }
const T = { const T = {
LParen: Symbol('LParen'), LParen: Symbol("LParen"),
RParen: Symbol('RParen'), RParen: Symbol("RParen"),
Comma: Symbol('Comma'), Comma: Symbol("Comma"),
DiscoverHowTo: Symbol('DiscoverHowTo'), DiscoverHowTo: Symbol("DiscoverHowTo"),
With: Symbol('With'), With: Symbol("With"),
Of: Symbol('Of'), Of: Symbol("Of"),
RumorHasIt: Symbol('RumorHasIt'), RumorHasIt: Symbol("RumorHasIt"),
WhatIf: Symbol('WhatIf'), WhatIf: Symbol("WhatIf"),
LiesBang: Symbol('LiesBang'), LiesBang: Symbol("LiesBang"),
EndOfStory: Symbol('EndOfStory'), EndOfStory: Symbol("EndOfStory"),
ExpertsClaim: Symbol('ExpertsClaim'), ExpertsClaim: Symbol("ExpertsClaim"),
ToBe: Symbol('ToBe'), ToBe: Symbol("ToBe"),
YouWontWantToMiss: Symbol('YouWontWantToMiss'), YouWontWantToMiss: Symbol("YouWontWantToMiss"),
LatestNewsOn: Symbol('LatestNewsOn'), LatestNewsOn: Symbol("LatestNewsOn"),
TotallyRight: Symbol('TotallyRight'), TotallyRight: Symbol("TotallyRight"),
CompletelyWrong: Symbol('CompletelyWrong'), CompletelyWrong: Symbol("CompletelyWrong"),
IsActually: Symbol('IsActually'), IsActually: Symbol("IsActually"),
And: Symbol('And'), And: Symbol("And"),
Or: Symbol('Or'), Or: Symbol("Or"),
Plus: Symbol('Plus'), Plus: Symbol("Plus"),
Minus: Symbol('Minus'), Minus: Symbol("Minus"),
Times: Symbol('Times'), Times: Symbol("Times"),
DividedBy: Symbol('DividedBy'), DividedBy: Symbol("DividedBy"),
Modulo: Symbol('Modulo'), Modulo: Symbol("Modulo"),
Beats: Symbol('Beats'), // > Beats: Symbol("Beats"), // >
SmallerThan: Symbol('SmallerThan'), // < SmallerThan: Symbol("SmallerThan"), // <
ShockingDevelopment: Symbol('ShockingDevelopment'), ShockingDevelopment: Symbol("ShockingDevelopment"),
PleaseLikeAndSubscribe: Symbol('PleaseLikeAndSubscribe'), PleaseLikeAndSubscribe: Symbol("PleaseLikeAndSubscribe"),
} };
const BINARY_OPS = [ const BINARY_OPS = [
T.IsActually, T.IsActually,
@ -149,139 +151,139 @@ function tokenize(prog) {
while (reader.hasNext()) { while (reader.hasNext()) {
const next = reader.next(); const next = reader.next();
switch (next) { switch (next) {
case 'DISCOVER': { case "DISCOVER": {
reader.expect('HOW'); reader.expect("HOW");
reader.expect('TO'); reader.expect("TO");
tokens.push(T.DiscoverHowTo); tokens.push(T.DiscoverHowTo);
break; break;
} }
case 'WITH': { case "WITH": {
tokens.push(T.With); tokens.push(T.With);
break; break;
} }
case 'OF': { case "OF": {
tokens.push(T.Of); tokens.push(T.Of);
break; break;
} }
case 'RUMOR': { case "RUMOR": {
reader.expect('HAS'); reader.expect("HAS");
reader.expect('IT'); reader.expect("IT");
tokens.push(T.RumorHasIt); tokens.push(T.RumorHasIt);
break; break;
} }
case 'WHAT': { case "WHAT": {
reader.expect('IF'); reader.expect("IF");
tokens.push(T.WhatIf); tokens.push(T.WhatIf);
break; break;
} }
case 'LIES!': { case "LIES!": {
tokens.push(T.LiesBang); tokens.push(T.LiesBang);
break; break;
} }
case 'END': { case "END": {
reader.expect('OF'); reader.expect("OF");
reader.expect('STORY'); reader.expect("STORY");
tokens.push(T.EndOfStory); tokens.push(T.EndOfStory);
break; break;
} }
case 'EXPERTS': { case "EXPERTS": {
reader.expect('CLAIM'); reader.expect("CLAIM");
tokens.push(T.ExpertsClaim); tokens.push(T.ExpertsClaim);
break; break;
} }
case 'TO': { case "TO": {
reader.expect('BE'); reader.expect("BE");
tokens.push(T.ToBe); tokens.push(T.ToBe);
break; break;
} }
case 'YOU': { case "YOU": {
reader.expect('WON\'T'); reader.expect("WON'T");
reader.expect('WANT'); reader.expect("WANT");
reader.expect('TO'); reader.expect("TO");
reader.expect('MISS'); reader.expect("MISS");
tokens.push(T.YouWontWantToMiss); tokens.push(T.YouWontWantToMiss);
break; break;
} }
case 'LATEST': { case "LATEST": {
reader.expect('NEWS'); reader.expect("NEWS");
reader.expect('ON'); reader.expect("ON");
tokens.push(T.LatestNewsOn); tokens.push(T.LatestNewsOn);
break; break;
} }
case 'IS': { case "IS": {
reader.expect('ACTUALLY'); reader.expect("ACTUALLY");
tokens.push(T.IsActually); tokens.push(T.IsActually);
break; break;
} }
case 'AND': { case "AND": {
tokens.push(T.And); tokens.push(T.And);
break; break;
} }
case 'OR': { case "OR": {
tokens.push(T.Or); tokens.push(T.Or);
break; break;
} }
case 'PLUS': { case "PLUS": {
tokens.push(T.Plus); tokens.push(T.Plus);
break; break;
} }
case 'MINUS': { case "MINUS": {
tokens.push(T.Minus); tokens.push(T.Minus);
break; break;
} }
case 'TIMES': { case "TIMES": {
tokens.push(T.Times); tokens.push(T.Times);
break; break;
} }
case 'DIVIDED': { case "DIVIDED": {
reader.expect('BY'); reader.expect("BY");
tokens.push(T.DividedBy); tokens.push(T.DividedBy);
break; break;
} }
case 'MODULO': { case "MODULO": {
tokens.push(T.Modulo); tokens.push(T.Modulo);
break; break;
} }
case 'BEATS': { case "BEATS": {
tokens.push(T.Beats); tokens.push(T.Beats);
break; break;
} }
case 'SMALLER': { case "SMALLER": {
reader.expect('THAN'); reader.expect("THAN");
tokens.push(T.SmallerThan); tokens.push(T.SmallerThan);
break; break;
} }
case 'SHOCKING': { case "SHOCKING": {
reader.expect('DEVELOPMENT'); reader.expect("DEVELOPMENT");
tokens.push(T.ShockingDevelopment); tokens.push(T.ShockingDevelopment);
break; break;
} }
case 'PLEASE': { case "PLEASE": {
reader.expect('LIKE'); reader.expect("LIKE");
reader.expect('AND'); reader.expect("AND");
reader.expect('SUBSCRIBE'); reader.expect("SUBSCRIBE");
tokens.push(T.PleaseLikeAndSubscribe); tokens.push(T.PleaseLikeAndSubscribe);
break; break;
} }
case 'TOTALLY': { case "TOTALLY": {
reader.expect('RIGHT'); reader.expect("RIGHT");
tokens.push(T.TotallyRight); tokens.push(T.TotallyRight);
break; break;
} }
case 'COMPLETELY': { case "COMPLETELY": {
reader.expect('WRONG'); reader.expect("WRONG");
tokens.push(T.CompletelyWrong); tokens.push(T.CompletelyWrong);
break; break;
} }
case '(': { case "(": {
tokens.push(T.LParen); tokens.push(T.LParen);
break; break;
} }
case ')': { case ")": {
tokens.push(T.RParen); tokens.push(T.RParen);
break; break;
} }
case ',': { case ",": {
tokens.push(T.Comma); tokens.push(T.Comma);
break; break;
} }
@ -302,21 +304,21 @@ function tokenize(prog) {
/* parser */ /* parser */
const N = { const N = {
NumberLiteral: Symbol('NumberLiteral'), NumberLiteral: Symbol("NumberLiteral"),
StringLiteral: Symbol('StringLiteral'), StringLiteral: Symbol("StringLiteral"),
BoolLiteral: Symbol('BoolLiteral'), BoolLiteral: Symbol("BoolLiteral"),
FnDecl: Symbol('FnDecl'), FnDecl: Symbol("FnDecl"),
FnCall: Symbol('FnCall'), FnCall: Symbol("FnCall"),
Ident: Symbol('Ident'), Ident: Symbol("Ident"),
Assignment: Symbol('Assignment'), Assignment: Symbol("Assignment"),
BinaryOp: Symbol('BinaryOp'), BinaryOp: Symbol("BinaryOp"),
IfExpr: Symbol('IfExpr'), IfExpr: Symbol("IfExpr"),
ExprGroup: Symbol('ExprGroup'), ExprGroup: Symbol("ExprGroup"),
ReturnExpr: Symbol('ReturnExpr'), ReturnExpr: Symbol("ReturnExpr"),
ProgEndExpr: Symbol('ProgEndExpr'), ProgEndExpr: Symbol("ProgEndExpr"),
PrintExpr: Symbol('PrintExpr'), PrintExpr: Symbol("PrintExpr"),
InputExpr: Symbol('InputExpr'), InputExpr: Symbol("InputExpr"),
} };
class Parser { class Parser {
constructor(tokens) { constructor(tokens) {
@ -352,36 +354,40 @@ class Parser {
} }
if (nodes[nodes.length - 1].type !== N.ProgEndExpr) { if (nodes[nodes.length - 1].type !== N.ProgEndExpr) {
throw new Error('Parsing error: A Tabloid program MUST end with PLEASE LIKE AND SUBSCRIBE'); throw new Error(
"Parsing error: A Tabloid program MUST end with PLEASE LIKE AND SUBSCRIBE"
);
} }
return nodes; return nodes;
} }
expectIdentString() { expectIdentString() {
const ident = this.tokens.next(); const ident = this.tokens.next();
if (typeof ident === 'string' && !ident.startsWith('"')) { if (typeof ident === "string" && !ident.startsWith('"')) {
return ident; return ident;
} }
throw new Error(`Parsing error: expected identifier, got ${ident.toString()}`); throw new Error(
`Parsing error: expected identifier, got ${ident.toString()}`
);
} }
atom() { atom() {
const next = this.tokens.next(); const next = this.tokens.next();
if (typeof next === 'number') { if (typeof next === "number") {
return { return {
type: N.NumberLiteral, type: N.NumberLiteral,
val: next, val: next,
} };
} else if (typeof next === 'string') { } else if (typeof next === "string") {
if (next.startsWith('"')) { if (next.startsWith('"')) {
return { return {
type: N.StringLiteral, type: N.StringLiteral,
val: next.substr(1), val: next.substr(1),
} };
} }
const ident = { const ident = {
type: N.Ident, type: N.Ident,
val: next, val: next,
} };
if (this.tokens.peek() === T.Of) { if (this.tokens.peek() === T.Of) {
return this.fnCall(ident); return this.fnCall(ident);
} }
@ -390,12 +396,12 @@ class Parser {
return { return {
type: N.BoolLiteral, type: N.BoolLiteral,
val: true, val: true,
} };
} else if (next === T.CompletelyWrong) { } else if (next === T.CompletelyWrong) {
return { return {
type: N.BoolLiteral, type: N.BoolLiteral,
val: false, val: false,
} };
} else if (next === T.DiscoverHowTo) { } else if (next === T.DiscoverHowTo) {
// fn literal // fn literal
const fnName = this.tokens.next(); const fnName = this.tokens.next();
@ -412,14 +418,14 @@ class Parser {
name: fnName, name: fnName,
args: args, args: args,
body: this.expr(), body: this.expr(),
} };
} else { } else {
return { return {
type: N.FnDecl, type: N.FnDecl,
name: fnName, name: fnName,
args: [], args: [],
body: this.expr(), body: this.expr(),
} };
} }
} else if (next === T.RumorHasIt) { } else if (next === T.RumorHasIt) {
// block // block
@ -445,9 +451,11 @@ class Parser {
}; };
} }
throw new Error(`Parsing error: expected ident, literal, or block, got ${ throw new Error(
next.toString() `Parsing error: expected ident, literal, or block, got ${next.toString()} before ${this.tokens
} before ${this.tokens.peek().toString()}`); .peek()
.toString()}`
);
} }
expr() { expr() {
const next = this.tokens.next(); const next = this.tokens.next();
@ -466,7 +474,7 @@ class Parser {
cond: cond, cond: cond,
ifBody: ifBody, ifBody: ifBody,
elseBody: elseBody, elseBody: elseBody,
} };
} else if (next === T.ExpertsClaim) { } else if (next === T.ExpertsClaim) {
// assignment // assignment
const name = this.expectIdentString(); const name = this.expectIdentString();
@ -476,30 +484,30 @@ class Parser {
type: N.Assignment, type: N.Assignment,
name, name,
val, val,
} };
} else if (next === T.ShockingDevelopment) { } else if (next === T.ShockingDevelopment) {
// return // return
return { return {
type: N.ReturnExpr, type: N.ReturnExpr,
val: this.expr(), val: this.expr(),
} };
} else if (next === T.PleaseLikeAndSubscribe) { } else if (next === T.PleaseLikeAndSubscribe) {
// prog end // prog end
return { return {
type: N.ProgEndExpr, type: N.ProgEndExpr,
} };
} else if (next === T.YouWontWantToMiss) { } else if (next === T.YouWontWantToMiss) {
// print expr // print expr
return { return {
type: N.PrintExpr, type: N.PrintExpr,
val: this.expr(), val: this.expr(),
} };
} else if (next === T.LatestNewsOn) { } else if (next === T.LatestNewsOn) {
// input expr // input expr
return { return {
type: N.InputExpr, type: N.InputExpr,
val: this.expr(), val: this.expr(),
} };
} }
this.tokens.backstep(); this.tokens.backstep();
@ -514,7 +522,7 @@ class Parser {
op, op,
left, left,
right, right,
} };
} }
return atom; return atom;
@ -526,11 +534,22 @@ class Parser {
this.tokens.next(); // comma this.tokens.next(); // comma
args.push(this.expr()); args.push(this.expr());
} }
return { // if (this.tokens.peek() === T.Of) {
// const x = this.fnCall(fnNode);
// return {
// type: N.FnCall,
// fn: x,
// args: args,
// };
// }
const x = {
type: N.FnCall, type: N.FnCall,
fn: fnNode, fn: fnNode,
args: args, args: args,
} };
// console.log("Top", x);
return x;
} }
} }
@ -565,6 +584,21 @@ class Environment {
} }
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 };
}
return node;
}
eval(node) { eval(node) {
const scope = this.scopes[this.scopes.length - 1]; const scope = this.scopes[this.scopes.length - 1];
@ -574,13 +608,22 @@ class Environment {
case N.BoolLiteral: case N.BoolLiteral:
return node.val; return node.val;
case N.FnDecl: { case N.FnDecl: {
console.log("DECL", node, scope);
node.closure = { ...scope };
if (node.closure) {
node.closure = { ...node.closure, ...scope };
}
scope[node.name] = node; scope[node.name] = node;
return node; return node;
} }
case N.FnCall: { case N.FnCall: {
const fn = this.eval(node.fn); const fn = this.eval(node.fn);
const args = node.args.map(arg => this.eval(arg));
console.log("fn - closure", node);
fn.body = this.putClosureOnAllSubNodes(fn.body, fn.closure);
console.log("fn", fn);
const args = node.args.map((arg) => this.eval(arg));
const calleeScope = {}; const calleeScope = {};
fn.args.forEach((argName, i) => { fn.args.forEach((argName, i) => {
calleeScope[argName] = args[i]; calleeScope[argName] = args[i];
@ -608,12 +651,16 @@ class Environment {
if (node.val in this.scopes[i]) { if (node.val in this.scopes[i]) {
return this.scopes[i][node.val]; return this.scopes[i][node.val];
} }
i --; i--;
}
if (node.closure && node.closure.hasOwnProperty(node.val)) {
return node.closure[node.val];
} }
throw new Error(`Runtime error: Undefined variable "${node.val}"`); throw new Error(`Runtime error: Undefined variable "${node.val}"`);
} }
case N.Assignment: { case N.Assignment: {
scope[node.name] = this.eval(node.val); scope[node.name] = this.eval(node.val);
console.log("assn", node);
return scope[node.name]; return scope[node.name];
} }
case N.BinaryOp: { case N.BinaryOp: {
@ -641,7 +688,9 @@ class Environment {
case T.SmallerThan: case T.SmallerThan:
return left < right; return left < right;
default: default:
throw new Error(`Runtime error: Unknown binary op ${node.op.toString()}`); throw new Error(
`Runtime error: Unknown binary op ${node.op.toString()}`
);
} }
} }
case N.IfExpr: { case N.IfExpr: {
@ -654,7 +703,9 @@ class Environment {
} }
case N.ExprGroup: { case N.ExprGroup: {
if (!node.exprs.length) { if (!node.exprs.length) {
throw new Error('Runtime error: Empty expression group with no expressions'); throw new Error(
"Runtime error: Empty expression group with no expressions"
);
} }
let rv; let rv;
@ -675,9 +726,9 @@ class Environment {
let val = this.eval(node.val); let val = this.eval(node.val);
// shim for boolean to-string's // shim for boolean to-string's
if (val === true) { if (val === true) {
val = 'TOTALLY RIGHT'; val = "TOTALLY RIGHT";
} else if (val === false) { } else if (val === false) {
val = 'COMPLETELY WRONG'; val = "COMPLETELY WRONG";
} }
this.runtime.print(val); this.runtime.print(val);
return val; return val;
@ -686,18 +737,21 @@ class Environment {
let val = this.eval(node.val); let val = this.eval(node.val);
// shim for boolean to-string's // shim for boolean to-string's
if (val === true) { if (val === true) {
val = 'TOTALLY RIGHT'; val = "TOTALLY RIGHT";
} else if (val === false) { } else if (val === false) {
val = 'COMPLETELY WRONG'; val = "COMPLETELY WRONG";
} }
return this.runtime.input(val); return this.runtime.input(val);
} }
default: default:
console.log(JSON.stringify(node, null, 2)); console.log(JSON.stringify(node, null, 2));
throw new Error(`Runtime error: Unknown AST Node of type ${ throw new Error(
node.type.toString() `Runtime error: Unknown AST Node of type ${node.type.toString()}:\n${JSON.stringify(
}:\n${JSON.stringify(node, null, 2)}`); node,
null,
2
)}`
);
} }
} }
} }

View File

@ -1,34 +1,36 @@
const PROG_FACTORIAL = `YOU WON'T WANT TO MISS 'Hello, World!' const PROG_FIBONACCI = `
DISCOVER HOW TO link WITH a
DISCOVER HOW TO factorial WITH n
RUMOR HAS IT RUMOR HAS IT
WHAT IF n IS ACTUALLY 0 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 something TO BE link OF 3
YOU WON'T WANT TO MISS something OF 1
PLEASE LIKE AND SUBSCRIBE
`;
const PROG_FACTORIAL = `DISCOVER HOW TO link WITH a
RUMOR HAS IT
DISCOVER HOW TO query WITH first
RUMOR HAS IT
WHAT IF first IS ACTUALLY 1
SHOCKING DEVELOPMENT 1 SHOCKING DEVELOPMENT 1
LIES! LIES!
SHOCKING DEVELOPMENT SHOCKING DEVELOPMENT 2
n TIMES factorial OF n MINUS 1
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
PLEASE LIKE AND SUBSCRIBE`;
const PROG_FIBONACCI = `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
SHOCKING DEVELOPMENT query
END OF STORY END OF STORY
EXPERTS CLAIM limit TO BE 10 YOU WON'T WANT TO MISS link OF 1 OF 2
YOU WON'T WANT TO MISS 'First 10 Fibonacci numbers'
EXPERTS CLAIM nothing TO BE fibonacci OF 0, 1, limit
PLEASE LIKE AND SUBSCRIBE`; PLEASE LIKE AND SUBSCRIBE`;
@ -44,65 +46,64 @@ const HEADLINES = [
`How To Lose Brain Fat With This Programming Language!`, `How To Lose Brain Fat With This Programming Language!`,
`Your Friends Will Be Jealous About This New Programming Language!`, `Your Friends Will Be Jealous About This New Programming Language!`,
`You Can Earn Millions With This Programming Language!`, `You Can Earn Millions With This Programming Language!`,
`The Cure For Cancer Could Be Found With The Programming Language!` `The Cure For Cancer Could Be Found With The Programming Language!`,
]; ];
function randomHeadline() { function randomHeadline() {
return HEADLINES[~~(Math.random() * HEADLINES.length)]; return HEADLINES[~~(Math.random() * HEADLINES.length)];
} }
const { const { Component } = window.Torus;
Component,
} = window.Torus;
class Editor extends Component { class Editor extends Component {
init() { init() {
this.prog = PROG_DEFAULT; this.prog = PROG_DEFAULT;
// script appends to it // script appends to it
this.output = ''; this.output = "";
this.errors = ''; this.errors = "";
this.handleRun = () => this.eval(); this.handleRun = () => this.eval();
this.handleInput = evt => { this.handleInput = (evt) => {
this.prog = evt.target.value; this.prog = evt.target.value;
this.render(); this.render();
} };
this.handleKeydown = evt => { this.handleKeydown = (evt) => {
if (evt.key === 'Tab') { if (evt.key === "Tab") {
evt.preventDefault(); evt.preventDefault();
const idx = evt.target.selectionStart; const idx = evt.target.selectionStart;
if (idx !== null) { if (idx !== null) {
const front = this.prog.substr(0, idx); const front = this.prog.substr(0, idx);
const back = this.prog.substr(idx); const back = this.prog.substr(idx);
this.prog = front + ' ' + back; this.prog = front + " " + back;
this.render(); this.render();
evt.target.setSelectionRange(idx + 4, idx + 4); evt.target.setSelectionRange(idx + 4, idx + 4);
} }
} }
} };
this.setFactorial = () => { this.setFactorial = () => {
this.prog = PROG_FACTORIAL; this.prog = PROG_FACTORIAL;
this.output = this.errors = ''; this.output = this.errors = "";
this.render(); this.render();
} };
this.setFibonacci= () => { this.setFibonacci = () => {
this.prog = PROG_FIBONACCI; this.prog = PROG_FIBONACCI;
this.output = this.errors = ''; this.output = this.errors = "";
this.render(); this.render();
} };
} }
eval() { eval() {
this.output = ''; this.output = "";
this.errors = ''; this.errors = "";
try { try {
const tokens = tokenize(this.prog); const tokens = tokenize(this.prog);
const nodes = new Parser(tokens).parse(); const nodes = new Parser(tokens).parse();
const env = new Environment({ const env = new Environment({
print: s => { print: (s) => {
this.output += s.toString().toUpperCase() + '!\n'; console.log(s);
this.output += s.toString().toUpperCase() + "!\n";
this.render(); this.render();
}, },
input: s => { input: (s) => {
return prompt(s); return prompt(s);
}, },
}); });
@ -116,16 +117,23 @@ class Editor extends Component {
return jdom`<div class="editor fixed block"> return jdom`<div class="editor fixed block">
<div class="controls"> <div class="controls">
<button class="block" <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" <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" <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>
<div class="code"> <div class="code">
<div class="filler"> <div class="filler">
${this.prog.split('\n') ${this.prog
.map(line => jdom`<p>${line.trim() ? line : '-'}</p>`)} .split("\n")
.map((line) => jdom`<p>${line.trim() ? line : "-"}</p>`)}
</div> </div>
<textarea class="editor-input" cols="30" rows="10" <textarea class="editor-input" cols="30" rows="10"
value=${this.prog} value=${this.prog}
@ -134,14 +142,26 @@ class Editor extends Component {
</textarea> </textarea>
</div> </div>
<div class="output"> <div class="output">
${this.output ? this.output ${
.split('\n') this.output
.map(line => jdom`<code class="output-line">${line}</code>`) ? this.output
: jdom`<code class="no-output">No output.</code>`} .split("\n")
.map(
(line) =>
jdom`<code class="output-line">${line}</code>`
)
: jdom`<code class="no-output">No output.</code>`
}
</div> </div>
${this.errors ? jdom`<div class="errors"> ${
${this.errors.split('\n').map(line => jdom`<code>${line}</code>`)} this.errors
</div>` : null} ? jdom`<div class="errors">
${this.errors
.split("\n")
.map((line) => jdom`<code>${line}</code>`)}
</div>`
: null
}
</div>`; </div>`;
} }
} }
@ -157,7 +177,7 @@ class App extends Component {
<nav> <nav>
<a href="https://github.com/thesephist/tabloid" <a href="https://github.com/thesephist/tabloid"
target="_blank" noopener noreferer>GitHub</a> target="_blank" noopener noreferer>GitHub</a>
<a href="#" onclick=${evt => { <a href="#" onclick=${(evt) => {
evt.preventDefault(); evt.preventDefault();
this.render(); this.render();
}}>NEW headline!</a> }}>NEW headline!</a>
@ -176,7 +196,7 @@ class App extends Component {
headlines. headlines.
</p> </p>
<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 Most Popular Quirks and Features</strong> of the Tabloid
programming language <strong>(Number Four Will Shock You!)</strong> programming language <strong>(Number Four Will Shock You!)</strong>
</p> </p>
@ -242,7 +262,7 @@ class App extends Component {
</p> </p>
<p> <p>
Before making Tabloid, I also created a more <strike>useful and 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/" programming language, called <a href="https://dotink.co/"
target="_blank">Ink</a>. target="_blank">Ink</a>.
</p> </p>