hopper
Version:
An interpreter for the Grace programming language
1,250 lines (979 loc) • 33.2 kB
JavaScript
// Provides the 'parse' function, which transforms a list of lexed tokens into a
// list of Grace AST nodes.
"use strict";
var Task, ast, error, lexer, lookahead, tokens, util;
Task = require("./task");
ast = require("./ast");
error = require("./parser/error");
lexer = require("./parser/lexer");
tokens = require("./parser/tokens");
util = require("./util");
function isMathOperator(op) {
return op === "^" || op === "/" || op === "*" || op === "+" || op === "-";
}
function precedence(lhs, rhs) {
var left, right;
left = lhs.value;
right = rhs.value;
if (left === right) {
return true;
}
if (!isMathOperator(left) || !isMathOperator(right)) {
error.raise(lhs, "Mismatched operators " + left + " and " + right);
}
return left === "^" || (left === "/" || left === "*") && right !== "^" ||
(left === "+" || left === "-") && (right === "+" || right === "-");
}
function slice(ctx, from, to) {
return Array.prototype.slice.call(ctx, from, to);
}
lookahead = {
"keyword": function (value, parser) {
return this.value(tokens.Keyword, value, parser);
},
"symbol": function (value, parser) {
return this.value(tokens.Symbol, value, parser);
},
"punctuation": function (value, parser) {
return this.value(tokens.Punctuation, value, parser);
},
"newline": function (parser) {
parser.test = function () {
var token = this.peek("newline");
return token &&
(token.constructor === tokens.Newline || token.value === ";");
};
return parser;
},
"identifier": function (parser) {
return this.type(tokens.Identifier, parser);
},
"operator": function (parser) {
return this.type(tokens.Symbol, parser);
},
"string": function (parser) {
return this.type(tokens.StringLiteral, parser);
},
"number": function (parser) {
return this.type(tokens.NumberLiteral, parser);
},
"value": function (type, value, parser) {
parser.test = function () {
var token = this.peek(type);
return token.constructor === type &&
(typeof value === "string" && token.value === value ||
typeof value === "function" && value(token.value));
};
return parser;
},
"type": function (type, parser) {
parser.test = function (value) {
var token = this.peek(type);
return token.constructor === type &&
(typeof value !== "string" || token.value === value);
};
return parser;
},
"name": function (parser) {
parser.test = function (value) {
var token, type;
token = this.peek();
type = token.constructor;
return (type === tokens.Identifier || type === tokens.Symbol) &&
(typeof value !== "string" || token.value === value);
};
return parser;
},
"parsers": function (name) {
var after, i, l, parser, parsers;
function run(test, failure) {
return function () {
var pName;
function then(result) {
return after ? after.call(this, result) : result;
}
for (i = 0; i < l; i += 1) {
pName = parsers[i];
if (this.test(pName)) {
if (test) {
return test;
}
return this.one(pName).then(then);
}
}
return failure.call(this);
};
}
l = arguments.length;
if (typeof arguments[l - 1] === "function") {
after = arguments[l - 1];
l -= 1;
}
parsers = Array.prototype.slice.call(arguments, 1, l);
l = parsers.length;
parser = run(false, function () {
this.raise(name);
});
parser.test = run(true, function () {
return false;
});
return parser;
}
};
function Parser(lex) {
this.lexer = lex;
this.indent = 0;
this.token = null;
}
util.inherits(Parser, Task.Async);
Parser.prototype.module = function () {
return this.lone("dialect").then(function (dialect) {
return this.any("import").then(function (imports) {
if (dialect !== null) {
imports.unshift(dialect);
}
return imports;
});
}).then(function (head) {
return this.objectBody().then(function (body) {
return head.concat(body);
});
});
};
Parser.prototype.newline = lookahead.newline(function () {
var indent, token;
token = this.peek("newline");
// A close brace counts as an implicit newline and may change indentation,
// otherwise indentation must match.
if (token.value !== "}") {
if (token.value === ";") {
this.poll();
} else if (token.constructor !== tokens.EndOfInput) {
token = this.poll();
if (token.constructor !== tokens.Newline) {
error.raise(token, "Unexpected appearance of " + token);
}
indent = token.indent;
if (indent !== this.indent && this.peek().value !== "}") {
error.raise(token, "Indent must match previous line");
}
}
}
});
Parser.prototype.def = lookahead.keyword("def", function () {
var ident, token;
token = this.keyword("def");
ident = this.identifier();
this.inDef = true;
return this.on("symbol", ":", function () {
return this.expression();
}).then(function (pattern) {
return this.lone("annotations").then(function (annotations) {
this.inDef = false;
if (this.test("symbol", ":=")) {
error.raise(this.poll(), "A constant declaration must use " +
new tokens.Symbol("=") + " instead of " + new tokens.Symbol(":="));
}
if (!this.test("symbol", "=")) {
error.raise(this.poll(), "A constant declaration must have " +
new tokens.Symbol("=") + " and a value");
}
this.symbol("=");
return this.expression().then(function (value) {
this.newline();
return new ast.Def(ident, pattern, annotations || [], value, token);
});
});
});
});
Parser.prototype["var"] = lookahead.keyword("var", function () {
var ident, token;
token = this.keyword("var");
ident = this.identifier();
return this.on("symbol", ":", function () {
return this.strict(this.expression);
}).then(function (pattern) {
return this.lone("annotations", true).then(function (annotations) {
if (this.test("symbol", "=")) {
error.raise(this.poll(), "A variable declaration must use " +
new tokens.Symbol(":=") + " instead of " + new tokens.Symbol("="));
}
return this.on("symbol", ":=", function () {
return this.expression();
}).then(function (value) {
this.newline();
return new ast.Var(ident, pattern, annotations || [], value, token);
});
});
});
});
Parser.prototype.declOrLiteral = lookahead.keyword("type", function () {
return this.attempt(function () {
var keyword = this.keyword("type");
if (this.test("punctuation", "{")) {
// Whoops, we thought this was a declaration but it's actually a literal.
// Push the keyword back and reparse as an expression line.
error.raise("Attempt to parse type literal as type declaration");
}
return keyword;
}).then(function (token) {
var name;
if (token === null) {
return this.expressionLine();
}
name = this.identifier();
this.inDef = true;
return this.on("symbol", "<", function () {
return this.commas("identifier").then(function (generics) {
this.symbol(">");
return generics;
});
}).then(function (generics) {
return this.lone("annotations").then(function (annotations) {
this.inDef = false;
this.symbol("=");
return this.lone("typeBraces").then(function (type) {
return type || this.expression();
}).then(function (value) {
this.newline();
return new ast.TypeDeclaration(name,
generics || [], annotations || [], value, token);
});
});
});
});
});
Parser.prototype.type = lookahead.keyword("type", function () {
this.keyword("type");
return this.typeBraces();
});
Parser.prototype.typeBraces = lookahead.punctuation("{", function () {
return this.braces(function (token) {
return this.any("typeBody").then(function (body) {
return new ast.Type(body, token);
});
});
});
Parser.prototype.typeBody = lookahead.parsers("signature", "signature",
function (signature) {
this.newline();
return signature;
});
Parser.prototype.object = lookahead.keyword("object", function () {
var token = this.keyword("object");
return this.lone("annotations", true).then(function (annotations) {
return this.braces(function () {
return this.objectBody().then(function (body) {
return new ast.ObjectConstructor(annotations || [], body, token);
});
});
});
});
Parser.prototype["class"] = lookahead.keyword("class", function () {
var name, token;
token = this.keyword("class");
name = this.identifier();
this.punctuation(".");
return this.methodRest("objectBody", function (signature, annotations, body) {
return new ast.Class(name, signature, annotations, body, token);
});
});
Parser.prototype.method = lookahead.keyword("method", function () {
var token = this.keyword("method");
return this.methodRest("methodBody", function (signature, annotations, body) {
return new ast.Method(signature, annotations, body, token);
});
});
Parser.prototype.constructor = lookahead.keyword("constructor", function () {
var token = this.keyword("constructor");
return this.methodRest("objectBody", function (signature, annotations, body) {
return new ast.Method(signature, annotations,
[new ast.ObjectConstructor([], body, token)], token);
});
});
Parser.prototype.methodRest = lookahead.name(function (parser, make) {
return this.signature().then(function (signature) {
return this.lone("annotations").then(function (annotations) {
annotations = annotations || [];
return this.braces(function () {
return this.one(parser).then(function (result) {
return make.call(this, signature, annotations, result);
});
});
});
});
});
Parser.prototype.signature = lookahead.name(function () {
return this.signaturePartFirst().then(function (first) {
return this.task(function () {
if (first.parameters.length === 0 || first.name.isOperator) {
return [first];
}
return this.any("signaturePartRest").then(function (rest) {
rest.unshift(first);
return rest;
});
}).then(function (parts) {
return this.on("symbol", "->", function () {
return this.strict(this.expression);
}).then(function (pattern) {
return new ast.Signature(parts, pattern, first);
});
});
});
});
Parser.prototype.signaturePartFirst = lookahead.name(function () {
return this.lone("operator").then(function (operator) {
if (operator === null) {
return this.identifier();
}
return operator;
}).then(function (name) {
if (!name.isOperator) {
if (name.value === "prefix") {
return this.on("operator", function (operator) {
name.isOperator = true;
name.value += operator.value;
}).then(function () {
return new ast.SignaturePart(name, [], []);
});
}
if (this.test("symbol", ":=")) {
this.poll();
name.isOperator = true;
name.value += " :=";
return this.parentheses(this.parameter).then(function (parameter) {
return new ast.SignaturePart(name, [], [parameter]);
});
}
}
return this.signaturePartPost(name, true);
});
});
Parser.prototype.signaturePartRest = lookahead.identifier(function () {
var name = this.identifier();
return this.signaturePartPost(name, false);
});
Parser.prototype.signaturePartPost = function (name, first) {
return this.task(function () {
if (!name.isOperator) {
return this.on("symbol", "<", function () {
return this.commas("identifier").then(function (generics) {
this.symbol(">");
return generics;
});
});
}
}).then(function (generics) {
return this[first ? "lone" : "one"]("parentheses", function () {
if (name.isOperator) {
return this.parameter().then(function (parameter) {
return [parameter];
});
}
return this.commas("parameter");
}).then(function (parameters) {
return new ast.SignaturePart(name, generics || [], parameters || []);
});
});
};
Parser.prototype.parameter =
lookahead.parsers("parameter", "vararg", "binding");
Parser.prototype.vararg = lookahead.symbol("*", function () {
var token = this.symbol("*");
return this.parameterName().then(function (name) {
return this.parameterType().then(function (type) {
return new ast.Parameter(name, type, true, token);
});
});
});
Parser.prototype.binding =
lookahead.parsers("parameter", "parameterName", function (name) {
return this.parameterType().then(function (type) {
return new ast.Parameter(name, type, false, name);
});
});
Parser.prototype.parameterName =
lookahead.parsers("parameter", "identifier", "underscore");
Parser.prototype.parameterType = function () {
return this.on("symbol", ":", function () {
return this.expression();
});
};
// Require one or more of the given parsings, separated by commas.
Parser.prototype.commas = function (parser) {
function comma(results) {
return this.on("punctuation", ",", function () {
return this.one(parser).then(function (result) {
results.push(result);
return comma.call(this, results);
});
}).then(function (next) {
return next || results;
});
}
return this.one(parser).then(function (first) {
return comma.call(this, [first]);
});
};
Parser.prototype.braces = lookahead.punctuation("{", function (f) {
var state = this.indent;
return this.wrapped("{", "}", function (token) {
this.postBraceIndent();
return this.resolve(f.call(this, token));
}).then(function (result) {
this.indent = state;
return result;
});
});
Parser.prototype.postBraceIndent = function () {
var indent, next;
next = this.peek("newline");
if (next.constructor === tokens.Newline) {
next = this.poll();
indent = next.indent;
if (indent < this.indent && this.peek().value !== "}") {
error.raise(next, "Invalid indent following opening brace");
}
this.indent = indent;
}
};
Parser.prototype.parentheses = lookahead.punctuation("(", function (f) {
return this.wrapped("(", ")", function () {
return this.resolve((f || this.expression).call(this))
.then(function (expr) {
return this.lone("newline").then(function () {
return expr;
});
});
});
});
Parser.prototype.wrapped = function (o, c, f) {
return this.resolve(f.call(this, this.punctuation(o)))
.then(function (result) {
var token;
if (!this.test("punctuation", c)) {
token = this.poll();
error.raise(token, "Unexpected appearance of " + token);
}
this.punctuation(c);
return result;
});
};
Parser.prototype.dialect = lookahead.keyword("dialect", function () {
var token = this.keyword("dialect");
return this.string().then(function (path) {
this.newline();
return new ast.Dialect(path, token);
});
});
Parser.prototype["import"] = lookahead.keyword("import", function () {
var token = this.keyword("import");
return this.string().then(function (path) {
var ident;
this.contextualKeyword("as");
ident = this.identifier();
this.newline();
return new ast.Import(path, ident, token);
});
});
Parser.prototype.inherits = lookahead.keyword("inherits", function () {
var token = this.keyword("inherits");
return this.expression().then(function (request) {
if (request.constructor !== ast.UnqualifiedRequest &&
request.constructor !== ast.QualifiedRequest &&
request.constructor !== ast.BooleanLiteral) {
this.raise("request", request);
}
this.newline();
return new ast.Inherits(request, token);
});
});
Parser.prototype["return"] = lookahead.keyword("return", function () {
var token = this.keyword("return");
return this.lone("expression").then(function (expression) {
this.newline();
return new ast.Return(expression, token);
});
});
Parser.prototype.statement = lookahead.parsers("statement",
"def", "var", "declOrLiteral", "return", "expressionLine", "newline");
Parser.prototype.expression = lookahead.parsers("expression",
"preBinaryOperator", function (expression) {
var token, which;
function buildBinary(lhs, op, rhs) {
return new ast.QualifiedRequest(lhs,
[new ast.RequestPart(op, [], [rhs])]);
}
// Parse trailing binary operator requests.
function operators(lhs, lop, rhs) {
return this.on("operator", function (rop) {
return this.preBinaryOperator().then(function (pre) {
if (precedence(lop, rop)) {
return operators.call(this, buildBinary(lhs, lop, rhs), rop, pre);
}
return operators.call(this, lhs, lop, buildBinary(rhs, rop, pre));
});
}).then(function (op) {
return op || buildBinary(lhs, lop, rhs);
});
}
// Avoid consuming generic closing parameters.
if (this.generics && this.peek().value[0] === ">") {
return expression;
}
if (!this.inDef && this.test("symbol", "=")) {
token = this.poll();
error.raise(token, "Assignment must use " +
new tokens.Symbol(":=") + ", not " + token);
}
// In these cases, the node *must* be a receiver in an operator request,
// otherwise it's optional.
which = expression.constructor === ast.Outer ||
expression.constructor === ast.Super ? "one" : "lone";
return this[which]("operator").then(function (op) {
return op && this.preBinaryOperator().then(function (pre) {
return operators.call(this, expression, op, pre);
});
}).then(function (request) {
return request || expression;
});
});
Parser.prototype.receiver = lookahead.parsers("expression",
"object", "type", "unqualifiedRequest", "literal", "bool",
"self", "super", "outer", "parentheses", "prefixOperator");
// Parse an expression up to a binary operator.
Parser.prototype.preBinaryOperator = lookahead.parsers("expression", "receiver",
function (expression) {
// Parse trailing dot requests.
function requests(receiver) {
return this.on("dotRequest", function (signature) {
return requests.call(this,
new ast.QualifiedRequest(receiver, signature));
}).then(function (request) {
return request || receiver;
});
}
return requests.call(this, expression);
});
// Expressions may appear alone on a single line, in which case they become a
// statement.
Parser.prototype.expressionLine = lookahead.parsers("expression line",
"expression", function (expression) {
this.newline();
return expression;
});
Parser.prototype.bool = lookahead.parsers("boolean", "true", "false");
Parser.prototype["true"] = lookahead.keyword("true", function () {
return new ast.BooleanLiteral(true, this.keyword("true"));
});
Parser.prototype["false"] = lookahead.keyword("false", function () {
return new ast.BooleanLiteral(false, this.keyword("false"));
});
Parser.prototype.prefixOperator = lookahead.operator(function () {
var prefix = this.operator();
prefix.value = "prefix" + prefix.value;
return this.receiver().then(function (receiver) {
return new ast.QualifiedRequest(receiver,
[new ast.RequestPart(prefix, [], [])]);
});
});
// Parse a request with no receiver.
Parser.prototype.unqualifiedRequest = lookahead.identifier(function () {
return this.requestSignature().then(function (signature) {
return new ast.UnqualifiedRequest(signature);
});
});
// Parse the signature part of a request, resulting in a list of signature
// parts.
Parser.prototype.postReceiver = lookahead.parsers("request signature",
"dotRequest", "binaryRequestSignature");
// Parse a dot-requested signature.
Parser.prototype.dotRequest = lookahead.punctuation(".", function () {
this.punctuation(".");
return this.requestSignature();
});
// Parse a request signature whose parts are identifiers.
Parser.prototype.requestSignature = lookahead.identifier(function () {
return this.requestPart(false).then(function (first) {
if (first.arguments.length === 0) {
return [first];
}
return this.any("requestPart", true).then(function (parts) {
parts.unshift(first);
return parts;
});
});
});
Parser.prototype.requestPart = lookahead.identifier(function (required) {
var name = this.identifier();
return this.task(function () {
var state;
if (this.test("symbol", "<") && !this.peek().spaced) {
state = this.generics;
return this.attempt(function () {
this.symbol("<");
this.generics = true;
return this.commas("expression").then(function (types) {
var after, next;
next = this.peek();
if (next.value[0] === ">" && next.value.length > 1) {
// The lexer got confused and attached the closing chevron to some
// following symbols. Rip out the chevron and leave the symbols.
next.value = next.value.substring(1);
} else {
this.symbol(">");
}
after = this.peek();
if (after.constructor === tokens.Identifier ||
after.constructor === tokens.Keyword && after.value !== "is" &&
after.value !== "true" && after.value !== "false") {
error.raise(after, "Invalid token following generic parameters");
}
return types;
});
}).then(function (generics) {
this.generics = state;
return generics;
});
}
}).then(function (generics) {
return this.on(this.isStrict ? "strictLiteral" : "literal", function (arg) {
if (arg.constructor !== ast.Block && this.test("punctuation", ".")) {
error.raise(this.punctuation("."),
"Method requests on literal parameters must be wrapped");
}
return [arg];
}).then(function (args) {
if (!required && !this.isStrict && args === null) {
return this.on("symbol", ":=", function () {
name.isOperator = true;
name.value += " :=";
return this.expression().then(function (expression) {
return [expression];
});
});
}
return args;
}).then(function (args) {
if (args === null) {
return this[required ? "one" : "lone"]("parentheses", function () {
return this.commas("expression");
});
}
return args;
}).then(function (args) {
return new ast.RequestPart(name, generics || [], args || []);
});
});
});
// Parse the signature of a binary operator request.
Parser.prototype.binaryRequestSignature = lookahead.operator(function () {
var operator = this.operator();
return this.expression().then(function (rhs) {
return [new ast.RequestPart(operator, [], [rhs])];
});
});
Parser.prototype.self = lookahead.keyword("self", function () {
return new ast.Self(this.keyword("self"));
});
Parser.prototype["super"] = lookahead.keyword("super", function () {
return new ast.Super(this.keyword("super"));
});
Parser.prototype.outer = lookahead.keyword("outer", function () {
return new ast.Outer(this.keyword("outer"));
});
Parser.prototype.block = lookahead.punctuation("{", function () {
return this.braces(function (token) {
return this.attempt(function () {
return this.task(function () {
if (!this.test("identifier") && !this.test("punctuation", "_")) {
return this.expression().then(function (params) {
return [
new ast.Parameter(new ast.Identifier("_", false, params),
params, false, params)
];
});
}
return this.commas("parameter");
}).then(function (params) {
this.symbol("->");
this.postBraceIndent();
return params;
});
}).then(function (params) {
return this.any("statement").then(function (body) {
return new ast.Block(params || [], body, token);
});
});
});
});
Parser.prototype.annotations = lookahead.keyword("is", function (isStrict) {
this.keyword("is");
return this.strict(function () {
return this.commas("expression");
}, isStrict);
});
Parser.prototype.literal =
lookahead.parsers("literal", "strictLiteral", "block");
Parser.prototype.strictLiteral =
lookahead.parsers("literal", "bool", "string", "number");
Parser.prototype.string = lookahead.string(function () {
var concat, string, token;
token = this.expect(tokens.StringLiteral);
string = new ast.StringLiteral(token.value, token);
if (token.interpolation) {
concat = new ast.Identifier("++", true, token);
return this.expression().then(function (expression) {
var interpolation = new ast.QualifiedRequest(string,
[new ast.RequestPart(concat, [], [expression])]);
// The newline allows the string to return to its previous indentation.
this.lone("newline");
this.punctuation("}");
this.token = this.lexer.nextToken(true);
return this.string().then(function (rest) {
return new ast.QualifiedRequest(interpolation,
[new ast.RequestPart(concat, [], [rest])]);
});
});
}
return this.resolve(string);
});
Parser.prototype.number = lookahead.number(function () {
var base, token, value, x;
token = this.expect(tokens.NumberLiteral);
value = token.value;
x = value.match(/[xX]/);
if (x !== null) {
base = Number(value.substring(0, x.index));
if (base > 1 && base < 37) {
value = parseInt(value.substring(x.index + 1), base);
}
}
return new ast.NumberLiteral(value, token);
});
Parser.prototype.objectBody = function () {
return this.lone("inherits").then(function (inherits) {
return this.any("statementOrMethod").then(function (body) {
if (inherits !== null) {
body.unshift(inherits);
}
return body;
});
});
};
Parser.prototype.methodBody = function () {
return this.any("statement");
};
Parser.prototype.statementOrMethod =
lookahead.parsers("statement", "method", "class", "constructor", "statement");
// Expect and consume a certain keyword.
Parser.prototype.keyword = lookahead.type(tokens.Keyword, function (key) {
var token = this.expect(tokens.Keyword, key);
if (token.value !== key) {
this.raise("keyword " + key, token);
}
return token;
});
// Expect and parse the given identifier as a keyword.
Parser.prototype.contextualKeyword = lookahead.type(tokens.Identifier,
function (key) {
var token = this.expect(tokens.Identifier, key);
if (token.value !== key) {
this.raise("keyword " + key, token);
}
return token;
});
// Expect and consume a certain symbol.
Parser.prototype.symbol = lookahead.type(tokens.Symbol, function (sym) {
var token = this.expect(tokens.Symbol, sym);
if (token.value !== sym) {
this.raise("symbol " + sym, token);
}
return token;
});
// Expect and consume a certain piece of punctuation.
Parser.prototype.punctuation = lookahead.type(tokens.Punctuation,
function (sym) {
var token = this.expect(tokens.Punctuation, sym);
if (token.value !== sym) {
this.raise(new tokens.Punctuation(sym, null), token);
}
return token;
});
// Expect and parse an operator.
Parser.prototype.operator = lookahead.value(tokens.Symbol, function (symbol) {
return symbol !== "=" && symbol !== "->" && symbol !== ":=" && symbol !== ":";
}, function () {
var token = this.expect(tokens.Symbol, "operator");
return new ast.Identifier(token.value, true, token);
});
// Expect and parse an identifier.
Parser.prototype.identifier = lookahead.identifier(function () {
var token = this.expect(tokens.Identifier);
return new ast.Identifier(token.value, false, token);
});
Parser.prototype.underscore = lookahead.punctuation("_", function () {
var token = this.punctuation("_");
return new ast.Identifier("_", false, token);
});
// Expect a certain type of token, throwing away newlines in between. May be
// provided with a second type which will be used instead of the first for
// error reporting.
Parser.prototype.expect = function (Type, etype) {
var token;
if (Type !== tokens.Newline) {
this.trim();
}
token = this.poll();
if (token === null || token.constructor !== Type) {
if (typeof etype === "string") {
etype = new Type(etype, token.location);
}
this.raise(etype || Type, token);
}
return token;
};
// Trim out leading newlines from the token queue whose indent is greater than
// the current indent.
Parser.prototype.trim = function () {
var token = this.peek("newline");
while (token.constructor === tokens.Newline && token.indent > this.indent) {
this.poll();
token = this.peek("newline");
}
};
// Poll the token queue, removing and returning the first element.
Parser.prototype.poll = function () {
var token = this.token;
if (token !== null) {
if (token.constructor !== tokens.EndOfInput) {
this.token = null;
}
} else {
token = this.lexer.nextToken();
this.token = token;
}
return token;
};
// Peek at the token queue, returning the first element, skipping over
// newlines whose indent is greater than the current indent. Optionally takes
// the type of the token to search for, to avoid skipping over newlines when
// newlines are being searched for.
Parser.prototype.peek = function (type) {
var lex, token;
token = this.token;
if (token !== null) {
return this.token;
}
lex = this.lexer;
token = lex.nextToken();
if (type !== "newline") {
while (token.constructor === tokens.Newline && token.indent > this.indent) {
token = lex.nextToken();
}
}
this.token = token;
return token;
};
Parser.prototype.raise = function (type, token) {
if (token === undefined) {
token = this.peek();
}
error.raise(token, "Expected " + type + ", but found " + token);
};
Parser.prototype.test = function (parser) {
return this[parser].test.apply(this, slice(arguments, 1));
};
Parser.prototype.one = function (parser) {
return this.resolve(this[parser].apply(this, slice(arguments, 1)));
};
Parser.prototype.lone = function () {
return this.test.apply(this, arguments) ?
this.one.apply(this, arguments) : this.resolve(null);
};
Parser.prototype.any = function () {
var args = arguments;
function any(results) {
if (this.test.apply(this, args)) {
return this.one.apply(this, args).then(function (result) {
if (typeof result === "object") {
results.push(result);
}
return any.call(this, results);
});
}
return this.resolve(results);
}
return any.call(this, []);
};
Parser.prototype.many = function () {
return this.one.apply(this, arguments).then(function (result) {
return this.any.apply(this, arguments).then(function (results) {
results.unshift(result);
return results;
});
});
};
Parser.prototype.on = function () {
var args, l;
l = arguments.length - 1;
args = slice(arguments, 0, l);
if (this.test.apply(this, args)) {
return this.one.apply(this, args).then(arguments[l]);
}
return this.resolve(null);
};
Parser.prototype.attempt = function (f) {
var lex, token;
lex = this.lexer;
token = this.token;
this.lexer = lex.clone();
return this.task(function () {
return f.call(this);
}).then(null, function () {
this.lexer = lex;
this.token = token;
return null;
});
};
Parser.prototype.strict = function (func, isStrict) {
var state = this.isStrict;
this.isStrict = isStrict === false ? false : true;
return this.resolve(func.call(this)).then(function (result) {
this.isStrict = state;
return result;
});
};
// Parse a token stream.
function runParser(code) {
var parser, token;
try {
parser = new Parser(new lexer.Lexer(code));
while (parser.peek().constructor === tokens.Newline) {
parser.poll();
}
return parser.module().then(function (module) {
do {
token = parser.poll();
} while (token.constructor !== tokens.EndOfInput &&
token.constructor === tokens.Newline);
if (token.constructor !== tokens.EndOfInput) {
error.raise(token, "Unexpected appearance of " + token);
}
return module;
}).bind(null);
} catch (reason) {
return Task.reject(reason);
}
}
// Parse the code at the given path.
function parse(code, path) {
return runParser(code).then(null, function (reason) {
reason.module = path;
throw reason;
});
}
exports.parse = parse;
exports.ParseError = error.ParseError;
exports.isSymbol = lexer.isSymbol;