Skip to content

Instantly share code, notes, and snippets.

@pasaran
Created October 29, 2012 11:27
Show Gist options
  • Save pasaran/3973052 to your computer and use it in GitHub Desktop.
Save pasaran/3973052 to your computer and use it in GitHub Desktop.
parser.02
var tokens = {};
tokens.ADD = /^[+-]/;
tokens.MUL = /^[*/%]/;
tokens.NUMBER = /^[0-9]+(\.[0-9]+)?/;
// --------------------------------------------------------------------------------------------------------------- //
var rules = {};
rules.expr = function() {
// console.log('rule expr');
return this.match('add');
};
rules.add = function(ast) {
// console.log('rule add');
ast.left = this.match('mul');
if (( this.test('ADD') )) {
ast.op = this.match('ADD');
ast.right = this.match('add')
} else {
return ast.left;
}
};
rules.mul = function(ast) {
// console.log('rule mul');
ast.left = this.match('primary');
if (( this.test('MUL') )) {
ast.op = this.match('MUL');
ast.right = this.match('mul')
} else {
return ast.left;
}
};
rules.primary = function() {
// console.log('rule primary', this.test('('), this.current);
if ( this.test('(') ) {
return this.match('subexpr');
}
return this.match('number');
};
rules.subexpr = function(ast) {
// console.log('rule subexpr');
this.match('(');
ast.expr = this.match('expr');
this.match(')');
};
rules.number = function(ast) {
// console.log('rule number');
ast.value = +this.match('NUMBER');
};
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>parser.02</title>
<script src="02.grammar.js"></script>
<script src="02.parser.js"></script>
</head>
<body>
<script>
var grammar = {
tokens: tokens,
rules: rules
};
var parser = new Parser(grammar);
var ast = parser.parse('2 + 3 * (5 - 6)', 'expr');
console.log( JSON.stringify(ast, null, 4) );
</script>
</body>
</html>
function Parser(grammar) {
this._patterns = {};
this._addTokens( grammar.tokens || {} );
this._addRules( grammar.rules || {} );
};
Parser.prototype._addTokens = function(tokens) {
for (var id in tokens) {
this._addToken( id, tokens[id] );
}
};
Parser.prototype._addToken = function(id, token) {
var compiled;
if (typeof token === 'string') {
var l = token.length;
compiled = function() {
if (this.current.substr(0, l) !== token) {
this.error('Expected token: ' + id);
}
this.move(l);
this.skip();
return token;
};
} else {
compiled = function() {
var r = token.exec(this.current);
if (!r) {
this.error('Expected token: ' + id);
}
var s = r[0];
this.move(s.length);
this.skip();
return s;
};
}
this._patterns[id] = compiled;
return compiled;
};
Parser.prototype._addRules = function(rules) {
var patterns = this._patterns;
for (var id in rules) {
(function(id, rule) {
patterns[id] = function() {
var ast = {
_id: id
};
var r = rule.call(this, ast);
return (r === undefined) ? ast : r;
};
})( id, rules[id] );
}
};
Parser.prototype.parse = function(input, id) {
this.input = input;
this.p = 0;
this.current = input;
var ast = this.match(id);
/*
if (this.current) {
this.error('End of string expected');
}
*/
return ast;
};
Parser.prototype.match = function(id) {
// console.log('match', id, this.current);
var r = this._get(id).call(this);
this.skip();
return r;
};
Parser.prototype.test = function(id) {
var r = true;
var p = this.p;
try {
this._get(id).call(this);
} catch (e) {
r = false;
}
this.p = p;
this.current = this.input.substr(p);
return r;
};
Parser.prototype.skip = function() {
var r = /^\s+/.exec(this.current);
if (r) {
this.move(r[0].length);
}
};
Parser.prototype.move = function(n) {
this.p += n;
this.current = this.current.substr(n);
};
Parser.prototype.error = function(msg) {
throw 'ERROR: ' + msg + ', at position ' + this.p;
};
Parser.prototype._get = function(id) {
var pattern = this._patterns[id];
if (!pattern) {
pattern = this._addToken(id, id);
}
return pattern;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment