Get MVP of parser

This commit is contained in:
Linus Lee 2020-09-24 04:32:05 -04:00
parent 901b367373
commit 81b5a5b0f1

341
main.js
View File

@ -6,12 +6,13 @@ WE SAID
WHAT IF n IS ACTUALLY 0 WHAT IF n IS ACTUALLY 0
WE SAID WE SAID
SHOCKING DEVELOPMENT 1 SHOCKING DEVELOPMENT 1
END OF STORY
LIES! WE SAID LIES! WE SAID
SHOCKING DEVELOPMENT MULTIPLY n, factorial OF (n) SHOCKING DEVELOPMENT n MULTIPLY factorial OF n
END OF STORY END OF STORY
END OF STORY END OF STORY
EXPERTS CLAIM result TO BE factorial OF (10) EXPERTS CLAIM result TO BE factorial OF 10
YOU WON'T WANT TO MISS 'RESULT IS' YOU WON'T WANT TO MISS 'RESULT IS'
YOU WON'T WANT TO MISS result YOU WON'T WANT TO MISS result
@ -130,7 +131,7 @@ const T = {
YouWontWantToMiss: Symbol('YouWontWantToMiss'), YouWontWantToMiss: Symbol('YouWontWantToMiss'),
IsActually: Symbol('IsActually'), IsActually: Symbol('IsActually'),
And: Symbol('And'), And: Symbol('And'),
or: Symbol('or'), Or: Symbol('Or'),
Add: Symbol('Add'), Add: Symbol('Add'),
Subtract: Symbol('Subtract'), Subtract: Symbol('Subtract'),
Multiply: Symbol('Multiply'), Multiply: Symbol('Multiply'),
@ -140,177 +141,183 @@ const T = {
SmallerThan: Symbol('SmallerThan'), // < SmallerThan: Symbol('SmallerThan'), // <
ShockingDevelopment: Symbol('ShockingDevelopment'), ShockingDevelopment: Symbol('ShockingDevelopment'),
PleaseLikeAndSubscribe: Symbol('PleaseLikeAndSubscribe'), PleaseLikeAndSubscribe: Symbol('PleaseLikeAndSubscribe'),
// not implemented yet
StayTuned: Symbol('StayTuned'), StayTuned: Symbol('StayTuned'),
Unexpectedly: Symbol('Unexpectedly'), Unexpectedly: Symbol('Unexpectedly'),
TotallyRight: Symbol('TotallyRight'), TotallyRight: Symbol('TotallyRight'),
CompletelyWrong: Symbol('CompletelyWrong'), CompletelyWrong: Symbol('CompletelyWrong'),
} }
class Tokenizer { const BINARY_OPS = [
constructor(prog) { T.IsActually,
this.reader = new Reader(new Wordifier(prog).wordify(), []); T.And,
this.tokens = []; T.Or,
} T.Add,
tokenize() { T.Subtract,
if (this.tokens.length) return this.tokens; T.Multiply,
T.Divide,
T.Modulo,
T.Beats,
T.SmallerThan,
];
while (this.reader.hasNext()) { function tokenize(prog) {
const next = this.reader.next(); const reader = new Reader(new Wordifier(prog).wordify(), []);
const tokens = [];
while (reader.hasNext()) {
const next = reader.next();
switch (next) { switch (next) {
case 'DISCOVER': { case 'DISCOVER': {
this.reader.expect('HOW'); reader.expect('HOW');
this.reader.expect('TO'); reader.expect('TO');
this.tokens.push(T.DiscoverHowTo); tokens.push(T.DiscoverHowTo);
break; break;
} }
case 'WITH': { case 'WITH': {
this.tokens.push(T.With); tokens.push(T.With);
break; break;
} }
case 'OF': { case 'OF': {
this.tokens.push(T.Of); tokens.push(T.Of);
break; break;
} }
case 'WE': { case 'WE': {
this.reader.expect('SAID'); reader.expect('SAID');
this.tokens.push(T.WeSaid); tokens.push(T.WeSaid);
break; break;
} }
case 'WHAT': { case 'WHAT': {
this.reader.expect('IF'); reader.expect('IF');
this.tokens.push(T.WhatIf); tokens.push(T.WhatIf);
break; break;
} }
case 'LIES!': { case 'LIES!': {
this.tokens.push(T.LiesBang); tokens.push(T.LiesBang);
break; break;
} }
case 'END': { case 'END': {
this.reader.expect('OF'); reader.expect('OF');
this.reader.expect('STORY'); reader.expect('STORY');
this.tokens.push(T.EndOfStory); tokens.push(T.EndOfStory);
break; break;
} }
case 'EXPERTS': { case 'EXPERTS': {
this.reader.expect('CLAIM'); reader.expect('CLAIM');
this.tokens.push(T.ExpertsClaim); tokens.push(T.ExpertsClaim);
break; break;
} }
case 'TO': { case 'TO': {
this.reader.expect('BE'); reader.expect('BE');
this.tokens.push(T.ToBe); tokens.push(T.ToBe);
break; break;
} }
case 'YOU': { case 'YOU': {
this.reader.expect('WON\'T'); reader.expect('WON\'T');
this.reader.expect('WANT'); reader.expect('WANT');
this.reader.expect('TO'); reader.expect('TO');
this.reader.expect('MISS'); reader.expect('MISS');
this.tokens.push(T.YouWontWantToMiss); tokens.push(T.YouWontWantToMiss);
break; break;
} }
case 'IS': { case 'IS': {
this.reader.expect('ACTUALLY'); reader.expect('ACTUALLY');
this.tokens.push(T.IsActually); tokens.push(T.IsActually);
break; break;
} }
case 'AND': { case 'AND': {
this.tokens.push(T.And); tokens.push(T.And);
break; break;
} }
case 'OR': { case 'OR': {
this.tokens.push(T.Or); tokens.push(T.Or);
break; break;
} }
case 'ADD': { case 'ADD': {
this.tokens.push(T.Add); tokens.push(T.Add);
break; break;
} }
case 'SUBTRACT': { case 'SUBTRACT': {
this.tokens.push(T.Subtract); tokens.push(T.Subtract);
break; break;
} }
case 'MULTIPLY': { case 'MULTIPLY': {
this.tokens.push(T.Multiply); tokens.push(T.Multiply);
break; break;
} }
case 'DIVIDE': { case 'DIVIDE': {
this.tokens.push(T.Divide); tokens.push(T.Divide);
break; break;
} }
case 'MODULO': { case 'MODULO': {
this.tokens.push(T.Modulo); tokens.push(T.Modulo);
break; break;
} }
case 'BEATS': { case 'BEATS': {
this.tokens.push(T.Beats); tokens.push(T.Beats);
break; break;
} }
case 'SMALLER': { case 'SMALLER': {
this.reader.expect('THAN'); reader.expect('THAN');
this.tokens.push(T.SmallerThan); tokens.push(T.SmallerThan);
break; break;
} }
case 'SHOCKING': { case 'SHOCKING': {
this.reader.expect('DEVELOPMENT'); reader.expect('DEVELOPMENT');
this.tokens.push(T.ShockingDevelopment); tokens.push(T.ShockingDevelopment);
break; break;
} }
case 'PLEASE': { case 'PLEASE': {
this.reader.expect('LIKE'); reader.expect('LIKE');
this.reader.expect('AND'); reader.expect('AND');
this.reader.expect('SUBSCRIBE'); reader.expect('SUBSCRIBE');
this.tokens.push(T.PleaseLikeAndSubscribe); tokens.push(T.PleaseLikeAndSubscribe);
break; break;
} }
case 'STAY': { case 'STAY': {
this.reader.expect('TUNED'); reader.expect('TUNED');
this.tokens.push(T.StayTuned); tokens.push(T.StayTuned);
break; break;
} }
case 'UNEXPECTEDLY': { case 'UNEXPECTEDLY': {
this.tokens.push(T.Unexpectedly); tokens.push(T.Unexpectedly);
break; break;
} }
case 'TOTALLY': { case 'TOTALLY': {
this.reader.expect('RIGHT'); reader.expect('RIGHT');
this.tokens.push(T.TotallyRight); tokens.push(T.TotallyRight);
break; break;
} }
case 'COMPLETELY': { case 'COMPLETELY': {
this.reader.expect('WRONG'); reader.expect('WRONG');
this.tokens.push(T.CompletelyWrong); tokens.push(T.CompletelyWrong);
break; break;
} }
case '(': { case '(': {
this.tokens.push(T.LParen); tokens.push(T.LParen);
break; break;
} }
case ')': { case ')': {
this.tokens.push(T.RParen); tokens.push(T.RParen);
break; break;
} }
case ',': { case ',': {
this.tokens.push(T.Comma); tokens.push(T.Comma);
break; break;
} }
default: { default: {
if (!isNaN(parseFloat(next))) { if (!isNaN(parseFloat(next))) {
// number literal // number literal
this.tokens.push(parseFloat(next)); tokens.push(parseFloat(next));
} else { } else {
// string or varname // string or varname
this.tokens.push(next); tokens.push(next);
} }
} }
} }
} }
return this.tokens; return tokens;
}
}
function tokenize(prog) {
const reader = new Reader(prog);
} }
/* parser */ /* parser */
@ -318,24 +325,204 @@ function tokenize(prog) {
const N = { const N = {
NumberLiteral: Symbol('NumberLiteral'), NumberLiteral: Symbol('NumberLiteral'),
StringLiteral: Symbol('StringLiteral'), StringLiteral: Symbol('StringLiteral'),
FnLiteral: Symbol('FnLiteral'), FnDecl: Symbol('FnDecl'),
FnCall: Symbol('FnCall'), FnCall: Symbol('FnCall'),
Ident: Symbol('Ident'), Ident: Symbol('Ident'),
Assignment: Symbol('Assignment'),
BinaryOp: Symbol('BinaryOp'), BinaryOp: Symbol('BinaryOp'),
IfExpr: Symbol('IfExpr'), IfExpr: Symbol('IfExpr'),
ExprList: Symbol('ExprList'), ExprGroup: Symbol('ExprGroup'),
// etc ReturnExpr: Symbol('ReturnExpr'),
ProgEndExpr: Symbol('ProgEndExpr'),
PrintExpr: Symbol('PrintExpr'),
}
class Parser {
constructor(tokens) {
this.tokens = new Reader(tokens, []);
}
/**
* Atom
* Ident
* NumberLiteral
* StringLiteral
* FnCall
* FnDecl
* ExprGroup
*
* Expression:
* (begins with atom)
* BinaryOp
* Atom
* (begins with keyword)
* IfExpr
* Assignment
* ReturnExpr
* ProgEndExpr
* PrintExpr
*
*/
parse() {
const nodes = [];
while (this.tokens.hasNext()) {
nodes.push(this.expr());
}
return nodes;
}
expectIdent() {
const ident = this.tokens.next();
if (typeof ident === 'string' && !ident.startsWith('"')) {
return ident;
}
throw new Error(`Parsing error: expected identifier, got ${ident.toString()}`);
}
atom() {
const next = this.tokens.next();
if (typeof next === 'number') {
return {
type: N.NumberLiteral,
val: next,
}
} else if (typeof next === 'string') {
if (next.startsWith('"')) {
return {
type: N.StringLiteral,
val: next.substr(1),
}
}
const ident = {
type: N.Ident,
val: next,
}
if (this.tokens.peek() === T.Of) {
return this.fnCall(ident);
}
return ident;
} else if (next === T.DiscoverHowTo) {
// fn literal
const fnName = this.tokens.next();
if (this.tokens.peek(T.With)) {
this.tokens.next(); // with
// with args
const args = [this.expectIdent()];
while (this.tokens.peek() === T.Comma) {
this.tokens.next(); // comma
args.push(this.expectIdent());
}
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.WeSaid) {
// block
const exprs = [];
while (this.tokens.hasNext() && this.tokens.peek() !== T.EndOfStory) {
exprs.push(this.expr());
}
this.tokens.expect(T.EndOfStory);
return exprs;
}
throw new Error(`Parsing error: expected ident, literal, or block, got ${
next.toString()
} before ${this.tokens.peek().toString()}`);
}
expr() {
const next = this.tokens.next();
if (next === T.WhatIf) {
// if expr
const cond = this.expr();
const ifBody = this.expr();
let elseBody = null;
if (this.tokens.peek() == T.LiesBang) {
this.tokens.next(); // LiesBang
elseBody = this.expr();
}
return {
type: N.IfExpr,
cond: cond,
ifBody: ifBody,
elseBody: elseBody,
}
} else if (next === T.ExpertsClaim) {
// assignment
const name = this.expectIdent();
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(),
}
}
this.tokens.backstep();
const atom = this.atom();
if (BINARY_OPS.includes(this.tokens.peek())) {
// infix binary ops
// TODO: support operator precedence
const left = atom;
const op = this.tokens.next();
const right = this.atom();
return {
type: N.BinaryOp,
left,
right,
}
}
return atom;
}
fnCall(fnNode) {
this.tokens.expect(T.Of);
// TODO: support multiple arguments
const args = [this.expr()];
return {
type: N.FnCall,
fn: fnNode,
args: args,
}
}
} }
/* executor (tree walk) */ /* executor (tree walk) */
class Environment { class Environment {
constructor(nodes) { constructor(nodes) {
this.scopes = [{}]; // begin with global scope
} }
} }
const tokens = new Tokenizer(prog).tokenize(); // main
const tokens = tokenize(prog);
console.log(tokens); console.log(tokens);
const nodes = new Parser(tokens).parse();
const reader = new Reader("test of this"); console.log(nodes);