Browse Source

Better errors, more accurate position info

pull/11/head
nisstyre56 11 years ago
parent
commit
93a7ff3071
  1. 8
      desugar.js
  2. 2
      errors.js
  3. 9
      example.jl
  4. 159
      parse.js
  5. 18
      representation.js
  6. 11
      tokenize.js

8
desugar.js

@ -28,12 +28,16 @@ function desugarDefFunc(def) {
} }
function curryFunc(ps, body) { function curryFunc(ps, body) {
var result;
if (_.isEmpty(ps)) { if (_.isEmpty(ps)) {
return desugar(body); return desugar(body);
} }
else { else {
return new typ.FuncT(desugar(_.first(ps)), result = new typ.FuncT(desugar(_.first(ps)),
curryFunc(_.rest(ps), body)); curryFunc(_.rest(ps), body));
result.charnum = ps.charnum;
result.linenum = ps.linenum;
return result;
} }
} }
@ -73,7 +77,7 @@ function desugar(stx) {
* from normal applications * from normal applications
*/ */
if (!typ.isTypeExpr(stx.p)) { if (!typ.isTypeExpr(stx.p)) {
throw errors.JInternalError("Type application error"); throw errors.JInternalError("Type application error near line " + stx.linenum + " at character #"+stx.charnum);
} }
return sugarTypeApp(stx); return sugarTypeApp(stx);
} }

2
errors.js

