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];
@ -610,10 +653,14 @@ class Environment {
} }
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
SHOCKING DEVELOPMENT query
END OF STORY END OF STORY
EXPERTS CLAIM result TO BE factorial OF 10 YOU WON'T WANT TO MISS link OF 1 OF 2
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
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
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>