UNPKG

snakeparser

Version:

Casual parser generator inspired by PEG.js

609 lines (495 loc) 17.5 kB
var expressions = {}; // Expression Class var Expression = function() { }; expressions["exp"] = Expression; var extendsExpression = function(cls, name) { cls.prototype = new Expression(); cls.prototype._name = name; cls.prototype.constructor = cls; expressions[name] = cls; }; // Classes extends Expression var Nop = function() { }; extendsExpression(Nop, "nop"); var Fail = function() { }; extendsExpression(Fail, "fl"); var MatchString = function(s) { this.string = s; }; extendsExpression(MatchString, "str"); var MatchCharacterClass = function(cc, i) { this.characterClass = cc; this.invert = !!i; }; extendsExpression(MatchCharacterClass, "cc"); var MatchAnyCharacter = function() { }; extendsExpression(MatchAnyCharacter, "ac"); var OrderedChoice = function(es) { if (es instanceof Array) this.children = es; else { this.children = [].slice.call(arguments, 0, [].indexOf.call(arguments)); } }; extendsExpression(OrderedChoice, "oc"); var Sequence = function(es) { if (es instanceof Array) this.children = es; else { this.children = [].slice.call(arguments, 0, [].indexOf.call(arguments)); } }; extendsExpression(Sequence, "seq"); var Repeat = function(min, max, e) { this.min = min !== undefined ? min : 0; this.max = max !== undefined ? (max === "min" ? min : max) : Infinity; if (this.min < 0 || this.max < this.min) throw new Error("Invalid repeat expression."); this.child = e; this.possibleInfiniteLoop = this.max === Infinity; }; extendsExpression(Repeat, "rep"); var Objectize = function(e) { this.child = e; }; extendsExpression(Objectize, "obj"); var Arraying = function(e) { this.child = e; }; extendsExpression(Arraying, "arr"); var Property = function(k, e) { this.key = k; this.child = e; }; extendsExpression(Property, "pr"); var Tokenize = function(e) { this.child = e; }; extendsExpression(Tokenize, "tkn"); var ContextVariable = function(variable) { this.variable = variable; }; extendsExpression(ContextVariable, "cv"); var Literal = function(v) { this.value = v; }; extendsExpression(Literal, "ltr"); var PositiveLookaheadAssertion = function(e) { this.child = e; }; extendsExpression(PositiveLookaheadAssertion, "pla"); var NegativeLookaheadAssertion = function(e) { this.child = e; }; extendsExpression(NegativeLookaheadAssertion, "nla"); var Modify = function(e, i, c, ip) { this.child = e; this.identifier = i; this.code = c; this.identifierPlaceholder = ip; }; extendsExpression(Modify, "mod"); var Guard = function(e, i, c, ip) { this.child = e; this.identifier = i; this.code = c; this.identifierPlaceholder = ip; }; extendsExpression(Guard, "grd"); var Waste = function(e) { this.child = e; }; extendsExpression(Waste, "wst"); var RuleReference = function(r, a, rule, body) { this.ruleIdent = r; this.arguments = a; this.rule = rule; this.body = body; }; extendsExpression(RuleReference, "rul"); RuleReference.prototype.getReference = function() { var rule = this.rule; if (!rule.references) rule.references = []; findReference: for (var i in rule.references) { if (rule.references[i].parameters.length !== this.arguments.length) continue findReference; for (var j = 0; j < this.arguments.length; ++j) if (rule.references[i].arguments[j].body.toString() !== this.arguments[j].body.toString()) continue findReference; return rule.references[i]; } var reference = { arguments: rule.arguments, referenceCount: 0, body: null, }; rule.references.push(reference); return reference; }; // prepare 名前付きでないルールの結びつけ Expression.prototype.prepare = function(rules) { }; OrderedChoice.prototype.prepare = function(rules) { for (var i in this.children) this.children[i].prepare(rules); }; Sequence.prototype.prepare = OrderedChoice.prototype.prepare; Repeat.prototype.prepare = function(rules) { this.child.prepare(rules); }; Objectize.prototype.prepare = Repeat.prototype.prepare; Arraying.prototype.prepare = Repeat.prototype.prepare; Tokenize.prototype.prepare = Repeat.prototype.prepare; Property.prototype.prepare = Repeat.prototype.prepare; PositiveLookaheadAssertion.prototype.prepare = Repeat.prototype.prepare; NegativeLookaheadAssertion.prototype.prepare = Repeat.prototype.prepare; Modify.prototype.prepare = Repeat.prototype.prepare; Guard.prototype.prepare = Repeat.prototype.prepare; Waste.prototype.prepare = Repeat.prototype.prepare; RuleReference.prototype.prepare = function(rules) { var rule = rules[this.ruleIdent]; if (!rule) throw new Error('Identifier ' + this.ruleIdent + ' is not defined.'); this.rule = rule; if (rule === "argument") // 引数の参照の場合はここまで return; // 参照をカウント rule.referenceCount = (rule.referenceCount || 0) + 1; if (rule.parameters instanceof Array) { // 引数付きルールの参照の場合 // アリティチェック if (!(this.arguments instanceof Array) || rule.parameters.length !== this.arguments.length) { throw new Error('Referenced rule ' + rule.ident + ' takes ' + rule.parameters.length + ' arguments (' + this.arguments.length + ' given).'); } // 引数を再帰的に prepare for (var i in this.arguments) this.arguments[i].prepare(rules); } else { // 引数なしルールの参照の場合 // アリティチェック if (this.arguments instanceof Array) throw new Error('Referenced rule ' + rule.ident + ' takes no arguments.'); this.body = rule.body; } }; // expand 引数付きルールの呼び出しを展開する Expression.prototype.expand = function(env) { }; OrderedChoice.prototype.expand = function(env) { for (var i in this.children) this.children[i].expand(env); }; Sequence.prototype.expand = OrderedChoice.prototype.expand; Repeat.prototype.expand = function(env) { this.child.expand(env); }; Objectize.prototype.expand = Repeat.prototype.expand; Arraying.prototype.expand = Repeat.prototype.expand; Tokenize.prototype.expand = Repeat.prototype.expand; Property.prototype.expand = Repeat.prototype.expand; PositiveLookaheadAssertion.prototype.expand = Repeat.prototype.expand; NegativeLookaheadAssertion.prototype.expand = Repeat.prototype.expand; Modify.prototype.expand = Repeat.prototype.expand; Guard.prototype.expand = Repeat.prototype.expand; Waste.prototype.expand = Repeat.prototype.expand; RuleReference.prototype.expand = function(env) { if (this.arguments instanceof Array) { // 引数付きルールの参照の場合 this.body = this.reduce(env, 1); } else { // 引数付きでないルールの参照の場合 this.body = this.rule.body; // 入れる必要無さそうだけど canLeftRecurs で使う } }; // reduce 簡約 Expression.prototype.reduce = function(env, depth) { return this; }; OrderedChoice.prototype.reduce = function(env, depth) { var changed = false; var children = []; for (var i in this.children) { children[i] = this.children[i].reduce(env, depth); changed = changed || this.children[i] !== children[i]; } if (!changed) return this; return new this.constructor(children); }; Sequence.prototype.reduce = OrderedChoice.prototype.reduce; Repeat.prototype.reduce = function(env, depth) { var child = this.child.reduce(env, depth); if (child === this.child) return this; return new Repeat(this.min, this.max, child); }; Objectize.prototype.reduce = function(env, depth) { var child = this.child.reduce(env, depth); if (child === this.child) return this; return new this.constructor(child); }; Arraying.prototype.reduce = Objectize.prototype.reduce; Tokenize.prototype.reduce = Objectize.prototype.reduce; PositiveLookaheadAssertion.prototype.reduce = Objectize.prototype.reduce; NegativeLookaheadAssertion.prototype.reduce = Objectize.prototype.reduce; Waste.prototype.reduce = Objectize.prototype.reduce; Property.prototype.reduce = function(env, depth) { var child = this.child.reduce(env, depth); if (child === this.child) return this; return new Property(this.key, child); }; Modify.prototype.reduce = function(env, depth) { var child = this.child.reduce(env, depth); if (child === this.child) return this; return new this.constructor(child, this.identifier, this.code, this); }; Guard.prototype.reduce = Modify.prototype.reduce; RuleReference.prototype.reduce = function(env, depth) { if (this.rule === "argument") { // 引数の参照の場合 var body = env[this.ruleIdent]; if (!body) throw new Error('Referenced argument ' + this.ruleIdent + ' not found.'); return body; } else if (this.arguments instanceof Array) { // 引数付きルールの参照の場合 if (depth === 32) throw new Error("Parameterized rule reference nested too deep."); if (this.rule.recursive) { // 再帰 var arguments = []; for (var i in this.arguments) { arguments[i] = this.arguments[i].reduce(env, depth + 1); } // すでに簡約されていないかチェック this.rule.reduceds = this.rule.reduceds || []; var reduced = null; findReduced: for (var i in this.rule.reduceds) { reduced = this.rule.reduceds[i]; for (var j in reduced.arguments) { if (reduced.arguments.toString(j) !== arguments.toString()) { reduced = null; continue findReduced; } } break; } if (!reduced) { // 簡約されていなかったので簡約する reduced = new RuleReference( this.ruleIdent + "$" + this.rule.reduceds.length, arguments, null, null ); this.rule.reduceds.push(reduced); var env1 = {}; env1.__proto__ = env; for (var i in arguments) env1[this.rule.parameters[i]] = arguments[i]; reduced.body = this.rule.body.reduce(env1, depth + 1); } this.reduced = reduced; return reduced; } else { // 展開 var env1 = {}; env1.__proto__ = env; for (var i in this.arguments) { env1[this.rule.parameters[i]] = this.arguments[i].reduce(env, depth + 1); } return this.rule.body.reduce(env1, depth + 1); } } else { // 引数付きでないルールの参照の場合 return this; } }; // toString Expression.prototype.toString = function() { return this._name + "()"; }; OrderedChoice.prototype.toString = function() { var ss = []; for (var i in this.children) ss.push(this.children[i].toString()); return this._name + "(" + ss.join(",") + ")"; }; Sequence.prototype.toString = OrderedChoice.prototype.toString; MatchString.prototype.toString = function() { return this._name + "(" + JSON.stringify(this.string) + ")"; }; MatchCharacterClass.prototype.toString = function() { return this._name + "(" + JSON.stringify(this.characterClass) + "," + +this.invert + ")"; }; Repeat.prototype.toString = function() { return this._name + "(" + this.min + "," + this.max + "," + this.child.toString() + ")"; }; Objectize.prototype.toString = function() { return this._name + "(" + this.child.toString() + ")"; }; Arraying.prototype.toString = Objectize.prototype.toString; Tokenize.prototype.toString = Objectize.prototype.toString; PositiveLookaheadAssertion.prototype.toString = Objectize.prototype.toString; NegativeLookaheadAssertion.prototype.toString = Objectize.prototype.toString; Property.prototype.toString = function() { return this._name + "(" + JSON.stringify(this.key) + "," + this.child.toString() + ")"; }; Literal.prototype.toString = function() { return this._name + "(" + JSON.stringify(this.value) + ")"; }; ContextVariable.prototype.toString = function() { return this._name + "(" + JSON.stringify(this.variable) + ")"; }; Modify.prototype.toString = function() { if (this.code) { return this._name + "(" + this.child.toString() + ",null," + JSON.stringify(this.code) + ")"; } else { return this._name + "(" + this.child.toString() + "," + JSON.stringify(this.identifier) + ",null)"; } }; Guard.prototype.toString = Modify.prototype.toString; Waste.prototype.toString = function() { return this._name + "(" + this.child.toString() + ")"; }; RuleReference.prototype.toString = function() { if (!this.parameters) return this._name + "(" + JSON.stringify(this.ruleIdent) + ")"; var args = this.arguments.map(function(e) { return e.toString(); }).join(","); return this._name + "(" + JSON.stringify(this.ruleIdent) + ",[" + args + "])"; }; // traverse Expression.prototype.traverse = function(func) { func(this); }; OrderedChoice.prototype.traverse = function(func) { func(this); for (var i in this.children) this.children[i].traverse(func); }; Sequence.prototype.traverse = OrderedChoice.prototype.traverse; Repeat.prototype.traverse = function(func) { func(this); this.child.traverse(func); }; Objectize.prototype.traverse = Repeat.prototype.traverse; Arraying.prototype.traverse = Repeat.prototype.traverse; Tokenize.prototype.traverse = Repeat.prototype.traverse; Property.prototype.traverse = Repeat.prototype.traverse; PositiveLookaheadAssertion.prototype.traverse = Repeat.prototype.traverse; NegativeLookaheadAssertion.prototype.traverse = Repeat.prototype.traverse; Modify.prototype.traverse = Repeat.prototype.traverse; Guard.prototype.traverse = Repeat.prototype.traverse; Waste.prototype.traverse = Repeat.prototype.traverse; RuleReference.prototype.traverse = function(func) { func(this); }; // isRecursive 引数付きルールに対して Expression.prototype.isRecursive = function(ruleIdent, passedRules) { return false; }; OrderedChoice.prototype.isRecursive = function(ruleIdent, passedRules) { for (var i in this.children) if (this.children[i].isRecursive(ruleIdent, passedRules)) return true; return false; }; Sequence.prototype.isRecursive = OrderedChoice.prototype.isRecursive; Repeat.prototype.isRecursive = function(ruleIdent, passedRules) { return this.child.isRecursive(ruleIdent, passedRules); }; Objectize.prototype.isRecursive = function(ruleIdent, passedRules) { return this.child.isRecursive(ruleIdent, passedRules); }; Arraying.prototype.isRecursive = Objectize.prototype.isRecursive; Tokenize.prototype.isRecursive = Objectize.prototype.isRecursive; PositiveLookaheadAssertion.prototype.isRecursive = Objectize.prototype.isRecursive; NegativeLookaheadAssertion.prototype.isRecursive = Objectize.prototype.isRecursive; Waste.prototype.isRecursive = Objectize.prototype.isRecursive; Property.prototype.isRecursive = function(ruleIdent, passedRules) { return this.child.isRecursive(ruleIdent, passedRules); }; Modify.prototype.isRecursive = function(ruleIdent, passedRules) { return this.child.isRecursive(ruleIdent, passedRules); }; Guard.prototype.isRecursive = Modify.prototype.isRecursive; RuleReference.prototype.isRecursive = function(ruleIdent, passedRules) { if (this.arguments instanceof Array) { // 引数付きルールの参照の場合 if (this.ruleIdent === ruleIdent) return true; if (passedRules.indexOf(this.ruleIdent) !== -1) return false; for (var i in this.arguments) if (this.arguments[i].isRecursive(ruleIdent, passedRules)) return true; return this.rule.body.isRecursive(ruleIdent, passedRules.concat([this.ruleIdent])); } }; ////////////////////////////////////////////////////////// // -1 必ず進む 0 進まない可能性がある 1 左再帰する可能性がある Expression.prototype.canLeftRecurs = function(rule, passedRules) { return 0; }; Nop.prototype.canLeftRecurs = function(rule, passedRules) { return -1; }; OrderedChoice.prototype.canLeftRecurs = function(rule, passedRules) { var res = -1; for (var i in this.children) res = Math.max(res, this.children[i].canLeftRecurs(rule, passedRules)); return res; }; Sequence.prototype.canLeftRecurs = function(rule, passedRules) { for (var i in this.children) { var r = this.children[i].canLeftRecurs(rule, passedRules); if (r === -1) return -1; else if (r === 1) return 1; } return 0; }; MatchString.prototype.canLeftRecurs = function(rule, passedRules) { return this.string.length !== 0 ? -1 : 0; }; MatchCharacterClass.prototype.canLeftRecurs = function(rule, passedRules) { return -1; }; MatchAnyCharacter.prototype.canLeftRecurs = MatchCharacterClass.prototype.canLeftRecurs; Repeat.prototype.canLeftRecurs = function(rule, passedRules) { if (this.min === 0) { return Math.max(0, this.child.canLeftRecurs(rule, passedRules)); } else { return this.child.canLeftRecurs(rule, passedRules); } }; Objectize.prototype.canLeftRecurs = function(rule, passedRules) { return this.child.canLeftRecurs(rule, passedRules); }; Arraying.prototype.canLeftRecurs = Objectize.prototype.canLeftRecurs; Tokenize.prototype.canLeftRecurs = Objectize.prototype.canLeftRecurs; Property.prototype.canLeftRecurs = Objectize.prototype.canLeftRecurs; PositiveLookaheadAssertion.prototype.canLeftRecurs = Objectize.prototype.canLeftRecurs; NegativeLookaheadAssertion.prototype.canLeftRecurs = Objectize.prototype.canLeftRecurs; Modify.prototype.canLeftRecurs = Objectize.prototype.canLeftRecurs; Guard.prototype.canLeftRecurs = Objectize.prototype.canLeftRecurs; Waste.prototype.canLeftRecurs = Objectize.prototype.canLeftRecurs; RuleReference.prototype.canLeftRecurs = function(rule, passedRules) { if (rule === this.ruleIdent) return 1; if (passedRules.indexOf(this.ruleIdent) !== -1) return 0; // 別ルールの左再帰を検出した var ret = this.leftRecurs; if (ret !== undefined) return ret; ret = this.body.canLeftRecurs(rule, passedRules.concat([this.ruleIdent])); if (ret === -1) rule.leftRecurs = ret; return ret; }; module.exports = expressions;