Last active
February 2, 2018 03:08
-
-
Save sue71/07bc0d172e1a5b1d8d1d0fca64dcf7b8 to your computer and use it in GitHub Desktop.
simple LL-parser
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
declare const process: any; | |
const stdin = process.openStdin(); | |
enum TokenType { | |
VarName = 'VarName', | |
Assign = 'Assign', | |
Plus = 'Plus', | |
Minus = 'Minus', | |
Multi = 'Multi', | |
Divi = 'Divi', | |
Number = 'Number', | |
Print = 'Print', | |
Lparen = 'Lparen', | |
Rparen = 'Rparen', | |
Unknown = 'Unknown' | |
} | |
const isToken = (token: Token, type: TokenType) => { | |
return token.type === type; | |
}; | |
class Token { | |
constructor( | |
public type: TokenType, public value: number, public raw: string) {} | |
} | |
class Parser { | |
stack = []; | |
vars = []; | |
currentLine: string = ''; | |
currentPointer: number = 0; | |
token = undefined; | |
constructor() { | |
stdin.addListener('data', d => { | |
const inputText = d.toString().trim(); | |
this.currentLine = inputText; | |
this.currentPointer = 0; | |
try { | |
this.statement(); | |
} catch (e) { | |
console.error(e); | |
process.exit(1); | |
} | |
}); | |
} | |
// 文 | |
statement() { | |
let token = this.nextToken(); | |
switch (token.type) { | |
case TokenType.Print: | |
this.expression(); | |
const value = this.stack.pop(); | |
console.info(value); | |
break; | |
case TokenType.VarName: | |
if (!this.isToken(this.nextToken(), TokenType.Assign)) { | |
throw new Error(`Invalid token: ${token.value}`); | |
} | |
this.expression(); | |
this.setVar(token.value, this.popStack()); | |
break; | |
} | |
} | |
// 式 | |
expression() { | |
let token = this.term(); | |
while (this.isToken(token, TokenType.Plus) || | |
this.isToken(token, TokenType.Minus)) { | |
let op = token.type; | |
token = this.term(); | |
this.operate(op); | |
} | |
} | |
// 項 | |
term(): Token { | |
let token = this.factor(); | |
while (this.isToken(token, TokenType.Multi) || | |
this.isToken(token, TokenType.Divi)) { | |
let op = token.type; | |
token = this.factor(); | |
this.operate(op); | |
} | |
return token; | |
} | |
// 因子 | |
factor(): Token { | |
let token = this.nextToken(); | |
switch (token.type) { | |
case TokenType.VarName: | |
this.pushStack(this.vars[token.value]); | |
return this.nextToken(); | |
case TokenType.Number: | |
this.pushStack(token.value); | |
return this.nextToken(); | |
case TokenType.Lparen: | |
token = this.nextToken(); | |
this.expression(); | |
token = this.nextToken(); | |
if (!this.isToken(token, TokenType.Rparen)) { | |
throw new Error(''); | |
} | |
return this.nextToken(); | |
} | |
return this.nextToken(); | |
} | |
// 演算実行 | |
operate(op: TokenType) { | |
let b = this.stack.pop(), a = this.stack.pop(); | |
switch (op) { | |
case TokenType.Plus: | |
this.pushStack(a + b); | |
break; | |
case TokenType.Minus: | |
this.pushStack(a - b); | |
break; | |
case TokenType.Multi: | |
this.pushStack(a * b); | |
break; | |
case TokenType.Divi: | |
this.pushStack(a / b); | |
break; | |
} | |
} | |
character(): string { | |
let character = this.currentLine[this.currentPointer]; | |
if (/\s/.test(character)) { | |
return this.nextCharacter(); | |
} | |
return character; | |
} | |
nextCharacter(): string { | |
let character = this.currentLine[++this.currentPointer]; | |
if (/\s/.test(character)) { | |
return this.nextCharacter(); | |
} | |
return character; | |
} | |
nextToken(): Token { | |
let character = this.character(); | |
let token = new Token(TokenType.Unknown, 0, character); | |
if (character === undefined) { | |
return token; | |
} | |
if (this.isNumber(character)) { | |
let numberText = ''; | |
while (this.isNumber(character)) { | |
numberText += character; | |
character = this.nextCharacter(); | |
} | |
token.type = TokenType.Number; | |
token.value = parseInt(numberText, 10); | |
return token; | |
} | |
if (this.isVariable(character)) { | |
token.type = TokenType.VarName; | |
token.value = character.charCodeAt(0) - 'a'.charCodeAt(0); | |
this.nextCharacter(); | |
return token; | |
} | |
switch (character) { | |
case '+': | |
token.type = TokenType.Plus; | |
break; | |
case '-': | |
token.type = TokenType.Minus; | |
break; | |
case '*': | |
token.type = TokenType.Multi; | |
break; | |
case '/': | |
token.type = TokenType.Divi; | |
break; | |
case '=': | |
token.type = TokenType.Assign; | |
break; | |
case '?': | |
token.type = TokenType.Print; | |
break; | |
case '(': | |
token.type = TokenType.Lparen; | |
break; | |
case ')': | |
token.type = TokenType.Rparen; | |
break; | |
default: | |
token.type = TokenType.Unknown; | |
} | |
this.nextCharacter(); | |
return token; | |
} | |
isToken(token: Token, type: TokenType) { | |
return token.type == type; | |
} | |
isNumber(text: string): boolean { | |
return /\d+/.test(text); | |
} | |
isVariable(text: string): boolean { | |
return /[a-z]+/.test(text); | |
} | |
setVar(index: number, value: number) { | |
this.vars[index] = value; | |
} | |
pushStack(token) { | |
this.stack.push(token); | |
} | |
popStack(): number { | |
return this.stack.pop(); | |
} | |
} | |
new Parser(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment