Permalink
Branch: master
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
242 lines (228 sloc) 8.49 KB
/*
Oh what fun, I'll finally get to compile a grammar using LLLPG!
It's taken more than a year to get to this point, how exciting!!!
OK, the goal: WRITE THE GRAMMAR OF LES IN LES.
*/
#importMacros LeMP.Prelude.Les;
import System;
import System.Collections.Generic;
import System.Linq;
import System.Text;
import Loyc;
import Loyc.Collections;
import Loyc.Syntax;
import Loyc.Syntax.Lexing;
namespace Loyc.Syntax.Les {
using TT = TokenType;
using S = CodeSymbols;
using P = LesPrecedence;
// 0162=Unreachable code detected; 0642=Possibly mistaken empty statement
#rawText("#pragma warning disable 162, 642");
@[public, partial] class Les2Parser
{
@[FullLLk, LL(2), AddCsLineDirectives(@false), PrematchByDefault]
LLLPG (parser(laType(TT), matchType(int), terminalType(Token), allowSwitch(@true)));
alias("@" = TT.At);
alias(":" = TT.Colon);
alias(";" = TT.Semicolon);
alias("," = TT.Comma);
alias("!" = TT.Not);
alias(" (" = TT.SpaceLParen);
alias("(" = TT.LParen);
alias(")" = TT.RParen);
alias("[" = TT.LBrack);
alias("]" = TT.RBrack);
alias("{" = TT.LBrace);
alias("}" = TT.RBrace);
@[public] rule StmtList()::VList!LNode @{
{endMarker := TT.Semicolon;}
result:ExprList(ref endMarker)
};
@[public] fn ExprList(list::VList!LNode = default(VList!LNode))::VList!LNode {
endMarker := default(TT);
return (ExprList(ref endMarker, list));
};
// A sequence of expressions separated by commas OR semicolons.
// The `ref endMarker` parameter tells the caller if semicolons were used.
@[public, virtual] rule ExprList(ref endMarker::TokenType, list::VList!LNode = default(VList!LNode))::VList!LNode @{
// `(/X)` is the same as `[X]?` except that in the first version, X is
// the default branch, which simplifies the output code in this case.
{if (LT0.Value `#is` string) { endMarker = TT.EOF; };} // possible JSON (allow comma as separator)
(/ e:TopExpr)
[ end:(","|";")
{e ??= MissingExpr(end);}
{list.Add(e.WithRange(e.Range.StartIndex, end.EndIndex));}
{CheckEndMarker(ref endMarker, ref end);}
({$e = null;} / e:TopExpr)
]*
{if ($e != null || end.Type() == TT.Comma) { list.Add(e ?? MissingExpr(end)); };}
{return list;}
};
@[public, virtual] rule ExprListLazy(endMarker::Holder!TokenType)::IEnumerable!LNode @{
{if (LT0.Value `#is` string) { endMarker = TT.EOF; };} // possible JSON (allow comma as separator)
(/ e:TopExpr)
[ end:(","|";")
{e ??= MissingExpr(end);}
{#rawText("yield"); return e.WithRange(e.Range.StartIndex, end.EndIndex);}
{CheckEndMarker(ref endMarker.Value, ref end);}
({$e = null;} / e:TopExpr)
]*
{if ($e != null || end.Type() == TT.Comma) {@[#yield] return e ?? MissingExpr(end);}}
};
fn CheckEndMarker(ref endMarker::TokenType, ref end::Token)
{
if (endMarker != end.Type()) {
if (endMarker == default(TT)) {
endMarker = end.Type();
} else {
Error(-1, "Unexpected separator: {0} should be {1}",
ToString(end.TypeInt), ToString(endMarker->int));
};
};
};
@[protected] rule TopExpr()::LNode @{
{var attrStart = int.MaxValue;}
greedy[ // @[Attributes]
at:"@"
{if $at.Type() == default(TT) {ErrorSink.Write(Severity.Warning, LaIndexToMsgContext(0), "Attribute: expected '@['")}
else {attrStart = System.Math.Min(attrStart, at.StartIndex);};}
t:"[" attrs:ExprList(attrs) "]"
]*
( e:Expr(StartStmt)
/ // Superexpression
id:=TT.Id // identifier
{var args = VList!LNode.Empty;}
args+=Expr(P.SuperExpr)
[ {
if (LA0->TT == TT.LParen) {
var loc = args[args.Count - 2, args.Last].Range.End;
Error(0, "Expected a space before '(' (possibly missing ';' or ',' at {0})", loc);
};
}
args+=Particle
]*
{e = MarkSpecial(F.Call(id, args, id.StartIndex, args.Last.Range.EndIndex));}
)
{if (attrStart < e.Range.StartIndex) { e = e.WithRange(attrStart, e.Range.EndIndex); };}
{return e.PlusAttrsBefore(attrs);}
};
// Types of (normal) expressions:
// - particles: ids, literals, (parenthesized), {braced}
// - ++prefix_operators
// - infix + operators
// - suffix_operators++
// - Special primary expressions:
// method_calls(with arguments), indexers[with indexes], generic!arguments
@[LL(1)]
token Expr(context::Precedence)::LNode @{
{prec::Precedence;}
e:PrefixExpr(context)
greedy
[ // Infix operator
&{@[Local] context.CanParse(prec=InfixPrecedenceOf(LT($LI)))}
{if (!prec.CanMixWith(context)) {
Error(0, "Operator '{0}' is not allowed in this context. Add parentheses to clarify the code's meaning.", LT0.Value);
};}
t:(TT.NormalOp|TT.BQOperator|TT.Dot|TT.Assignment)
rhs:=Expr(prec)
{e = F.Call(t, e, rhs, e.Range.StartIndex, rhs.Range.EndIndex, NodeStyle.Operator);}
| // Method_calls(with arguments) and indexers[with indexes]
&{@[Local] context.CanParse(P.Primary)}
e=FinishPrimaryExpr(e)
| // Generic!arguments
&{@[Local] context.CanParse(P.Of)}
"!"
{var args = (new VList!LNode { e }); endIndex::int;}
( "(" args=ExprList(args) c:=")" {endIndex = c.EndIndex;}
/ T:=Expr(P.Of) {args.Add(T); endIndex = T.Range.EndIndex;}
)
{e = F.Call(S.Of, args, e.Range.StartIndex, endIndex, $"!".StartIndex, $"!".EndIndex, NodeStyle.Operator);}
| // Suffix operator
&{@[Local] context.CanParse(SuffixPrecedenceOf(LT($LI)))}
t:TT.PreOrSufOp
{e = F.Call(ToSuffixOpName(t.Value -> Symbol), e, e.Range.StartIndex, t.EndIndex, t.StartIndex, t.EndIndex, NodeStyle.Operator);}
]*
{return e;}
};
// Helper rule that parses one of the syntactically special primary expressions
rule FinishPrimaryExpr(e::LNode)::LNode @{
( // call(function)
{var endMarker = default(TokenType);}
"(" list:ExprList(ref endMarker) c:=")"
{
e = MarkCall(F.Call(e, list, e.Range.StartIndex, c.EndIndex));
if (endMarker == TT.Semicolon) { e.Style = NodeStyle.Statement | NodeStyle.Alternate; };
}
| // Indexer / square brackets
{var args = (new VList!LNode { e });}
"[" args=ExprList(args) c:="]"
{e = F.Call(S.IndexBracks, args, e.Range.StartIndex, c.EndIndex, $"[".StartIndex, $"[".EndIndex, NodeStyle.Operator);}
)
{return e;}
};
rule PrefixExpr(context::Precedence)::LNode @
{ // Prefix operator
t:(TT.NormalOp|"!"|TT.BQOperator|TT.Dot|TT.Assignment|TT.PrefixOp|TT.PreOrSufOp)
e:Expr(PrefixPrecedenceOf(t))
{$result = F.Call(t, e, t.StartIndex, e.Range.EndIndex, NodeStyle.Operator);}
| result:Particle
};
// An Particle is:
// - an (expression) in parenthesis or a tuple
// - a literal or simple identifier
// - simple calls are also handled here, as a space optimization
// - a token literal @{ ... }
// - a prefix operator followed by an Expr
// - a { block } in braces
// - a [ list ] in square brackets
rule Particle()::LNode @{
( id:=TT.Id // identifier
{$result = F.Id(id).SetStyle(id.Style);}
| lit:=TT.Literal // literal
{$result = F.Literal(lit).SetStyle(lit.Style);}
| // @{Token literal}
o:"@"
( "[" tree:TokenTree c:"]"
| "{" tree:TokenTree c:"}" )
{$result = F.Literal(tree, o.StartIndex, c.EndIndex);}
| // {braces}
o:"{" list:=StmtList c:"}"
{$result = F.Braces(list, o.StartIndex, c.EndIndex).SetStyle(NodeStyle.Statement);}
| o:"[" list:=ExprList c:"]"
{$result = F.Call(S.Array, list, o.StartIndex, c.EndIndex, o.StartIndex, o.EndIndex, NodeStyle.Expression);}
| // (parens) - possibly a tuple
{var endMarker = default(TT);}
o:("("|" (") {var hasAttrList = LA0->TT == TT.LBrack || LA0->TT == TT.At;}
list:=ExprList(ref endMarker)
c:")" {
if (endMarker == TT.Semicolon || list.Count != 1) {
$result = F.Call(S.Tuple, list, o.StartIndex, c.EndIndex, o.StartIndex, o.EndIndex, NodeStyle.Expression);
if (endMarker == TT.Comma) {
var msg = "Tuples require ';' as a separator.";
if (o.Type() == TT.SpaceLParen) {
msg += " If a function call was intended, remove the space(s) before '('.";
};
ErrorSink.Error(list[0].Range.End, msg);
};
} else {
$result = hasAttrList ? list[0] : F.InParens(list[0], o.StartIndex, c.EndIndex);
};
}
| error {
Error(0, "Expected a particle (id, literal, {braces} or (parens)).");
$result = MissingExpr(LT0);
}
)
};
rule TokenTree::TokenTree @{
{$result = `new` TokenTree(SourceFile);}
nongreedy(
open:=("("|" ("|"["|"{")
TokenTree
{$result.Add(open.WithValue($TokenTree));}
result+=(")"|"]"|"}")
/ result+=_
)*
};
};
};