Permalink
Branch: master
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
329 lines (300 sloc) 12.3 KB
#ecs;
#importMacros(Loyc.LLPG);
using System(, .Text, .Linq, .Collections.Generic, .Diagnostics);
using Loyc; // for IMessageSink, Symbol, etc.
using Loyc.Collections; // many handy interfaces & classes
using Loyc.Syntax.Lexing; // For BaseLexer
using Loyc.Syntax; // For BaseParser<Token> and LNode
namespace Loyc.Syntax.Les; // braces around the rest of the file are optional
using TT = TokenType; // Abbreviate TokenType as TT
using P = LesPrecedence;
using S = CodeSymbols;
partial class Les3Parser
{
#rawText("#pragma warning disable 162, 642");
protected new const TT EOF = TT.EOF;
// Note: verbose messages are only printed when custom tool is given --verbose flag
[FullLLk, LL(2), Verbosity(1), PrematchByDefault]
LLLPG (parser(laType: TT, matchType: int, terminalType: Token, allowSwitch: true)) @{
alias("@" = TT.At);
alias("." = TT.Dot);
alias(":" = TT.Colon);
alias(";" = TT.Semicolon);
alias("," = TT.Comma);
alias("!" = TT.Not);
alias("(" = TT.LParen);
alias(")" = TT.RParen);
alias("[" = TT.LBrack);
alias("]" = TT.RBrack);
alias("{" = TT.LBrace);
alias("}" = TT.RBrace);
alias("'{" = TT.LTokenLiteral);
alias("\n" = TT.Newline);
members {
void CheckEndMarker(ref TokenType endMarker, ref Token end)
{
var endType = end.Type();
if (endType == TokenType.Newline)
endType = TokenType.Semicolon;
if (endMarker != endType) {
if (endMarker == default(TT)) {
endMarker = endType;
} else {
Error(-1, "Unexpected separator: {0} should be {1}",
ToString(end.TypeInt), ToString((int) endMarker));
}
}
}
void MissingEndMarker(LNode previousExpr, TokenType endMarker)
{
var location = new SourceRange(SourceFile, LT(-1).EndIndex + 1);
ErrorSink.Write(Severity.Error, location, "Expected '{0}'", endMarker == TT.Comma ? ',' : ';');
}
public VList<LNode> ExprList(VList<LNode> list = default(VList<LNode>))
{
var endMarker = default(TT);
return ExprList(ref endMarker, list);
}
public void CheckForSpaceAtEndOfAttribute()
{
if (LT0.StartIndex == LT(-1).EndIndex) {
var location = new SourceRange(SourceFile, LT0.StartIndex);
ErrorSink.Write(Severity.Error, location, "Expected space after attribute");
}
}
public bool IsContinuator(object ltv) => ltv != null && Continuators.ContainsKey(ltv);
}
NewlinesOpt : greedy("\n")*;
// A sequence of expressions separated by commas OR semicolons.
// The `ref endMarker` parameter tells the caller if semicolons were used.
[LL(1)]
public ExprList[ref TokenType endMarker, VList<LNode> list = default(VList<LNode>), bool isBracedBlock = false] returns [VList<LNode> result] :
NewlinesOpt
(e:TopExpr)?
[ ( &{isBracedBlock} {Error(0, "Unexpected closing bracket");} (")"|"]") )?
end:(","|"\n"|";")
{CheckEndMarker(ref endMarker, ref end);}
NewlinesOpt
{list.Add(e ?? MissingExpr(end));}
({$e = null;} / e:TopExpr)
ErrorTokensOpt
]*
{ if ($e != null || end.Type() == TT.Comma)
list.Add(e ?? MissingExpr(end, afterToken: true));
$result = list;
};
ErrorTokensOpt :
<=> // Make TokenList invisible to prediction analysis, so LLLPG treats this as an empty rule
{int _errorPosition = InputPosition;}
TokenList {
if (!$TokenList.IsEmpty)
Error(_errorPosition - InputPosition, "Expected end of expression (',', ';', etc.)");
};
// Lazy version of ExprList is used for parsing the top-level code in a streaming way
[LL(1)]
public ExprListLazy[Holder<TokenType> endMarker] returns [IEnumerable<LNode> result] :
NewlinesOpt
(e:TopExpr)?
[ end:(","|"\n"|";")
{CheckEndMarker(ref endMarker.Value, ref end);}
NewlinesOpt
{yield return e ?? MissingExpr(end);}
({$e = null;} / e:TopExpr)
ErrorTokensOpt
]*
{if ($e != null || end.Type() == TT.Comma) {yield return e ?? MissingExpr(end, afterToken: true);}};
[LL(1)] // Simplify the output
protected TopExpr returns [LNode result] :
{int startIndex = LT0.StartIndex;}
{var attrs = new VList<LNode>();}
( // @Attribute
"@" ("@" | attrs+=Particle[isAttribute: true])
greedy(~("{"|"\n") => {CheckForSpaceAtEndOfAttribute();})?
NewlinesOpt
)*
( e:Expr[Precedence.MinValue]
greedy(
":" (("\n"|")"|"]"|"}"|EOF) =>)
{e = F.Call(@@`':suf`, e, e.Range.StartIndex, $":".EndIndex, $":".StartIndex, $":".EndIndex);}
)?
/ error {Error(0, "Expected an expression here");}
nongreedy(_)+
(e:TopExpr | ("\n"|";"|","|")"|"]"|"}"|EOF) => {e = MissingExpr(LT0);})
)
{if (!attrs.IsEmpty) { e = e.PlusAttrsBefore(attrs).WithRange(startIndex, e.Range.EndIndex); }}
{$result = e;};
////////////////////////////////////////////////////////////////////////////
// Normal Expressions
////////////////////////////////////////////////////////////////////////////
// - particles: ids, literals, (parenthesized), {braced}
// - ++prefix_operators
// - infix + operators
// - suffix_operators++
// - juxtaposition operator
// - Special primary expressions:
// method_call(with arguments), block{call;}, indexer[with, indexes], generic!arguments,
[LL(1)] // simplifies output a lot
rule Expr[Precedence context] returns[LNode result] :
( // Dot-expression
e:KeywordExpression
| {Precedence prec;}
e:PrefixExpr[context]
greedy
( // Method_calls(with arguments), block_calls {...}, indexers[with indexes], generic!arguments
&{[Local] context.CanParse(P.Primary)}
e=FinishPrimaryExpr(e)
| // Infix operator
&{[Local] CanParse(context, $LI, out prec)}
opName:=InfixOperatorName[out Token op]
rhs:=Expr(prec)
{e = F.Call(opName, e, rhs, e.Range.StartIndex, rhs.Range.EndIndex, op.StartIndex, op.EndIndex, NodeStyle.Operator);}
| // Suffix operator
&{[Local] context.CanParse(_prec.Find(OperatorShape.Suffix, LT($LI).Value))}
t:TT.PreOrSufOp
{e = F.Call(_prec.ToSuffixOpName((Symbol) t.Value), e, e.Range.StartIndex, t.EndIndex, t.StartIndex, t.EndIndex, NodeStyle.Operator);}
| // ! operator (generic #of)
&{[Local] context.CanParse(P.Of)}
"!"
{var args = new VList<LNode> { e }; int endIndex;}
( "(" 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);}
)*
) {return e;};
InfixOperatorName[out Token op] returns [Symbol result]
: op=(TT.NormalOp|TT.Assignment|".") "\n"* {$result = (Symbol) op.Value;}
| &{[Hoist] (TT)LA($LI+1) != TT.Newline} op=":" {$result = (Symbol) op.Value;}
| &!{[Hoist] IsContinuator(LT($LI).Value)}
op=TT.Id
( // detect combo operator (optional punctuation part)
&{op.EndIndex == LT0.StartIndex} op2:(TT.NormalOp|TT.Assignment|".")
{
$result = GSymbol.Get("'"+op.Value.ToString() + op2.Value.ToString().Substring(1));
}
/ {
$result = GSymbol.Get("'"+op.Value.ToString());
if ((TT)LA0 == TT.Newline)
Error(0, "Syntax error. {0}' is used like an operator but is followed by a newline, which is not allowed unless the expression is placed in parentheses.".Localized($result));
}
)
( "\n"+
/ {
if (LT(-1).EndIndex == LT0.StartIndex)
Error(0, "Syntax error. {0}' is used like an operator but is not followed by a space.".Localized($result));
}
)
;
// Helper rule that parses one of the syntactically special primary expressions
FinishPrimaryExpr[LNode e] returns [LNode result]
: // call(function)
result:CallArgs[e]
| // Indexer / square brackets
{var args = new VList<LNode> { e };}
lb:="[" args=ExprList[args] rb:="]"
{return F.Call(S.IndexBracks, args, e.Range.StartIndex, rb.EndIndex, lb.StartIndex, rb.EndIndex, NodeStyle.Operator);}
;
CallArgs[LNode target] returns [LNode result] :
{var endMarker = default(TokenType);}
"(" args:ExprList[ref endMarker] ")"
{
$result = MarkCall(F.Call(target, args, target.Range.StartIndex, $")".EndIndex).SetBaseStyle(NodeStyle.PrefixNotation));
if (endMarker == TT.Semicolon) { $result.Style |= NodeStyle.Alternate; }
};
PrefixExpr[Precedence context] returns [LNode result]
: // Prefix operator
op:(TT.NormalOp|"!"|TT.BQOperator|TT.Assignment|TT.PrefixOp|TT.PreOrSufOp)
e:Expr[PrefixPrecedenceOf(op)]
{$result = F.Call(op, e, op.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 prefix operator followed by an Expr
// - a { block } in braces
// - a [ list ] in square brackets
Particle[bool isAttribute = false] returns [LNode result]
: id:=(TT.Id|TT.BQId) // identifier
{$result = F.Id(id).SetStyle(id.Style);}
| lit:=TT.Literal // literal
{$result = F.Literal(lit);}
| // ' token list
op:=TT.SingleQuoteOp TokenList
{$result = F.Call((Symbol)op.Value, $TokenList, op.StartIndex,
$TokenList.IsEmpty ? op.EndIndex : $TokenList.Last.Range.EndIndex);}
| // {statements; in; braces}
result:BracedBlock
| // [list]
result:SquareBracketList
| // (parens) - possibly a tuple
{var endMarker = default(TT);}
"(" {bool saveParens = !isAttribute && (TT)LA0 != TT.At;}
list:=ExprList[ref endMarker]
")" {
if (endMarker != default(TT) || list.Count != 1) {
$result = F.Call(S.Tuple, list, $"(".StartIndex, $")".EndIndex, $"(".StartIndex, $"(".EndIndex);
} else {
$result = saveParens ? F.InParens(list[0], $"(".StartIndex, $")".EndIndex) : list[0];
};
}
| error {
$result = MissingExpr(LT0, "Expected a particle (id, literal, {braces} or (parens)).");
};
SquareBracketList returns [LNode result] :
"[" list:=ExprList "]"
{$result = F.Call(S.Array, list, $"[".StartIndex, $"]".EndIndex, $"[".StartIndex, $"[".EndIndex).SetStyle(NodeStyle.Expression);};
// Token lists (TODO: add unit tests)
[LL(1), #new]
TokenList returns [VList<LNode> result] @init {$result = LNode.List();} :
greedy( result+=TokenListParticle )*;
[LL(1)]
TokenListEx returns [VList<LNode> result] :
( t:(","|";") {$result.Add(F.Id(t));}
| result+=TokenListParticle
)*;
[LL(1)]
TokenListParticle returns [LNode result]
: "(" TokenListEx ")"
{ return F.Call(@@`'()`, $TokenListEx, $"(".StartIndex, $")".EndIndex); }
/ SquareBracketList { return $SquareBracketList; }
/ BracedBlock { return $BracedBlock; }
/ t:=TT.Literal { return F.Literal(t); }
/ t:=(~(","|";"|"\n"|")"|"]"|"}")) { return F.Id(t); };
////////////////////////////////////////////////////////////////////////////
// Keyword Statement and related (continuators, braced blocks, paren blocks)
////////////////////////////////////////////////////////////////////////////
KeywordExpression returns [LNode result] @init {var args = new VList<LNode>();} :
kw:TT.Keyword {var keyword = kw.Value as Symbol;}
( // simplify the output by telling LLLPG what to expect if not an expr
("\n" | ";" | EOF | ")" | "]" | "}") =>
/ args+=Expr[Precedence.MinValue] )
greedy( "\n"? args+=BracedBlock )?
greedy( args+=Continuator )*
{
int endIndex = args.IsEmpty ? kw.EndIndex : args.Last.Range.EndIndex;
$result = MarkSpecial(F.Call(keyword, args, kw.StartIndex, endIndex, kw.StartIndex, kw.EndIndex));
}
(=>); // Simplify output by not considering what comes afterward
Continuator returns [LNode result] :
"\n"?
kw:ContinuatorKeyword {var opName = Continuators[kw.Value];}
(=>) // simplify output by preventing an unneeded check in KeywordExpression
"\n"?
( bb:BracedBlock {$result = F.Call(opName, bb, kw.StartIndex, bb.Range.EndIndex, kw.StartIndex, kw.EndIndex);}
/ e:TopExpr
( greedy("\n")? bb:BracedBlock
{$result = F.Call(opName, e, bb, kw.StartIndex, bb.Range.EndIndex, kw.StartIndex, kw.EndIndex);}
/ {$result = F.Call(opName, e, kw.StartIndex, e.Range.EndIndex, kw.StartIndex, kw.EndIndex);})
);
BracedBlock returns [LNode result] :
"{" => // simplify the output by forcing LL(1) analysis on rules using this rule
"{"
{endMarker := default(TT);}
stmts:ExprList(ref endMarker, isBracedBlock: true)
"}"
{return F.Call(S.Braces, stmts, $"{".StartIndex, $"}".EndIndex, $"{".StartIndex, $"{".EndIndex).SetStyle(NodeStyle.Statement);};
ContinuatorKeyword returns [Token result] : &{[Hoist] IsContinuator(LT($LI).Value)} result:TT.Id;
}; // end LLLPG parser
}