Get MVP of parser
This commit is contained in:
parent
901b367373
commit
81b5a5b0f1
341
main.js
341
main.js
@ -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);
|
||||||
|
Loading…
Reference in New Issue
Block a user