Skip to content

Instantly share code, notes, and snippets.

@yorickpeterse
Created September 8, 2015 12:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yorickpeterse/48cde95091326ee4a0e0 to your computer and use it in GitHub Desktop.
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
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);
(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