Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
5719d7e118 |
@ -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
|
||||||
|
)}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user