/**
* Authors: k.inaba
* License: NYSL 0.9982 http://www.kmonos.net/nysl/
*
* Parser for Polemy programming language
*/
module polemy.parse;
import polemy._common;
import polemy.lex;
import polemy.ast;
///
class ParseException : Exception
{
mixin ExceptionWithPosition;
}
/// Entry points of this module
AST parseString(S, T...)(S str, T fn_ln_cn)
{ return parserFromString(str, fn_ln_cn).parse(); }
/// Entry points of this module
AST parseFile(S, T...)(S filename, T ln_cn)
{ return parserFromFile(filename, ln_cn).parse(); }
// Named Constructors of Parser
private auto parserFromLexer(Lexer)(Lexer lex)
{ return new Parser!Lexer(lex); }
private auto parserFromString(T...)(T params)
{ return parserFromLexer(polemy.lex.lexerFromString(params)); }
private auto parserFromFile(T...)(T params)
{ return parserFromLexer(polemy.lex.lexerFromFile(params)); }
// Parser
private class Parser(Lexer)
if( isForwardRange!(Lexer) && is(ElementType!(Lexer) == Token) )
{
AST parse()
{
auto e = Body();
if( !lex.empty )
throw genex!ParseException(currentPosition(), "parsing ended but some tokens left");
return e;
}
AST Body()
{
if( lex.empty || !lex.front.quoted && ["}",")","]"].canFind(lex.front.str) )
return doNothingExpression();
auto saved = lex.save;
auto pos = lex.front.pos;
string kwd = lex.front.str;
if( tryEat("let") || tryEat("var") || tryEat("def") || tryEat("@") )
{
if( kwd == "@" ) {
kwd ~= eatId("after @",true);
if( tryEat("(") ) {
lex = saved;
goto asExpression;
}
}
immutable LexPosition varpos = (lex.empty ? null : lex.front.pos);
string var = eatId("after "~kwd,true);
// [TODO] refactor. only auto e = ... differ
if(tryEat("(")) {
kwd = (kwd[0]=='@' ? kwd : ""); // "let, var, def ==> neutral layer"
auto e = parseLambdaAfterOpenParen(varpos);
if( tryEat(";") && !lex.empty && (lex.front.quoted || !["}",")","]"].canFind(lex.front.str)) )
return new LetExpression(pos, var, kwd, e, Body());
else
return new LetExpression(pos, var, kwd, e, new VarExpression(varpos, var));
} else {
eat("=", "after "~kwd);
kwd = (kwd[0]=='@' ? kwd : ""); // "let, var, def ==> neutral layer"
auto e = E(0);
if( tryEat(";") && !lex.empty && (lex.front.quoted || !["}",")","]"].canFind(lex.front.str)) )
return new LetExpression(pos, var, kwd, e, Body());
else
return new LetExpression(pos, var, kwd, e, new VarExpression(varpos, var));
}
}
else
{
asExpression:
auto e = E(0);
if( tryEat(";") && !lex.empty && (lex.front.quoted || (lex.front.str!="}" && lex.front.str!=")")) )
return new LetExpression(pos, "_", "", e, Body());
else
return e;
}
}
// [TODO] make customizable from program
static immutable string[][] operator_perferences = [
["||"],
["&&"],
["!="],
["=="],
["<","<=",">",">="],
["|"],
["^"],
["&"],
["<<", ">>"],
["+","-"],
["~"],
["*","/","%"],
["^^"]
];
AST E(int level)
{
if( operator_perferences.length <= level )
return Funcall();
else
{
auto ops = operator_perferences[level];
auto e = E(level+1);
seq:
while( !lex.empty )
{
auto pos = lex.front.pos;
foreach(op; ops)
if( tryEat(op) )
{
e = new FuncallExpression(e.pos, new VarExpression(pos, op), e, E(level+1));
continue seq;
}
break;
}
return e;
}
}
AST Funcall()
{
auto e = BaseExpression();
while( tryEat("(") )
{
auto pos = currentPosition();
AST[] args;
while( !tryEat(")") ) {
if( lex.empty )
throw genex!UnexpectedEOF(pos,"Closing ')' for arguments not found");
args ~= E(0);
if( !tryEat(",") ) {
eat(")", "after function parameters");
break;
}
}
e = new FuncallExpression(e.pos, e, args);
}
return e;
}
AST BaseExpression()
{
if( lex.empty )
throw genex!UnexpectedEOF(currentPosition(), "Reached EOF when tried to parse an expression");
auto pos = lex.front.pos;
if( lex.front.quoted )
{
scope(exit) lex.popFront;
return new StrLiteral(pos, lex.front.str);
}
if( isNumber(lex.front.str) )
{
scope(exit) lex.popFront;
return new IntLiteral(pos, BigInt(cast(string)lex.front.str));
}
if( tryEat("@") )
{
auto lay = "@"~eatId("for layer ID");
eat("(", "for layered execution");
auto e = Body();
eat(")", "after "~lay~"(...");
return new LayeredExpression(pos, lay, e);
}
if( tryEat("(") )
{
auto e = Body();
eat(")", "after parenthesized expression");
return e;
}
if( tryEat("if") )
{
eat("(", "after if");
auto cond = E(0);
eat(")", "after if condition");
auto thenPos = lex.front.pos;
eat("{", "after if condition");
auto th = Body();
eat("}", "after if-then body");
auto el = doNothingExpression();
auto elsePos = (lex.empty ? LexPosition.dummy : lex.front.pos);
if( tryEat("else") ) {
eat("{", "after else");
el = Body();
eat("}", "after else body");
}
return new FuncallExpression(pos,
new VarExpression(pos, "if"),
cond,
new FunLiteral(thenPos, [], th),
new FunLiteral(elsePos, [], el)
);
}
if( tryEat("fun") || tryEat("\u03BB") )
{
eat("(", "after fun");
return parseLambdaAfterOpenParen(pos);
}
scope(exit) lex.popFront;
return new VarExpression(pos, lex.front.str);
}
AST parseLambdaAfterOpenParen(immutable LexPosition pos)
{
Parameter[] params;
while( !tryEat(")") )
{
params ~= new Parameter(eatId("for function parameter"), []);
if( !tryEat(",") ) {
eat(")", "after function parameters");
break;
}
}
eat("{", "after function parameters");
auto funbody = Body();
eat("}", "after function body");
return new FunLiteral(pos, params, funbody);
}
private:
Lexer lex;
this(Lexer lex) { this.lex = lex; }
void eat(string kwd, lazy string msg)
{
if( !tryEat(kwd) )
if( lex.empty )
throw genex!UnexpectedEOF(
currentPosition(), sprintf!"%s is expected for %s but not found"(kwd,msg));
else
throw genex!ParseException(
currentPosition(), sprintf!"%s is expected for %s but not found"(kwd,msg));
}
bool tryEat(string kwd)
{
if( lex.empty || lex.front.quoted || lex.front.str!=kwd )
return false;
lex.popFront;
return true;
}
string eatId(lazy string msg, bool allowQuoted=false)
{
if( lex.empty )
throw genex!UnexpectedEOF(currentPosition(), "identifier is expected but not found "~msg);
if( !allowQuoted && lex.front.quoted )
throw genex!ParseException(currentPosition(), "identifier is expected but not found "~msg);
scope(exit) lex.popFront;
return lex.front.str;
}
bool isNumber(string s)
{
return find!(`a<'0'||'9'<a`)(s).empty;
}
AST doNothingExpression()
{
return new IntLiteral(currentPosition(), BigInt(178));
}
immutable(LexPosition) currentPosition()
{
return lex.empty ? null : lex.front.pos;
}
}
unittest
{
mixin EasyAST;
assert_eq(parseString(`123`), intl(123));
assert_eq(parseString(`"foo"`), strl("foo"));
assert_eq(parseString(`fun(){1}`), fun([],intl(1)));
assert_eq(parseString(`fun(x){1}`), fun(["x"],intl(1)));
assert_eq(parseString("\u03BB(){1}"), fun([],intl(1)));
assert_eq(parseString("\u03BB(x){1}"), fun(["x"],intl(1)));
assert_eq(parseString(`1;2`), let("_","",intl(1),intl(2)));
assert_eq(parseString(`1;2;`), let("_","",intl(1),intl(2)));
assert_eq(parseString(`let x=1;2`), let("x","",intl(1),intl(2)));
assert_eq(parseString(`var x=1;2;`), let("x","",intl(1),intl(2)));
assert_eq(parseString(`def x=1`), let("x","",intl(1),var("x")));
assert_eq(parseString(`@val x=1;`), let("x","@val",intl(1),var("x")));
assert_eq(parseString(`@typ x="#int";`), let("x","@typ",strl("#int"),var("x")));
assert_eq(parseString(`f(1,2)`), call(var("f"),intl(1),intl(2)));
assert_eq(parseString(`if(1){2}`), call(var("if"),intl(1),fun([],intl(2)),fun([],intl(178))));
assert_eq(parseString(`if(1){2}else{3}`), call(var("if"),intl(1),fun([],intl(2)),fun([],intl(3))));
assert_eq(parseString(`if(1){}else{3}()()`),
call(call(call(var("if"),intl(1),fun([],intl(178)),fun([],intl(3))))));
assert_eq(parseString(`1+2*3`), call(var("+"),intl(1),call(var("*"),intl(2),intl(3))));
assert_eq(parseString(`(1+2)*3`), call(var("*"),call(var("+"),intl(1),intl(2)),intl(3)));
assert_eq(parseString(`1*(2+3)`), call(var("*"),intl(1),call(var("+"),intl(2),intl(3))));
assert_eq(parseString(`1*2+3`), call(var("+"),call(var("*"),intl(1),intl(2)),intl(3)));
assert_eq(parseString(`@x(1)`), lay("@x", intl(1)));
assert_eq(parseString(`
let x = 100; #comment
let y = 200; #comment!!!!!
x+y
`),
let("x", "", intl(100), let("y", "", intl(200), call(var("+"), var("x"), var("y"))))
);
assert_eq(parseString(`
var fac = fun(x){ if(x <= 1) {1} else {x*fac(x-1)} };
fac(10)
`),
let("fac", "", fun(["x"],
call(var("if"),
call(var("<="), var("x"), intl(1)),
fun([], intl(1)),
fun([], call(var("*"), var("x"), call(var("fac"),call(var("-"),var("x"),intl(1)))))
)),
call(var("fac"),intl(10))
)
);
}
unittest
{
assert_throw!UnexpectedEOF(parseString(`1+`));
assert_throw!ParseException(parseString(`1+2}`));
assert_throw!UnexpectedEOF(parseString(`let "x"`));
assert_throw!UnexpectedEOF(parseString(`var`));
assert_throw!ParseException(parseString(`@val x ==`));
assert_throw!ParseException(parseString(`if(){1}`));
assert_throw!UnexpectedEOF(parseString(`f(`));
}
unittest
{
mixin EasyAST;
assert_eq(parseString(`def foo(x) { x+1 }; foo`),
let("foo", "",
fun(["x"], call(var("+"), var("x"), intl(1))),
var("foo"))
);
}