|
|
@ -11,33 +11,33 @@ var error = require("./errors.js"); |
|
|
|
var print = console.log; |
|
|
|
|
|
|
|
function fst(ts) { |
|
|
|
return ts[ts.length-1]; |
|
|
|
return ts[ts.length-1]; |
|
|
|
} |
|
|
|
|
|
|
|
function snd(ts) { |
|
|
|
return ts[ts.length-2]; |
|
|
|
return ts[ts.length-2]; |
|
|
|
} |
|
|
|
|
|
|
|
/*Checks if the next token is not followed by any of ``checks'' */ |
|
|
|
function notFollowedBy(tokens, checks, linenum, charnum) { |
|
|
|
if (!fst(tokens)) { |
|
|
|
if (!fst(tokens)) { |
|
|
|
throw error.JSyntaxError(0,0,"unexpected end of source"); |
|
|
|
} |
|
|
|
var nextT = fst(tokens)[0]; |
|
|
|
if (checks.some(function (x) { |
|
|
|
if (checks.some(function (x) { |
|
|
|
return x === nextT; |
|
|
|
})) |
|
|
|
return false; |
|
|
|
else |
|
|
|
return true; |
|
|
|
return false; |
|
|
|
else |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
/* returns a function that takes a parameter and |
|
|
|
checks if it is in the array ``props''*/ |
|
|
|
function makeChecker(props) { |
|
|
|
return function(x) { |
|
|
|
return x && props.some(function (y) {return y(x);}); |
|
|
|
}; |
|
|
|
return function(x) { |
|
|
|
return x && props.some(function (y) {return y(x);}); |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
function tokTypeCheck(name) { |
|
|
@ -61,43 +61,43 @@ function parseMany(parse, exprType, valid, tokens, charnum, linenum) { |
|
|
|
"Unexpected end of source"); |
|
|
|
} |
|
|
|
var current = fst(tokens)[0]; |
|
|
|
var results = []; |
|
|
|
var parsed; |
|
|
|
|
|
|
|
if (valid(fst(tokens))) { |
|
|
|
parsed = parse(tokens); |
|
|
|
} |
|
|
|
else { |
|
|
|
throw error.JSyntaxError(linenum, |
|
|
|
var results = []; |
|
|
|
var parsed; |
|
|
|
|
|
|
|
if (valid(fst(tokens))) { |
|
|
|
parsed = parse(tokens); |
|
|
|
} |
|
|
|
else { |
|
|
|
throw error.JSyntaxError(linenum, |
|
|
|
charnum, |
|
|
|
"Unexpected token: ``"+fst(tokens)[0]+"''"); |
|
|
|
} |
|
|
|
} |
|
|
|
results.push(parsed); |
|
|
|
|
|
|
|
//make sure there are at least 2 tokens to parse
|
|
|
|
if (tokens.length > 1 && fst(tokens) && valid(fst(tokens))) { |
|
|
|
while (valid(snd(tokens))) { |
|
|
|
if (!(valid(fst(tokens)))) |
|
|
|
//make sure there are at least 2 tokens to parse
|
|
|
|
if (tokens.length > 1 && fst(tokens) && valid(fst(tokens))) { |
|
|
|
while (valid(snd(tokens))) { |
|
|
|
if (!(valid(fst(tokens)))) |
|
|
|
break; |
|
|
|
results.push(parse(tokens)); |
|
|
|
if (!exprType(fst(results).exprType)) |
|
|
|
break; |
|
|
|
if (fst(tokens)) |
|
|
|
if (!exprType(fst(results).exprType)) |
|
|
|
break; |
|
|
|
if (fst(tokens)) |
|
|
|
current = fst(tokens)[0]; |
|
|
|
else |
|
|
|
throw error.JSyntaxError(charnum, linenum, "Unexpected end of source"); |
|
|
|
if (tokens.length <= 1) |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
//do the same validity check as before and in the loop
|
|
|
|
if (tokens.length <= 1) |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
//do the same validity check as before and in the loop
|
|
|
|
if (!fst(tokens)) |
|
|
|
throw error.JSyntaxError(linenum, |
|
|
|
charnum, |
|
|
|
"unexpected end of source"); |
|
|
|
if (valid(fst(tokens))) |
|
|
|
results.push(parse(tokens)); |
|
|
|
return results; |
|
|
|
if (valid(fst(tokens))) |
|
|
|
results.push(parse(tokens)); |
|
|
|
return results; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -149,7 +149,7 @@ function parseList(tokens) { |
|
|
|
|
|
|
|
|
|
|
|
function parseDefFunction(tokens) { |
|
|
|
var fname = parse(tokens); |
|
|
|
var fname = parse(tokens); |
|
|
|
var parameters; |
|
|
|
if (fname.exprType != "Name") { |
|
|
|
throw error.JSyntaxError(fst(tokens)[3], |
|
|
@ -285,7 +285,7 @@ function parseLetBinding(tokens, linenum, charnum) { |
|
|
|
charnum, |
|
|
|
"A definition cannot be the value of a binding"); |
|
|
|
} |
|
|
|
return new typ.Def(name, bound); |
|
|
|
return new typ.Def(name, bound); |
|
|
|
} |
|
|
|
|
|
|
|
function parseLetItem(tokens) { |
|
|
@ -321,12 +321,12 @@ function parseDef(tokens, linenum, charnum) { |
|
|
|
fst(tokens)[2]); |
|
|
|
} |
|
|
|
|
|
|
|
if (notFollowedBy(tokens, ["identifier"], linenum, charnum)) { |
|
|
|
throw error.JSyntaxError(linenum, |
|
|
|
if (notFollowedBy(tokens, ["identifier"], linenum, charnum)) { |
|
|
|
throw error.JSyntaxError(linenum, |
|
|
|
charnum, |
|
|
|
"def must be followed by identifier, not "+fst(tokens)[0]); |
|
|
|
} |
|
|
|
else { |
|
|
|
} |
|
|
|
else { |
|
|
|
var identifier = parse(tokens); |
|
|
|
if (!fst(tokens)) |
|
|
|
throw error.JSyntaxError(linenum, |
|
|
@ -349,8 +349,8 @@ function parseDef(tokens, linenum, charnum) { |
|
|
|
charnum, |
|
|
|
"A definition cannot be the value of a binding"); |
|
|
|
} |
|
|
|
return new typ.Def(identifier, bound); |
|
|
|
} |
|
|
|
return new typ.Def(identifier, bound); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
function parseDefOp(tokens, linenum, charnum) { |
|
|
@ -402,35 +402,35 @@ function parseDefOp(tokens, linenum, charnum) { |
|
|
|
function parseIf(tokens) { |
|
|
|
var linenum = fst(tokens)[3]; |
|
|
|
var charnum = fst(tokens)[2]; |
|
|
|
if (!notFollowedBy(tokens, |
|
|
|
if (!notFollowedBy(tokens, |
|
|
|
["def","comma","lambda"], |
|
|
|
linenum, |
|
|
|
charnum)) { |
|
|
|
throw error.JSyntaxError(linenum, |
|
|
|
throw error.JSyntaxError(linenum, |
|
|
|
charnum, |
|
|
|
"``if'' cannot be followed by "+fst(tokens)[0]) ; |
|
|
|
} |
|
|
|
else { |
|
|
|
var ifC = parse(tokens); |
|
|
|
if (!fst(tokens) || fst(tokens)[0] !== "thenexp") { |
|
|
|
throw error.JSyntaxError(fst(tokens)[3], |
|
|
|
} |
|
|
|
else { |
|
|
|
var ifC = parse(tokens); |
|
|
|
if (!fst(tokens) || fst(tokens)[0] !== "thenexp") { |
|
|
|
throw error.JSyntaxError(fst(tokens)[3], |
|
|
|
fst(tokens)[2], |
|
|
|
"if ``exp'' must be folowed by ``then'' exp, not "+snd(tokens)[0]); |
|
|
|
} |
|
|
|
else { |
|
|
|
tokens.pop(); |
|
|
|
var thenC = parse(tokens); |
|
|
|
else { |
|
|
|
tokens.pop(); |
|
|
|
var thenC = parse(tokens); |
|
|
|
|
|
|
|
if (fst(tokens) && fst(tokens)[0] === "elsexp") { |
|
|
|
tokens.pop(); |
|
|
|
if (fst(tokens) && fst(tokens)[0] === "elsexp") { |
|
|
|
tokens.pop(); |
|
|
|
if (_.size(tokens) < 1) { |
|
|
|
throw error.JSyntaxError(linenum, |
|
|
|
charnum, |
|
|
|
"Unexpected end of source"); |
|
|
|
} |
|
|
|
else { |
|
|
|
var elseC = parse(tokens); |
|
|
|
return new typ.If(ifC, thenC, elseC); |
|
|
|
var elseC = parse(tokens); |
|
|
|
return new typ.If(ifC, thenC, elseC); |
|
|
|
} |
|
|
|
} |
|
|
|
else { |
|
|
@ -452,20 +452,20 @@ function validFormPar(tok) { |
|
|
|
function parseLambda(tokens) { |
|
|
|
var linenum = fst(tokens)[2]; |
|
|
|
var charnum = fst(tokens)[3]; |
|
|
|
var parameters = parseMany(parse, |
|
|
|
var parameters = parseMany(parse, |
|
|
|
validName, |
|
|
|
validFormPar, |
|
|
|
tokens, |
|
|
|
charnum, |
|
|
|
linenum); |
|
|
|
if (fst(tokens)[1] !== "->") { |
|
|
|
throw error.JSyntaxError(fst(tokens)[3], |
|
|
|
if (fst(tokens)[1] !== "->") { |
|
|
|
throw error.JSyntaxError(fst(tokens)[3], |
|
|
|
fst(tokens)[2], |
|
|
|
"arrow must follow parameters in lambda, not "+fst(tokens)[0]); |
|
|
|
} |
|
|
|
tokens.pop(); |
|
|
|
var body = parse(tokens); |
|
|
|
return new typ.FuncT(parameters, body); |
|
|
|
} |
|
|
|
tokens.pop(); |
|
|
|
var body = parse(tokens); |
|
|
|
return new typ.FuncT(parameters, body); |
|
|
|
} |
|
|
|
|
|
|
|
var invalidArguments = ["def", "comma", "right_paren", "right_square", "right_brace", "left_brace", "right_brace"].map(tokTypeCheck); |
|
|
@ -477,31 +477,31 @@ function computeApp(tokens, charnum, linenum) { |
|
|
|
var lhs = parse(tokens); |
|
|
|
var next; |
|
|
|
var result; |
|
|
|
if (fst(tokens)) { |
|
|
|
next = fst(tokens); |
|
|
|
if (fst(tokens)) { |
|
|
|
next = fst(tokens); |
|
|
|
} |
|
|
|
else { |
|
|
|
throw error.JSyntaxError(linenum, |
|
|
|
else { |
|
|
|
throw error.JSyntaxError(linenum, |
|
|
|
charnum, |
|
|
|
"Unexpected end of source"); |
|
|
|
} |
|
|
|
if (typ.OPInfo[next[1]]) { |
|
|
|
/* it's an infix expression */ |
|
|
|
result = parseInfix(tokens, 1, lhs, linenum, charnum); |
|
|
|
if (!fst(tokens) || fst(tokens)[0] !== "right_paren") { |
|
|
|
throw error.JSyntaxError(linenum, |
|
|
|
} |
|
|
|
if (typ.OPInfo[next[1]]) { |
|
|
|
/* it's an infix expression */ |
|
|
|
result = parseInfix(tokens, 1, lhs, linenum, charnum); |
|
|
|
if (!fst(tokens) || fst(tokens)[0] !== "right_paren") { |
|
|
|
throw error.JSyntaxError(linenum, |
|
|
|
charnum, |
|
|
|
"Mismatched parentheses or missing parenthesis on right-hand side"); |
|
|
|
} |
|
|
|
else { |
|
|
|
} |
|
|
|
else { |
|
|
|
tokens.pop(); |
|
|
|
return result; |
|
|
|
} |
|
|
|
} |
|
|
|
else { |
|
|
|
/* it's a prefix application */ |
|
|
|
return result; |
|
|
|
} |
|
|
|
} |
|
|
|
else { |
|
|
|
/* it's a prefix application */ |
|
|
|
var parameters; |
|
|
|
if (fst(tokens)[0] !== "right_paren") { |
|
|
|
if (fst(tokens)[0] !== "right_paren") { |
|
|
|
parameters = parseMany(parse, |
|
|
|
validArgTypes, |
|
|
|
validArgument, |
|
|
@ -512,16 +512,16 @@ function computeApp(tokens, charnum, linenum) { |
|
|
|
else { |
|
|
|
parameters = []; |
|
|
|
} |
|
|
|
if ((!fst(tokens)) || fst(tokens)[0] !== "right_paren") { |
|
|
|
throw error.JSyntaxError(linenum, |
|
|
|
if ((!fst(tokens)) || fst(tokens)[0] !== "right_paren") { |
|
|
|
throw error.JSyntaxError(linenum, |
|
|
|
charnum, |
|
|
|
"Mismatched parentheses or missing parenthesis on right-hand side"); |
|
|
|
} |
|
|
|
else { |
|
|
|
} |
|
|
|
else { |
|
|
|
tokens.pop(); |
|
|
|
return typ.makeApp(lhs, parameters); |
|
|
|
} |
|
|
|
} |
|
|
|
return typ.makeApp(lhs, parameters); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/*Parses infix expressions by precedence climbing |
|
|
@ -529,81 +529,81 @@ function computeApp(tokens, charnum, linenum) { |
|
|
|
http://eli.thegreenplace.net/2012/08/02/parsing-expressions-by-precedence-climbing/
|
|
|
|
*/ |
|
|
|
function parseInfix(tokens, minPrec, lhs, linenum, charnum) { |
|
|
|
if (!lhs) { |
|
|
|
lhs = parse(tokens); |
|
|
|
} |
|
|
|
while (true) { |
|
|
|
var cur = fst(tokens); |
|
|
|
if (!cur) { |
|
|
|
throw error.JSyntaxError(linenum, |
|
|
|
if (!lhs) { |
|
|
|
lhs = parse(tokens); |
|
|
|
} |
|
|
|
while (true) { |
|
|
|
var cur = fst(tokens); |
|
|
|
if (!cur) { |
|
|
|
throw error.JSyntaxError(linenum, |
|
|
|
charnum, |
|
|
|
"Unexpected end of source"); |
|
|
|
} |
|
|
|
var opinfo = typ.OPInfo[cur[1]]; |
|
|
|
|
|
|
|
if (!opinfo || opinfo[0] < minPrec) |
|
|
|
break; |
|
|
|
|
|
|
|
var op = new typ.Name(cur[1]); |
|
|
|
var prec = opinfo[0]; |
|
|
|
var assoc = opinfo[1]; |
|
|
|
var nextMinPrec = assoc === "Left" ? prec + 1 : prec; |
|
|
|
tokens.pop(); |
|
|
|
/*remove the operator token*/ |
|
|
|
var rhs = parseInfix(tokens, nextMinPrec); |
|
|
|
lhs = typ.makeApp(op, [lhs, rhs]); |
|
|
|
} |
|
|
|
return lhs; |
|
|
|
} |
|
|
|
var opinfo = typ.OPInfo[cur[1]]; |
|
|
|
|
|
|
|
if (!opinfo || opinfo[0] < minPrec) |
|
|
|
break; |
|
|
|
|
|
|
|
var op = new typ.Name(cur[1]); |
|
|
|
var prec = opinfo[0]; |
|
|
|
var assoc = opinfo[1]; |
|
|
|
var nextMinPrec = assoc === "Left" ? prec + 1 : prec; |
|
|
|
tokens.pop(); |
|
|
|
/*remove the operator token*/ |
|
|
|
var rhs = parseInfix(tokens, nextMinPrec); |
|
|
|
lhs = typ.makeApp(op, [lhs, rhs]); |
|
|
|
} |
|
|
|
return lhs; |
|
|
|
} |
|
|
|
|
|
|
|
function parse(tokens) { |
|
|
|
var charnum = fst(tokens)[2]; |
|
|
|
var linenum = fst(tokens)[3]; |
|
|
|
var toktype; |
|
|
|
if (fst(tokens)) { |
|
|
|
toktype = fst(tokens)[0]; |
|
|
|
if (fst(tokens)) { |
|
|
|
toktype = fst(tokens)[0]; |
|
|
|
} |
|
|
|
else { |
|
|
|
process.exit(code=1); |
|
|
|
} |
|
|
|
var token = fst(tokens)[1]; |
|
|
|
tokens.pop(); |
|
|
|
if (toktype === "stringlit") { |
|
|
|
return new typ.StrT(token); |
|
|
|
else { |
|
|
|
process.exit(code=1); |
|
|
|
} |
|
|
|
var token = fst(tokens)[1]; |
|
|
|
tokens.pop(); |
|
|
|
if (toktype === "stringlit") { |
|
|
|
return new typ.StrT(token); |
|
|
|
} |
|
|
|
else if (toktype === "left_square") { |
|
|
|
return parseList(tokens); |
|
|
|
} |
|
|
|
else if (toktype === "lambda") { |
|
|
|
return parseLambda(tokens); |
|
|
|
else if (toktype === "lambda") { |
|
|
|
return parseLambda(tokens); |
|
|
|
} |
|
|
|
else if (toktype === "integer") { |
|
|
|
return new typ.IntT(token); |
|
|
|
else if (toktype === "integer") { |
|
|
|
return new typ.IntT(token); |
|
|
|
} |
|
|
|
else if (toktype === "float") { |
|
|
|
return new typ.FloatT(token); |
|
|
|
else if (toktype === "float") { |
|
|
|
return new typ.FloatT(token); |
|
|
|
} |
|
|
|
else if (toktype === "identifier") { |
|
|
|
else if (toktype === "identifier") { |
|
|
|
return new typ.Name(token); |
|
|
|
} |
|
|
|
else if (toktype === "constructor") { |
|
|
|
return new typ.TypeOp(token); |
|
|
|
} |
|
|
|
else if (toktype === "truelit" || toktype === "falselit") { |
|
|
|
return new typ.BoolT(token); |
|
|
|
else if (toktype === "truelit" || toktype === "falselit") { |
|
|
|
return new typ.BoolT(token); |
|
|
|
} |
|
|
|
else if (toktype === "def" || |
|
|
|
else if (toktype === "def" || |
|
|
|
toktype === "let") { |
|
|
|
return parseDef(tokens, fst(tokens)[3], fst(tokens)[2]); |
|
|
|
return parseDef(tokens, fst(tokens)[3], fst(tokens)[2]); |
|
|
|
} |
|
|
|
else if (toktype === "defop") { |
|
|
|
return parseDefOp(tokens, fst(tokens)[3], fst(tokens)[2]); |
|
|
|
} |
|
|
|
else if (toktype === "ifexp") { |
|
|
|
return parseIf(tokens); |
|
|
|
else if (toktype === "ifexp") { |
|
|
|
return parseIf(tokens); |
|
|
|
} |
|
|
|
else if (toktype === "left_paren") { |
|
|
|
if (fst(tokens)[0] === "lambda") { |
|
|
|
else if (toktype === "left_paren") { |
|
|
|
if (fst(tokens)[0] === "lambda") { |
|
|
|
tokens.pop(); |
|
|
|
var parsed = parseLambda(tokens); |
|
|
|
tokens.pop(); |
|
|
|