@ -11,7 +11,7 @@ function JSyntaxError(linenum, charnum, message) {
this.errormessage = message; this.errormessage = message;
this.stxerror = function() { this.stxerror = function() {
console.log("Syntax Error\n", console.log("Syntax Error\n",
"Line #", this.linenum,"\n", "Line #", this.linenum-2,"\n",
"Near character #", this.charnum, "\n", "Near character #", this.charnum, "\n",
this.errormessage); this.errormessage);
}; };

9
example.jl

@ -1,8 +1,7 @@
defop 2 Left (a ++#$ b) defop 2 Left (a ++ b)
(a - b) (a - b)
(qat :: (T -> 2))
(qat :: (A -> B))
def qat (lambda a b c -> (a + b)) def qat (lambda a b c -> (a + b))
def (add a b) def (add a b)
@ -18,7 +17,7 @@ def strs ["aa", "bb"]
def (mymap f xs) def (mymap f xs)
if ((length xs) == 0) if ((length xs) == 0)
then then
xs xs
else else
((f (head xs)) ((f (head xs))
: (mymap f (tail xs))) : (mymap f (tail xs)))
@ -62,7 +61,7 @@ def main
} (xs ++ [0,9]) } (xs ++ [0,9])
} }
if False if False
then undefined then superduper
else else
(unary + (unary +
fileLines + fileLines +

159
parse.js

@ -10,6 +10,25 @@ var error = require("./errors.js");
var print = console.log; var print = console.log;
function sourcePos(tokens, linenum, charnum) {
if (!tokens || tokens.length === 0) {
return { linenum : linenum,
charnum : charnum
};
}
return {
linenum : fst(tokens)[3],
charnum : fst(tokens)[2]
};
}
function addSrcPos(stx, tokens, linenum, charnum) {
var pos = sourcePos(tokens, linenum, charnum);
stx.linenum = pos.linenum;
stx.charnum = pos.charnum;
return stx;
}
function fst(ts) { function fst(ts) {
return ts[ts.length-1]; return ts[ts.length-1];
} }
@ -68,8 +87,8 @@ function parseMany(parse, exprType, valid, tokens, charnum, linenum) {
parsed = parse(tokens); parsed = parse(tokens);
} }
else { else {
throw error.JSyntaxError(linenum, throw error.JSyntaxError(fst(tokens)[3],
charnum, fst(tokens)[2],
"Unexpected token: ``"+fst(tokens)[0]+"''"); "Unexpected token: ``"+fst(tokens)[0]+"''");
} }
results.push(parsed); results.push(parsed);
@ -88,7 +107,9 @@ function parseMany(parse, exprType, valid, tokens, charnum, linenum) {
current = fst(tokens)[0]; current = fst(tokens)[0];
} }
else { else {
throw error.JSyntaxError(charnum, linenum, "Unexpected end of source"); throw error.JSyntaxError(linenum,
charnum,
"Unexpected end of source");
} }
if (tokens.length <= 1) { if (tokens.length <= 1) {
break; break;
@ -97,8 +118,8 @@ function parseMany(parse, exprType, valid, tokens, charnum, linenum) {
} }
//do the same validity check as before and in the loop //do the same validity check as before and in the loop
if (!fst(tokens)) { if (!fst(tokens)) {
throw error.JSyntaxError(linenum, throw error.JSyntaxError(fst(tokens)[3],
charnum, fst(tokens)[2],
"unexpected end of source"); "unexpected end of source");
} }
if (valid(fst(tokens))) { if (valid(fst(tokens))) {
@ -114,7 +135,7 @@ function parseMany(parse, exprType, valid, tokens, charnum, linenum) {
function parseBetween(exprType, between, tokens, charnum, linenum) { function parseBetween(exprType, between, tokens, charnum, linenum) {
var first = parse(tokens); var first = parse(tokens);
if (!exprType(first)) { if (!exprType(first)) {
throw error.JSyntaxError(charnum, linenum, "Unexpected token: ``"+fst(tokens)[0]+"''"); throw error.JSyntaxError(fst(tokens)[2], fst(tokens)[3], "Unexpected token: ``"+fst(tokens)[0]+"''");
} }
var items = [first]; var items = [first];
var parsed; var parsed;
@ -123,8 +144,8 @@ function parseBetween(exprType, between, tokens, charnum, linenum) {
tokens.pop(); tokens.pop();
parsed = parse(tokens); parsed = parse(tokens);
if (!fst(tokens)) if (!fst(tokens))
throw error.JSyntaxError(linenum, throw error.JSyntaxError(fst(tokens)[3],
charnum, fst(tokens)[2],
"Missing terminator: "+between); "Missing terminator: "+between);
items.push(parsed); items.push(parsed);
} }
@ -133,8 +154,9 @@ function parseBetween(exprType, between, tokens, charnum, linenum) {
return items; return items;
} }
function parseList(tokens) { function parseList(tokens, linenum, charnum) {
var xs; var xs;
var result;
if (fst(tokens)[0] === "right_square") { if (fst(tokens)[0] === "right_square") {
xs = []; xs = [];
} }
@ -153,13 +175,15 @@ function parseList(tokens) {
"list must be terminated by ]"); "list must be terminated by ]");
} }
tokens.pop(); tokens.pop();
return new typ.ListT(xs); result = addSrcPos(new typ.ListT(xs), tokens, linenum, charnum);
return result;
} }
function parseDefFunction(tokens) { function parseDefFunction(tokens, linenum, charnum) {
var fname = parse(tokens); var fname = parse(tokens);
var parameters; var parameters;
var result;
if (fname.exprType !== "Name") { if (fname.exprType !== "Name") {
throw error.JSyntaxError(fst(tokens)[3], throw error.JSyntaxError(fst(tokens)[3],
fst(tokens)[2], fst(tokens)[2],
@ -183,13 +207,16 @@ function parseDefFunction(tokens) {
} }
tokens.pop(); tokens.pop();
var body = parse(tokens); var body = parse(tokens);
return new typ.DefFunc(fname, parameters, body); result = addSrcPos(new typ.DefFunc(fname, parameters, body), tokens, linenum, charnum);
return result;
} }
validLet = makeChecker(["Definition", "FunctionDefinition"].map(formTypeCheck)); validLet = makeChecker(["Definition", "FunctionDefinition"].map(formTypeCheck));
letEnd = _.compose($.not, makeChecker(["right_brace"].map(tokTypeCheck))); letEnd = _.compose($.not, makeChecker(["right_brace"].map(tokTypeCheck)));
function parseLetForm(tokens, linenum, charnum) { function parseLetForm(tokens, linenum, charnum) {
var result;
if (!fst(tokens)) { if (!fst(tokens)) {
error.JSyntaxError(linenum, error.JSyntaxError(linenum,
charnum, charnum,
@ -211,28 +238,28 @@ function parseLetForm(tokens, linenum, charnum) {
charnum, charnum,
"Unexpected end of source"); "Unexpected end of source");
} }
linenum = fst(tokens)[3];
charnum = fst(tokens)[2];
tokens.pop(); tokens.pop();
if (tokens.length <= 0) { if (tokens.length <= 0) {
throw error.JSyntaxError(linenum, throw error.JSyntaxError(fst(tokens)[3],
charnum, fst(tokens)[2],
"let/def form must have a body"); "let/def form must have a body");
} }
var body = parse(tokens); var body = parse(tokens);
if (body.exprType === "Definition" || if (body.exprType === "Definition" ||
body.exprType === "FunctionDefinition") { body.exprType === "FunctionDefinition") {
throw error.JSyntaxError(linenum, throw error.JSyntaxError(fst(tokens)[3],
charnum, fst(tokens)[2],
"Body of a let/def expression cannot be a definition"); "Body of a let/def expression cannot be a definition");
} }
return new typ.LetExp(pairs, body); result = addSrcPos(new typ.LetExp(pairs, body), tokens, linenum, charnum);
return result;
} }
function parseLetFunction(tokens, linenum, charnum) { function parseLetFunction(tokens, linenum, charnum) {
var fname = parse(tokens); var fname = parse(tokens);
var parameters; var parameters;
var result;
if (fname.exprType != "Name") { if (fname.exprType != "Name") {
throw error.JSyntaxError(fst(tokens)[3], throw error.JSyntaxError(fst(tokens)[3],
@ -263,11 +290,14 @@ function parseLetFunction(tokens, linenum, charnum) {
} }
tokens.pop(); tokens.pop();
var body = parse(tokens); var body = parse(tokens);
return new typ.DefFunc(fname, parameters, body); result = addSrcPos(new typ.DefFunc(fname, parameters, body), tokens, linenum, charnum);
return result;
} }
function parseLetBinding(tokens, linenum, charnum) { function parseLetBinding(tokens, linenum, charnum) {
var name = parse(tokens); var name = parse(tokens);
var result;
if (name.exprType != "Name") { if (name.exprType != "Name") {
throw error.JSyntaxError(linenum, throw error.JSyntaxError(linenum,
charnum, charnum,
@ -294,7 +324,8 @@ function parseLetBinding(tokens, linenum, charnum) {
charnum, charnum,
"A definition cannot be the value of a binding"); "A definition cannot be the value of a binding");
} }
return new typ.Def(name, bound); result = addSrcPos(new typ.Def(name, bound), tokens, linenum, charnum);
return result;
} }
function parseLetItem(tokens) { function parseLetItem(tokens) {
@ -312,6 +343,8 @@ function parseLetItem(tokens) {
} }
function parseDef(tokens, linenum, charnum) { function parseDef(tokens, linenum, charnum) {
var result;
if (tokens.length < 2) if (tokens.length < 2)
throw error.JSyntaxError(linenum, throw error.JSyntaxError(linenum,
charnum, charnum,
@ -358,11 +391,15 @@ function parseDef(tokens, linenum, charnum) {
charnum, charnum,
"A definition cannot be the value of a binding"); "A definition cannot be the value of a binding");
} }
return new typ.Def(identifier, bound); result = addSrcPos(new typ.Def(identifier, bound), tokens, linenum, charnum);
return result;
} }
} }
function parseDefOp(tokens, linenum, charnum) { function parseDefOp(tokens, linenum, charnum) {
var result;
var names;
if (fst(tokens)[0] !== "integer" || if (fst(tokens)[0] !== "integer" ||
fst(tokens)[1] < 1) { fst(tokens)[1] < 1) {
throw error.JSyntaxError(linenum, throw error.JSyntaxError(linenum,
@ -400,10 +437,22 @@ function parseDefOp(tokens, linenum, charnum) {
"defop pattern must be terminated with )"); "defop pattern must be terminated with )");
} }
tokens.pop(); tokens.pop();
return new typ.DefFunc(new typ.Name(pattern[1][1]), names = [new typ.Name(pattern[1][1]),
[new typ.Name(pattern[0][1]), new typ.Name(pattern[0][1]),
new typ.Name(pattern[2][1])], new typ.Name(pattern[2][1])];
parse(tokens)); names.map(function(name) {
name.linenum = linenum;
name.charnum = charnum;
return name;
});
result = addSrcPos(new typ.DefFunc(names[0],
names.slice(1,3),
parse(tokens)),
tokens,
linenum,
charnum);
return result;
} }
@ -411,6 +460,7 @@ function parseDefOp(tokens, linenum, charnum) {
function parseIf(tokens) { function parseIf(tokens) {
var linenum = fst(tokens)[3]; var linenum = fst(tokens)[3];
var charnum = fst(tokens)[2]; var charnum = fst(tokens)[2];
var result;
if (!notFollowedBy(tokens, if (!notFollowedBy(tokens,
["def","comma","lambda"], ["def","comma","lambda"],
linenum, linenum,
@ -439,7 +489,8 @@ function parseIf(tokens) {
} }
else { else {
var elseC = parse(tokens); var elseC = parse(tokens);
return new typ.If(ifC, thenC, elseC); result = addSrcPos(new typ.If(ifC, thenC, elseC), tokens, linenum, charnum);
return result;
} }
} }
else { else {
@ -461,6 +512,7 @@ function validFormPar(tok) {
function parseLambda(tokens) { function parseLambda(tokens) {
var linenum = fst(tokens)[2]; var linenum = fst(tokens)[2];
var charnum = fst(tokens)[3]; var charnum = fst(tokens)[3];
var result;
var parameters = parseMany(parse, var parameters = parseMany(parse,
validName, validName,
validFormPar, validFormPar,
@ -474,7 +526,8 @@ function parseLambda(tokens) {
} }
tokens.pop(); tokens.pop();
var body = parse(tokens); var body = parse(tokens);
return new typ.FuncT(parameters, body); result = addSrcPos(new typ.FuncT(parameters, body), tokens, linenum, charnum);
return result;
} }
var invalidArguments = ["def", "comma", "right_paren", "right_square", "right_brace", "left_brace", "right_brace"].map(tokTypeCheck); var invalidArguments = ["def", "comma", "right_paren", "right_square", "right_brace", "left_brace", "right_brace"].map(tokTypeCheck);
@ -498,8 +551,8 @@ function computeApp(tokens, charnum, linenum) {
/* it's an infix expression */ /* it's an infix expression */
result = parseInfix(tokens, 1, lhs, linenum, charnum); result = parseInfix(tokens, 1, lhs, linenum, charnum);
if (!fst(tokens) || fst(tokens)[0] !== "right_paren") { if (!fst(tokens) || fst(tokens)[0] !== "right_paren") {
throw error.JSyntaxError(linenum, throw error.JSyntaxError(fst(tokens)[3],
charnum, fst(tokens)[2],
"Mismatched parentheses or missing parenthesis on right-hand side"); "Mismatched parentheses or missing parenthesis on right-hand side");
} }
else { else {
@ -515,25 +568,26 @@ function computeApp(tokens, charnum, linenum) {
validArgTypes, validArgTypes,
validArgument, validArgument,
tokens, tokens,
charnum, fst(tokens)[2],
linenum); fst(tokens)[3]);
} }
else { else {
parameters = []; parameters = [];
} }
if ((!fst(tokens)) || fst(tokens)[0] !== "right_paren") { if ((!fst(tokens)) || fst(tokens)[0] !== "right_paren") {
throw error.JSyntaxError(linenum, throw error.JSyntaxError(fst(tokens)[3],
charnum, fst(tokens)[2],
"Mismatched parentheses or missing parenthesis on right-hand side"); "Mismatched parentheses or missing parenthesis on right-hand side");
} }
else { else {
tokens.pop(); tokens.pop();
return typ.makeApp(lhs, parameters); return addSrcPos(typ.makeApp(lhs, parameters), tokens, linenum, charnum);
} }
} }
} }
/*Parses infix expressions by precedence climbing /*Parses infix expressions by precedence climbing
* console.log(stx);
See this for more info and an implementation in python See this for more info and an implementation in python
http://eli.thegreenplace.net/2012/08/02/parsing-expressions-by-precedence-climbing/ http://eli.thegreenplace.net/2012/08/02/parsing-expressions-by-precedence-climbing/
*/ */
@ -553,14 +607,14 @@ function parseInfix(tokens, minPrec, lhs, linenum, charnum) {
if (!opinfo || opinfo[0] < minPrec) if (!opinfo || opinfo[0] < minPrec)
break; break;
var op = new typ.Name(cur[1]); var op = addSrcPos(new typ.Name(cur[1]), tokens, linenum, charnum);
var prec = opinfo[0]; var prec = opinfo[0];
var assoc = opinfo[1]; var assoc = opinfo[1];
var nextMinPrec = assoc === "Left" ? prec + 1 : prec; var nextMinPrec = assoc === "Left" ? prec + 1 : prec;
tokens.pop(); tokens.pop();
/*remove the operator token*/ /*remove the operator token*/
var rhs = parseInfix(tokens, nextMinPrec); var rhs = parseInfix(tokens, nextMinPrec);
lhs = typ.makeApp(op, [lhs, rhs]); lhs = addSrcPos(typ.makeApp(op, [lhs, rhs]), tokens, linenum, charnum);
} }
return lhs; return lhs;
} }
@ -569,6 +623,7 @@ function parse(tokens) {
var charnum = fst(tokens)[2]; var charnum = fst(tokens)[2];
var linenum = fst(tokens)[3]; var linenum = fst(tokens)[3];
var toktype; var toktype;
var result;
if (fst(tokens)) { if (fst(tokens)) {
toktype = fst(tokens)[0]; toktype = fst(tokens)[0];
} }
@ -578,35 +633,41 @@ function parse(tokens) {
var token = fst(tokens)[1]; var token = fst(tokens)[1];
tokens.pop(); tokens.pop();
if (toktype === "stringlit") { if (toktype === "stringlit") {
return new typ.StrT(token); result = addSrcPos(new typ.StrT(token), tokens, linenum, charnum);
return result;
} }
else if (toktype === "left_square") { else if (toktype === "left_square") {
return parseList(tokens); return parseList(tokens, linenum, charnum);
} }
else if (toktype === "lambda") { else if (toktype === "lambda") {
return parseLambda(tokens); return parseLambda(tokens);
} }
else if (toktype === "integer") { else if (toktype === "integer") {
return new typ.IntT(token); result = addSrcPos(new typ.IntT(token), tokens, linenum, charnum);
return result;
} }
else if (toktype === "float") { else if (toktype === "float") {
return new typ.FloatT(token); result = addSrcPos(new typ.FloatT(token), tokens, linenum, charnum);
return result;
} }
else if (toktype === "identifier") { else if (toktype === "identifier") {
return new typ.Name(token); result = addSrcPos(new typ.Name(token), tokens, linenum, charnum);
return result;
} }
else if (toktype === "constructor") { else if (toktype === "constructor") {
return new typ.TypeOp(token); result = addSrcPos(new typ.TypeOp(token), tokens, linenum, charnum);
return result;
} }
else if (toktype === "truelit" || toktype === "falselit") { else if (toktype === "truelit" || toktype === "falselit") {
return new typ.BoolT(token); result = addSrcPos(new typ.BoolT(token), tokens, linenum, charnum);
return result;
} }
else if (toktype === "def" || else if (toktype === "def" ||
toktype === "let") { toktype === "let") {
return parseDef(tokens, fst(tokens)[3], fst(tokens)[2]); return parseDef(tokens, linenum, charnum);
} }
else if (toktype === "defop") { else if (toktype === "defop") {
return parseDefOp(tokens, fst(tokens)[3], fst(tokens)[2]); return parseDefOp(tokens, linenum, charnum);
} }
else if (toktype === "ifexp") { else if (toktype === "ifexp") {
return parseIf(tokens); return parseIf(tokens);
@ -619,11 +680,11 @@ function parse(tokens) {
return parsed; return parsed;
} }
else else
return computeApp(tokens, charnum, linenum); return computeApp(tokens, linenum, charnum);
} }
else { else {
throw error.JSyntaxError(fst(tokens)[3], throw error.JSyntaxError(linenum,
fst(tokens)[2], charnum,
"Unexpected token: ``" + toktype+"''"); "Unexpected token: ``" + toktype+"''");
} }
} }

18
representation.js

@ -9,7 +9,9 @@ var Expression = {
type : type :
function () { function () {
return this.exprType; return this.exprType;
} },
linenum : 0,
charnum : 0
}; };
var TypeExpression = { var TypeExpression = {
@ -139,13 +141,15 @@ function ListT(xs) {
return this; return this;
} }
ListT.prototype = Expression;
function Nil() { function Nil() {
this.exprType = "Nil"; this.exprType = "Nil";
return this; return this;
} }
Nil.prototype = Expression; Nil.prototype = Expression;
ListT.prototype = Expression;
function FuncT(p, body) { function FuncT(p, body) {
this.p = p; this.p = p;
@ -176,6 +180,8 @@ function App(func, p) {
return this; return this;
} }
App.prototype = Expression;
// Names are not types // Names are not types
function Name(identifier) { function Name(identifier) {
this.ident = identifier; this.ident = identifier;
@ -184,6 +190,8 @@ function Name(identifier) {
return this; return this;
} }
Name.prototype = Expression;
function Def(ident, exp) { function Def(ident, exp) {
this.ident = ident; this.ident = ident;
this.val = exp; this.val = exp;
@ -191,6 +199,8 @@ function Def(ident, exp) {
return this; return this;
} }
Def.prototype = Expression;
function DefFunc(ident, params, body) { function DefFunc(ident, params, body) {
this.ident = ident; this.ident = ident;
this.val = this.ident; this.val = this.ident;
@ -200,6 +210,8 @@ function DefFunc(ident, params, body) {
return this; return this;
} }
DefFunc.prototype = Expression;
function If(condition, thenexp, elseexp) { function If(condition, thenexp, elseexp) {
this.condition = condition; this.condition = condition;
this.thenexp = thenexp; this.thenexp = thenexp;
@ -208,6 +220,8 @@ function If(condition, thenexp, elseexp) {
return this; return this;
} }
If.prototype = Expression;
function TypeVar(name) { function TypeVar(name) {
this.exprtype = "TypeVar"; this.exprtype = "TypeVar";
this.name = name; this.name = name;

11
tokenize.js

@ -140,18 +140,19 @@ function tokenizeCtor(tokstream,
function tokenizeStr(tokstream, charnum, linenum) { function tokenizeStr(tokstream, charnum, linenum) {
var stringlit = []; var stringlit = [];
var n = 1; var n = 1;
var new_charnum = charnum;
tokstream = tokstream.substr(1); tokstream = tokstream.substr(1);
while (tokstream[0].charCodeAt() !== 34) { while (tokstream[0].charCodeAt() !== 34) {
stringlit.push(tokstream[0]); stringlit.push(tokstream[0]);
tokstream = tokstream.substr(1); tokstream = tokstream.substr(1);
n++; n++;
charnum++; new_charnum++;
if (tokstream.length < 1) { if (tokstream.length < 1) {
throw error.JSyntaxError(linenum, charnum, "Error: missing quotation mark"); throw error.JSyntaxError(linenum, charnum, "Error: missing quotation mark");
} }
} }
n++; n++;
return [n, ["stringlit", stringlit.join(''), charnum, linenum]]; return [n, ["stringlit", stringlit.join(''), new_charnum, linenum]];
} }
@ -200,7 +201,7 @@ function tokenize(tokstream, matchop) {
/* falls through */ /* falls through */
case 10: // '\n' case 10: // '\n'
linenum++; linenum++;
charnum = 1; charnum = 1; /* Reset the character number for each line to 1 */
tokens.push(["whitespace", '\n', charnum, linenum]); tokens.push(["whitespace", '\n', charnum, linenum]);
tokstream = tokstream.substr(1); tokstream = tokstream.substr(1);
break; break;
@ -279,7 +280,7 @@ function tokenize(tokstream, matchop) {
case 105: // 'i' case 105: // 'i'
var ifexp = peek(tokstream, "ifexp", "if"); var ifexp = peek(tokstream, "ifexp", "if");
if (ifexp) { if (ifexp) {
tokens.push($.extend(ifexp, [linenum, charnum])); tokens.push($.extend(ifexp, [charnum, linenum]));
tokstream = tokstream.substr(2); tokstream = tokstream.substr(2);
break; break;
} }
@ -324,7 +325,7 @@ function tokenize(tokstream, matchop) {
case 108: // l case 108: // l
lambda = peek(tokstream, "lambda", "lambda"); lambda = peek(tokstream, "lambda", "lambda");
if (lambda) { if (lambda) {
tokens.push($.extend(lambda, [linenum, charnum])); tokens.push($.extend(lambda, [charnum, linenum]));
tokstream = tokstream.substr(6); tokstream = tokstream.substr(6);
break; break;
} }

Loading…
Cancel
Save