Created
September 8, 2015 12:12
-
-
Save yorickpeterse/48cde95091326ee4a0e0 to your computer and use it in GitHub Desktop.
Port of the ruby-ll driver to TypeScript, using examples/math.rll for the transition tables/syntax/etc
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
type Token = [string, string]; | |
type Tokens = Array<Token>; | |
type NodeChildren = Array<any>; | |
class AstNode { | |
public node_type: string; | |
public children: NodeChildren; | |
constructor(node_type: string, children: NodeChildren) { | |
this.node_type = node_type; | |
this.children = children; | |
} | |
inspect(depth, options = {}) { | |
return this.pretty_inspect(); | |
} | |
pretty_inspect(indent = 0) { | |
let spaces = ''; | |
let children = ''; | |
for ( let index = 0; index < indent; index++ ) { | |
spaces += ' '; | |
} | |
this.children.forEach(function(child) { | |
if ( child.pretty_inspect ) { | |
children += `\n${child.pretty_inspect(indent + 2)}` | |
} | |
else if ( child.inspect ) { | |
children += ` ${child.inspect()}`; | |
} | |
else { | |
children += ` ${child.toString()}`; | |
} | |
}); | |
return `${spaces}(${this.node_type}${children})` | |
} | |
} | |
class Parser { | |
static T_EOF = -1; | |
static T_RULE = 0; | |
static T_TERMINAL = 1; | |
static T_EPSILON = 2; | |
static T_ACTION = 3; | |
static T_STAR = 4; | |
static T_PLUS = 5; | |
static T_ADD_VALUE_STACK = 6; | |
static T_APPEND_VALUE_STACK = 7; | |
static T_QUESTION = 8; | |
static TERMINALS = [ | |
'$EOF', // 0 | |
'T_NUMBER', // 1 | |
'T_ADD', // 2 | |
'T_DIV', // 3 | |
'T_MUL', // 4 | |
'T_SUB', // 5 | |
'T_AND', // 6 | |
'T_OR' // 7 | |
]; | |
static RULES = [ | |
[3, 0, 4, 5, 6, 0, 0, 4], // 0 | |
[3, 1, 0, 4, 0, 2], // 1 | |
[3, 2, 0, 0, 0, 3], // 2 | |
[3, 3, 1, 2], // 3 | |
[3, 4, 1, 3], // 4 | |
[3, 5, 1, 4], // 5 | |
[3, 6, 1, 5], // 6 | |
[3, 7, 1, 6], // 7 | |
[3, 8, 1, 7], // 8 | |
[3, 9, 1, 1], // 9 | |
[3, 10, 0, 1] // 10 | |
]; | |
static TABLE = [ | |
[-1, 0, -1, -1, -1, -1, -1, -1], // 0 | |
[-1, -1, 1, 1, 1, 1, 2, 2], // 1 | |
[-1, -1, 3, 4, 5, 6, -1, -1], // 2 | |
[-1, -1, -1, -1, -1, -1, 7, 8], // 3 | |
[-1, 9, -1, -1, -1, -1, -1, -1], // 4 | |
[-1, -1, 10, 10, 10, 10, 10, 10] // 5 | |
]; | |
static ACTIONS: Array<[string, number]> = [ | |
['_rule_0', 2], // 0 | |
['_rule_1', 2], // 1 | |
['_rule_2', 2], // 2 | |
['_rule_3', 1], // 3 | |
['_rule_4', 1], // 4 | |
['_rule_5', 1], // 5 | |
['_rule_6', 1], // 6 | |
['_rule_7', 1], // 7 | |
['_rule_8', 1], // 8 | |
['_rule_9', 1], // 9 | |
['_rule_10', 1] // 10 | |
]; | |
tokens: Tokens; | |
constructor(tokens: Tokens) { | |
this.tokens = tokens; | |
} | |
parse(): AstNode { | |
let stack: Array<number> = []; | |
let value_stack: Array<any> = []; | |
// EOF | |
stack.push(Parser.T_EOF); | |
stack.push(Parser.T_EOF); | |
// Start rule | |
Parser.RULES[0].forEach(function(value) { | |
stack.push(value); | |
}); | |
this.tokens.forEach((token) => { | |
let token_type = token[0] | |
let token_value = token[1]; | |
while ( true ) { | |
if ( stack.length == 0 ) { | |
throw new Error("empty stack"); | |
} | |
let stack_value = stack.pop(); | |
let stack_type = stack.pop(); | |
let token_id = 0; | |
if ( Parser.TERMINALS.indexOf(token_type) != -1 ) { | |
token_id = Parser.TERMINALS.indexOf(token_type); | |
} | |
// A rule or the "+" operator | |
if ( stack_type == Parser.T_RULE || stack_type == Parser.T_PLUS ) { | |
let production_i = Parser.TABLE[stack_value][token_id]; | |
if ( production_i == Parser.T_EOF ) { | |
throw new Error("Received EOF for a rule or +"); | |
} | |
else { | |
// Append a "*" operator for all following occurrences | |
// as they are optional | |
if ( stack_type == Parser.T_PLUS ) { | |
stack.push(Parser.T_STAR); | |
stack.push(stack_value); | |
stack.push(Parser.T_APPEND_VALUE_STACK); | |
stack.push(0); | |
} | |
Parser.RULES[production_i].forEach(function(value) { | |
stack.push(value); | |
}); | |
} | |
} | |
// "*" operator | |
else if ( stack_type == Parser.T_STAR ) { | |
let production_i = Parser.TABLE[stack_value][token_id]; | |
if ( production_i != Parser.T_EOF ) { | |
stack.push(Parser.T_STAR); | |
stack.push(stack_value); | |
stack.push(Parser.T_APPEND_VALUE_STACK); | |
stack.push(0); | |
Parser.RULES[production_i].forEach(function(value) { | |
stack.push(value); | |
}); | |
} | |
} | |
// "?" operator | |
else if ( stack_type == Parser.T_QUESTION ) { | |
let production_i = Parser.TABLE[stack_value][token_id]; | |
if ( production_i == Parser.T_EOF ) { | |
value_stack.push(null); | |
} | |
else { | |
Parser.RULES[production_i].forEach(function(value) { | |
stack.push(value); | |
}); | |
} | |
} | |
// Adds a new array to the value stack that can be used to group | |
// operator values together | |
else if ( stack_type == Parser.T_ADD_VALUE_STACK ) { | |
value_stack.push([]); | |
} | |
// Appends the last value on the value stack to the operator | |
// buffer that preceeds it. | |
else if ( stack_type == Parser.T_APPEND_VALUE_STACK ) { | |
let last_value = value_stack.pop(); | |
let buffer = value_stack[value_stack.length - 1]; | |
buffer.push(last_value); | |
} | |
// Terminal | |
else if ( stack_type == Parser.T_TERMINAL ) { | |
if ( stack_value == token_id ) { | |
value_stack.push(token_value); | |
break; | |
} | |
else { | |
throw new Error("unexpected terminal"); | |
} | |
} | |
// Action | |
else if ( stack_type == Parser.T_ACTION ) { | |
let method = Parser.ACTIONS[stack_value][0]; | |
let num_args = Parser.ACTIONS[stack_value][1]; | |
let action_args = []; | |
if ( num_args > value_stack.length ) { | |
num_args = value_stack.length; | |
} | |
while ( (num_args--) > 0 ) { | |
if ( value_stack.length > 0 ) { | |
action_args[num_args] = value_stack.pop(); | |
} | |
} | |
value_stack.push(this[method](action_args)); | |
} | |
else if ( stack_type == Parser.T_EOF || stack_type == null ) { | |
break; | |
} | |
} | |
}); | |
if ( value_stack.length == 0 ) { | |
return null; | |
} | |
else { | |
return value_stack.pop(); | |
} | |
} | |
_rule_0(val) { | |
let ret = val[0] | |
// Combines all the operators together in a left-associative manner. | |
if ( val[1] ) { | |
val[1].forEach(function(pair) { | |
ret = new AstNode(pair[0], [ret, pair[1]]); | |
}); | |
} | |
return ret | |
} | |
_rule_1(val) { | |
return val; | |
} | |
_rule_2(val) { | |
return val; | |
} | |
_rule_3(val) { | |
return 'plus'; | |
} | |
_rule_4(val) { | |
return 'div'; | |
} | |
_rule_5(val) { | |
return 'mul'; | |
} | |
_rule_6(val) { | |
return 'sub'; | |
} | |
_rule_7(val) { | |
return 'and'; | |
} | |
_rule_8(val) { | |
return 'or'; | |
} | |
_rule_9(val) { | |
return new AstNode('number', [val[0]]); | |
} | |
_rule_10(val) { | |
return val[0]; | |
} | |
} | |
let tokens: Array<Token> = [ | |
['T_NUMBER', '10'], | |
['T_ADD', '+'], | |
['T_NUMBER', '20'], | |
['T_SUB', '-'], | |
['T_NUMBER', '5'], | |
[null, null] | |
]; | |
let parser = new Parser(tokens); | |
let output = parser.parse(); | |
console.log(output); |
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
(sub | |
(plus | |
(number 10) | |
(number 20)) | |
(number 5)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment