mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
122 lines
3.4 KiB
JavaScript
122 lines
3.4 KiB
JavaScript
(function() {
|
|
|
|
// The "tree string" format is a simple format for representing syntax trees.
|
|
//
|
|
// For example, the parse of `x++;` is written as:
|
|
// "program(expressionStmnt(postfix(identifier(x) ++) ;))"
|
|
//
|
|
// A Node is written as "name(item1 item2 item3)", with additional whitespace
|
|
// allowed anywhere between the name, parentheses, and items.
|
|
//
|
|
// Tokens don't need to be escaped unless they contain '(', ')', whitespace, or
|
|
// backticks, or are empty. If they do, they can be written enclosed in backticks.
|
|
// To escape a backtick within backticks, double it.
|
|
//
|
|
// `stringify` generates "canonical" tree strings, which have no extra escaping
|
|
// or whitespace, just one space between items in a Node.
|
|
|
|
|
|
ParseNode.prototype.stringify = function () {
|
|
return ParseNode.stringify(this);
|
|
};
|
|
|
|
var backtickEscape = function (str) {
|
|
if (/[\s()`]/.test(str))
|
|
return '`' + str.replace(/`/g, '``') + '`';
|
|
else if (! str)
|
|
return '``';
|
|
else
|
|
return str;
|
|
};
|
|
|
|
var backtickUnescape = function (str) {
|
|
if (str.charAt(0) === '`') {
|
|
if (str.length === 1 || str.slice(-1) !== '`')
|
|
throw new Error("Mismatched ` in " + str);
|
|
if (str.length === 2)
|
|
str = '';
|
|
else
|
|
str = str.slice(1, -1).replace(/``/g, '`');
|
|
}
|
|
return str;
|
|
};
|
|
|
|
ParseNode.stringify = function (tree) {
|
|
if (tree instanceof ParseNode) {
|
|
var str = backtickEscape(tree.name);
|
|
str += '(';
|
|
var escapedChildren = [];
|
|
for(var i = 0, N = tree.children.length; i < N; i++)
|
|
escapedChildren.push(ParseNode.stringify(tree.children[i]));
|
|
str += escapedChildren.join(' ');
|
|
str += ')';
|
|
return str;
|
|
}
|
|
|
|
// Treat a token object or string as a token.
|
|
if (typeof tree.text === 'function')
|
|
tree = tree.text();
|
|
else if (tree.text)
|
|
tree = tree.text;
|
|
return backtickEscape(String(tree));
|
|
};
|
|
|
|
ParseNode.unstringify = function (str) {
|
|
var lexemes = str.match(/\(|\)|`([^`]||``)*`|`|[^\s()`]+/g) || [];
|
|
var N = lexemes.length;
|
|
var state = {
|
|
i: 0,
|
|
getParseError: function (expecting) {
|
|
throw new Error("unstringify: Expecting " + expecting +", found " +
|
|
(lexemes[this.i] || "end of string"));
|
|
},
|
|
peek: function () { return lexemes[this.i]; },
|
|
advance: function () { this.i++; }
|
|
};
|
|
var paren = function (chr) {
|
|
return new Parser(chr, function (t) {
|
|
if (t.peek() !== chr)
|
|
return null;
|
|
t.advance();
|
|
return chr;
|
|
});
|
|
};
|
|
var EMPTY_STRING = [""];
|
|
var token = new Parser('token', function (t) {
|
|
var txt = t.peek();
|
|
if (!txt || txt.charAt(0) === '(' || txt.charAt(0) === ')')
|
|
return null;
|
|
|
|
t.advance();
|
|
// can't return falsy value from successful parser
|
|
return backtickUnescape(txt) || EMPTY_STRING;
|
|
});
|
|
|
|
// Make "item" lazy so it can be recursive.
|
|
var item = Parsers.lazy(function () { return item; });
|
|
|
|
// Parse a single node or token.
|
|
item = Parsers.mapResult(
|
|
Parsers.seq(token,
|
|
Parsers.opt(Parsers.seq(
|
|
paren('('), Parsers.opt(Parsers.list(item)), paren(')')))),
|
|
function (v) {
|
|
for(var i = 0, N = v.length; i < N; i++)
|
|
if (v[i] === EMPTY_STRING)
|
|
v[i] = "";
|
|
|
|
if (v.length === 1)
|
|
// token
|
|
return v[0];
|
|
// node. exclude parens
|
|
return new ParseNode(v[0], v.slice(2, -1));
|
|
});
|
|
|
|
var endOfString = new Parser("end of string", function (t) {
|
|
return t.i === N ? [] : null;
|
|
});
|
|
|
|
return Parsers.seq(item, endOfString).parseRequired(state)[0];
|
|
};
|
|
|
|
})(); |