// Parser.fu - Fusion parser
//
// Copyright (C) 2011-2026 Piotr Fusik
//
// This file is part of Fusion Transpiler,
// see https://github.com/fusionlanguage/fut
//
// Fusion Transpiler is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Fusion Transpiler is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Fusion Transpiler. If not, see http://www.gnu.org/licenses/
public class FuParser : FuLexer
{
string? XcrementParent = null;
FuLoop? CurrentLoop = null;
FuCondCompletionStatement!? CurrentLoopOrSwitch = null;
string() FindNameFilename;
int FindNameLine = -1;
int FindNameColumn;
FuName? FoundName = null;
public void FindName!(string filename, int line, int column)
{
this.FindNameFilename = filename;
this.FindNameLine = line;
this.FindNameColumn = column;
this.FoundName = null;
}
public FuSymbol? GetFoundDefinition() => this.FoundName == null ? null : this.FoundName.GetSymbol();
bool DocParseLine!(FuDocPara! para)
{
if (para.Children.Count > 0)
para.Children.Add(new FuDocLine());
this.LexemeOffset = this.CharOffset;
for (int lastNonWhitespace = 0;;) {
switch (PeekChar()) {
case -1:
case '\n':
case '\r':
para.Children.Add(new FuDocText { Text = GetLexeme() });
return lastNonWhitespace == '.';
case '\t':
case ' ':
ReadChar();
break;
case '`':
if (this.CharOffset > this.LexemeOffset)
para.Children.Add(new FuDocText { Text = GetLexeme() });
ReadChar();
this.LexemeOffset = this.CharOffset;
for (;;) {
int c = PeekChar();
if (c == '`') {
para.Children.Add(new FuDocCode { Text = GetLexeme() });
ReadChar();
break;
}
if (c < 0 || c == '\n') {
ReportError("Unterminated code in documentation comment");
break;
}
ReadChar();
}
this.LexemeOffset = this.CharOffset;
lastNonWhitespace = '`';
break;
default:
lastNonWhitespace = ReadChar();
break;
}
}
}
void DocParsePara!(FuDocPara! para)
{
do {
DocParseLine(para);
NextToken();
} while (See(FuToken.DocRegular));
}
FuCodeDoc#? ParseDoc!()
{
if (!See(FuToken.DocRegular))
return null;
FuCodeDoc# doc = new FuCodeDoc();
bool period;
do {
period = DocParseLine(doc.Summary);
NextToken();
} while (!period && See(FuToken.DocRegular));
for (;;) {
switch (this.CurrentToken) {
case FuToken.DocRegular:
FuDocPara# para = new FuDocPara();
DocParsePara(para);
doc.Details.Add(para);
break;
case FuToken.DocBullet:
FuDocList# list = new FuDocList();
do {
list.Items.Add();
DocParsePara(list.Items.Last());
} while (See(FuToken.DocBullet));
doc.Details.Add(list);
break;
case FuToken.DocBlank:
NextToken();
break;
default:
return doc;
}
}
}
void CheckXcrementParent!()
{
if (this.XcrementParent != null) {
string op = See(FuToken.Increment) ? "++" : "--";
ReportError($"{op} not allowed on the right side of {this.XcrementParent}");
}
}
FuLiteralDouble# ParseDouble!()
{
double d;
if (!d.TryParse(GetLexeme().Replace("_", "")))
ReportError("Invalid floating-point number");
FuLiteralDouble# result = new FuLiteralDouble { Loc = this.TokenLoc, Type = this.Host.Program.System.DoubleType, Value = d };
NextToken();
return result;
}
bool SeeDigit()
{
int c = PeekChar();
return c >= '0' && c <= '9';
}
FuInterpolatedString# ParseInterpolatedString!()
{
FuInterpolatedString# result = new FuInterpolatedString { Loc = this.TokenLoc };
do {
string() prefix = this.StringValue;
NextToken();
FuExpr# arg = ParseExpr();
FuExpr# width = Eat(FuToken.Comma) ? ParseExpr() : null;
int format = ' ';
int precision = -1;
if (See(FuToken.Colon)) {
format = ReadChar();
if (SeeDigit()) {
precision = ReadChar() - '0';
if (SeeDigit())
precision = precision * 10 + ReadChar() - '0';
}
NextToken();
}
result.AddPart(prefix, arg, width, format, precision);
Check(FuToken.RightBrace);
} while (ReadString(true) == FuToken.InterpolatedString);
result.Suffix = this.StringValue;
NextToken();
return result;
}
FuExpr# ParseParenthesized!()
{
Expect(FuToken.LeftParenthesis);
FuExpr# result = ParseExpr();
Expect(FuToken.RightParenthesis);
return result;
}
bool IsFindName()
{
FuSourceFile file = this.Host.Program.SourceFiles.Last();
if (this.Host.Program.LineLocs.Count - file.Line - 1 == this.FindNameLine && file.Filename == this.FindNameFilename) {
int loc = this.Host.Program.LineLocs.Last() + this.FindNameColumn;
return loc >= this.TokenLoc && loc <= this.Loc;
}
return false;
}
bool ParseName!(FuName! result)
{
if (IsFindName())
this.FoundName = result;
result.Loc = this.TokenLoc;
result.Name = this.StringValue;
return Expect(FuToken.Id);
}
void ParseCollection!(List<FuExpr#>! result, FuToken closing)
{
if (!See(closing)) {
do
result.Add(ParseExpr());
while (Eat(FuToken.Comma));
}
ExpectOrSkip(closing);
}
FuExpr# ParsePrimaryExpr!(bool type = false, bool new_ = false)
{
FuExpr#? result;
switch (this.CurrentToken) {
case FuToken.Increment:
case FuToken.Decrement:
CheckXcrementParent();
return new FuPrefixExpr { Loc = this.TokenLoc, Op = NextToken(), Inner = ParsePrimaryExpr() };
case FuToken.Minus:
case FuToken.Tilde:
case FuToken.ExclamationMark:
return new FuPrefixExpr { Loc = this.TokenLoc, Op = NextToken(), Inner = ParsePrimaryExpr() };
case FuToken.New:
FuPrefixExpr# newResult = new FuPrefixExpr { Loc = this.TokenLoc, Op = NextToken() };
result = ParsePrimaryExpr(true, true);
if (Eat(FuToken.LeftBrace))
result = new FuBinaryExpr { Loc = this.TokenLoc, Left = result, Op = FuToken.LeftBrace, Right = ParseObjectLiteral() };
newResult.Inner = result;
result = newResult;
break;
case FuToken.LiteralLong:
result = this.Host.Program.System.NewLiteralLong(this.LongValue, this.TokenLoc);
NextToken();
break;
case FuToken.LiteralDouble:
result = ParseDouble();
break;
case FuToken.LiteralChar:
result = FuLiteralChar.New(this.LongValue, this.TokenLoc);
NextToken();
break;
case FuToken.LiteralString:
result = this.Host.Program.System.NewLiteralString(this.StringValue, this.TokenLoc);
NextToken();
break;
case FuToken.False:
result = new FuLiteralFalse { Loc = this.TokenLoc, Type = this.Host.Program.System.BoolType };
NextToken();
break;
case FuToken.True:
result = new FuLiteralTrue { Loc = this.TokenLoc, Type = this.Host.Program.System.BoolType };
NextToken();
break;
case FuToken.Null:
result = new FuLiteralNull { Loc = this.TokenLoc, Type = this.Host.Program.System.NullType };
NextToken();
break;
case FuToken.InterpolatedString:
result = ParseInterpolatedString();
break;
case FuToken.LeftParenthesis:
result = ParseParenthesized();
break;
case FuToken.Id:
FuSymbolReference# symbol = new FuSymbolReference();
ParseName(symbol);
if (Eat(FuToken.FatArrow)) {
FuLambdaExpr# lambda = new FuLambdaExpr { Loc = symbol.Loc };
lambda.Add(FuVar.New(null, symbol.Name));
lambda.Body = ParseExpr();
return lambda;
}
if (type && Eat(FuToken.Less)) {
FuAggregateInitializer# typeArgs = new FuAggregateInitializer { Loc = this.TokenLoc };
bool saveTypeArg = this.ParsingTypeArg;
this.ParsingTypeArg = true;
do
typeArgs.Items.Add(ParseType());
while (Eat(FuToken.Comma));
Expect(FuToken.RightAngle);
this.ParsingTypeArg = saveTypeArg;
symbol.Left = typeArgs;
}
result = symbol;
break;
case FuToken.Resource:
int loc = this.TokenLoc;
NextToken();
if (Eat(FuToken.Less)
&& this.StringValue == "byte"
&& Eat(FuToken.Id)
&& Eat(FuToken.LeftBracket)
&& Eat(FuToken.RightBracket)
&& Eat(FuToken.Greater))
result = new FuPrefixExpr { Loc = loc, Op = FuToken.Resource, Inner = ParseParenthesized() };
else {
ReportError("Expected 'resource<byte[]>'");
result = null;
}
break;
default:
ReportError("Invalid expression");
result = null;
break;
}
for (;;) {
switch (this.CurrentToken) {
case FuToken.Dot:
if (new_)
return result;
NextToken();
FuSymbolReference# path = new FuSymbolReference { Left = result };
ParseName(path);
result = path;
break;
case FuToken.LeftParenthesis:
NextToken();
if (result is FuSymbolReference# method) {
FuCallExpr# call = new FuCallExpr { Loc = this.TokenLoc, Method = method };
ParseCollection(call.Arguments, FuToken.RightParenthesis);
result = call;
}
else
ReportError("Expected a method");
break;
case FuToken.LeftBracket:
result = new FuBinaryExpr { Loc = this.TokenLoc, Left = result, Op = NextToken(), Right = See(FuToken.RightBracket) ? null : ParseExpr() };
Expect(FuToken.RightBracket);
break;
case FuToken.Increment:
case FuToken.Decrement:
CheckXcrementParent();
result = new FuPostfixExpr { Loc = this.TokenLoc, Inner = result, Op = NextToken() };
break;
case FuToken.ExclamationMark:
case FuToken.Hash:
result = new FuPostfixExpr { Loc = this.TokenLoc, Inner = result, Op = NextToken() };
break;
case FuToken.QuestionMark:
if (!type)
return result;
result = new FuPostfixExpr { Loc = this.TokenLoc, Inner = result, Op = NextToken() };
break;
default:
return result;
}
}
}
FuExpr# ParseMulExpr!()
{
FuExpr# left = ParsePrimaryExpr();
for (;;) {
switch (this.CurrentToken) {
case FuToken.Asterisk:
case FuToken.Slash:
case FuToken.Mod:
left = new FuBinaryExpr { Loc = this.TokenLoc, Left = left, Op = NextToken(), Right = ParsePrimaryExpr() };
break;
default:
return left;
}
}
}
FuExpr# ParseAddExpr!()
{
FuExpr# left = ParseMulExpr();
while (See(FuToken.Plus) || See(FuToken.Minus))
left = new FuBinaryExpr { Loc = this.TokenLoc, Left = left, Op = NextToken(), Right = ParseMulExpr() };
return left;
}
FuExpr# ParseShiftExpr!()
{
FuExpr# left = ParseAddExpr();
while (See(FuToken.ShiftLeft) || See(FuToken.ShiftRight))
left = new FuBinaryExpr { Loc = this.TokenLoc, Left = left, Op = NextToken(), Right = ParseAddExpr() };
return left;
}
FuExpr# ParseRelExpr!()
{
FuExpr# left = ParseShiftExpr();
for (;;) {
switch (this.CurrentToken) {
case FuToken.Less:
case FuToken.LessOrEqual:
case FuToken.Greater:
case FuToken.GreaterOrEqual:
left = new FuBinaryExpr { Loc = this.TokenLoc, Left = left, Op = NextToken(), Right = ParseShiftExpr() };
break;
case FuToken.Is:
FuBinaryExpr# isExpr = new FuBinaryExpr { Loc = this.TokenLoc, Left = left, Op = NextToken(), Right = ParsePrimaryExpr() };
if (See(FuToken.Id)) {
FuVar# def = ParseVar(isExpr.Right, false);
def.IsAssigned = true;
isExpr.Right = def;
}
return isExpr;
default:
return left;
}
}
}
FuExpr# ParseEqualityExpr!()
{
FuExpr# left = ParseRelExpr();
while (See(FuToken.Equal) || See(FuToken.NotEqual))
left = new FuBinaryExpr { Loc = this.TokenLoc, Left = left, Op = NextToken(), Right = ParseRelExpr() };
return left;
}
FuExpr# ParseAndExpr!()
{
FuExpr# left = ParseEqualityExpr();
while (See(FuToken.And))
left = new FuBinaryExpr { Loc = this.TokenLoc, Left = left, Op = NextToken(), Right = ParseEqualityExpr() };
return left;
}
FuExpr# ParseXorExpr!()
{
FuExpr# left = ParseAndExpr();
while (See(FuToken.Xor))
left = new FuBinaryExpr { Loc = this.TokenLoc, Left = left, Op = NextToken(), Right = ParseAndExpr() };
return left;
}
FuExpr# ParseOrExpr!()
{
FuExpr# left = ParseXorExpr();
while (See(FuToken.Or))
left = new FuBinaryExpr { Loc = this.TokenLoc, Left = left, Op = NextToken(), Right = ParseXorExpr() };
return left;
}
FuExpr# ParseCondAndExpr!()
{
FuExpr# left = ParseOrExpr();
while (See(FuToken.CondAnd)) {
string saveXcrementParent = this.XcrementParent;
this.XcrementParent = "&&";
left = new FuBinaryExpr { Loc = this.TokenLoc, Left = left, Op = NextToken(), Right = ParseOrExpr() };
this.XcrementParent = saveXcrementParent;
}
return left;
}
FuExpr# ParseCondOrExpr!()
{
FuExpr# left = ParseCondAndExpr();
while (See(FuToken.CondOr)) {
string saveXcrementParent = this.XcrementParent;
this.XcrementParent = "||";
left = new FuBinaryExpr { Loc = this.TokenLoc, Left = left, Op = NextToken(), Right = ParseCondAndExpr() };
this.XcrementParent = saveXcrementParent;
}
return left;
}
FuExpr# ParseExpr!()
{
FuExpr# left = ParseCondOrExpr();
if (See(FuToken.QuestionMark)) {
FuSelectExpr# result = new FuSelectExpr { Loc = this.TokenLoc, Cond = left };
NextToken();
string saveXcrementParent = this.XcrementParent;
this.XcrementParent = "?";
result.OnTrue = ParseExpr();
Expect(FuToken.Colon);
result.OnFalse = ParseExpr();
this.XcrementParent = saveXcrementParent;
return result;
}
return left;
}
FuExpr# ParseType!() => ParsePrimaryExpr(true);
FuExpr# ParseConstInitializer!()
{
if (Eat(FuToken.LeftBrace)) {
FuAggregateInitializer# result = new FuAggregateInitializer { Loc = this.TokenLoc };
ParseCollection(result.Items, FuToken.RightBrace);
return result;
}
return ParseExpr();
}
FuAggregateInitializer# ParseObjectLiteral!()
{
FuAggregateInitializer# result = new FuAggregateInitializer { Loc = this.TokenLoc };
do {
int loc = this.TokenLoc;
FuSymbolReference# field = new FuSymbolReference();
ParseName(field);
Expect(FuToken.Assign);
result.Items.Add(new FuBinaryExpr { Loc = loc, Left = field, Op = FuToken.Assign, Right = ParseExpr() });
} while (Eat(FuToken.Comma));
Expect(FuToken.RightBrace);
return result;
}
FuExpr#? ParseInitializer!()
{
if (!Eat(FuToken.Assign))
return null;
if (Eat(FuToken.LeftBrace))
return ParseObjectLiteral();
return ParseExpr();
}
void AddSymbol!(FuScope! scope, FuSymbol# symbol)
{
if (scope.Contains(symbol))
this.Host.ReportStatementError(symbol, "Duplicate symbol");
else
scope.Add(symbol);
}
FuVar# ParseVar!(FuExpr# type, bool initializer)
{
FuVar# result = new FuVar { TypeExpr = type };
ParseName(result);
result.Value = initializer ? ParseInitializer() : null;
return result;
}
FuConst# ParseConst!(FuVisibility visibility)
{
Expect(FuToken.Const);
FuConst# konst = new FuConst { Visibility = visibility, TypeExpr = ParseType(), VisitStatus = FuVisitStatus.NotYet };
ParseName(konst);
Expect(FuToken.Assign);
konst.Value = ParseConstInitializer();
CloseMember(FuToken.Semicolon, konst);
return konst;
}
FuExpr# ParseAssign!(bool allowVar, bool needSideEffect)
{
FuExpr#? left = allowVar ? ParseType() : ParseExpr();
switch (this.CurrentToken) {
case FuToken.Assign:
case FuToken.AddAssign:
case FuToken.SubAssign:
case FuToken.MulAssign:
case FuToken.DivAssign:
case FuToken.ModAssign:
case FuToken.AndAssign:
case FuToken.OrAssign:
case FuToken.XorAssign:
case FuToken.ShiftLeftAssign:
case FuToken.ShiftRightAssign:
return new FuBinaryExpr { Loc = this.TokenLoc, Left = left, Op = NextToken(), Right = ParseAssign(false, false) };
case FuToken.Id:
if (allowVar)
return ParseVar(left, true);
break;
default:
break;
}
if (needSideEffect && left != null && !left.HasSideEffect())
ReportError("Useless expression");
return left;
}
FuBlock# ParseBlock!(FuMethodBase!? method)
{
FuBlock# result = new FuBlock { Loc = this.TokenLoc };
Expect(FuToken.LeftBrace);
while (!See(FuToken.RightBrace) && !See(FuToken.EndOfFile))
result.Statements.Add(ParseStatement());
CloseMember(FuToken.RightBrace, method);
return result;
}
FuAssert# ParseAssert!()
{
FuAssert# result = new FuAssert { Loc = this.TokenLoc };
Expect(FuToken.Assert);
result.Cond = ParseExpr();
if (Eat(FuToken.Comma))
result.Message = ParseExpr();
Expect(FuToken.Semicolon);
return result;
}
FuBreak# ParseBreak!()
{
if (this.CurrentLoopOrSwitch == null)
ReportError("'break' outside loop or 'switch'");
FuBreak# result = new FuBreak { Loc = this.TokenLoc, LoopOrSwitch = this.CurrentLoopOrSwitch };
Expect(FuToken.Break);
Expect(FuToken.Semicolon);
if (this.CurrentLoopOrSwitch is FuLoop! loop)
loop.HasBreak = true;
return result;
}
FuContinue# ParseContinue!()
{
if (this.CurrentLoop == null)
ReportError("'continue' outside loop");
FuContinue# result = new FuContinue { Loc = this.TokenLoc, Loop = this.CurrentLoop };
Expect(FuToken.Continue);
Expect(FuToken.Semicolon);
return result;
}
void ParseLoopBody!(FuLoop! loop)
{
FuLoop outerLoop = this.CurrentLoop;
FuCondCompletionStatement! outerLoopOrSwitch = this.CurrentLoopOrSwitch;
this.CurrentLoop = loop;
this.CurrentLoopOrSwitch = loop;
loop.Body = ParseStatement();
this.CurrentLoopOrSwitch = outerLoopOrSwitch;
this.CurrentLoop = outerLoop;
}
FuDoWhile# ParseDoWhile!()
{
FuDoWhile# result = new FuDoWhile { Loc = this.TokenLoc };
Expect(FuToken.Do);
ParseLoopBody(result);
Expect(FuToken.While);
result.Cond = ParseParenthesized();
Expect(FuToken.Semicolon);
return result;
}
FuFor# ParseFor!()
{
FuFor# result = new FuFor { Loc = this.TokenLoc };
Expect(FuToken.For);
Expect(FuToken.LeftParenthesis);
if (!See(FuToken.Semicolon))
result.Init = ParseAssign(true, true);
Expect(FuToken.Semicolon);
if (!See(FuToken.Semicolon))
result.Cond = ParseExpr();
Expect(FuToken.Semicolon);
if (!See(FuToken.RightParenthesis))
result.Advance = ParseAssign(false, true);
Expect(FuToken.RightParenthesis);
ParseLoopBody(result);
return result;
}
void ParseForeachIterator!(FuForeach! result)
{
AddSymbol(result, ParseVar(ParseType(), false));
}
FuForeach# ParseForeach!()
{
FuForeach# result = new FuForeach { Loc = this.TokenLoc };
Expect(FuToken.Foreach);
Expect(FuToken.LeftParenthesis);
if (Eat(FuToken.LeftParenthesis)) {
ParseForeachIterator(result);
Expect(FuToken.Comma);
ParseForeachIterator(result);
Expect(FuToken.RightParenthesis);
}
else
ParseForeachIterator(result);
Expect(FuToken.In);
result.Collection = ParseExpr();
Expect(FuToken.RightParenthesis);
ParseLoopBody(result);
return result;
}
FuIf# ParseIf!()
{
FuIf# result = new FuIf { Loc = this.TokenLoc };
Expect(FuToken.If);
result.Cond = ParseParenthesized();
result.OnTrue = ParseStatement();
if (Eat(FuToken.Else))
result.OnFalse = ParseStatement();
return result;
}
FuLock# ParseLock!()
{
FuLock# result = new FuLock { Loc = this.TokenLoc };
Expect(FuToken.Lock_);
result.Lock = ParseParenthesized();
result.Body = ParseStatement();
return result;
}
FuNative# ParseNative!()
{
FuNative# result = new FuNative { Loc = this.TokenLoc };
Expect(FuToken.Native);
if (See(FuToken.LiteralString))
result.Content = this.StringValue;
else {
int offset = this.CharOffset;
Expect(FuToken.LeftBrace);
int nesting = 1;
for (;;) {
if (See(FuToken.EndOfFile)) {
Expect(FuToken.RightBrace);
return result;
}
if (See(FuToken.LeftBrace))
nesting++;
else if (See(FuToken.RightBrace)) {
if (--nesting == 0)
break;
}
NextToken();
}
assert this.Input[this.CharOffset - 1] == '}';
result.Content = Encoding.UTF8.GetString(this.Input, offset, this.CharOffset - 1 - offset);
}
NextToken();
return result;
}
int GetCurrentLine() => this.Host.Program.LineLocs.Count - this.Host.Program.SourceFiles.Last().Line - 1;
int GetTokenColumn() => this.TokenLoc - this.Host.Program.LineLocs.Last();
void SetMemberEnd(FuMember! member)
{
member.EndLine = GetCurrentLine();
member.EndColumn = this.Loc - this.Host.Program.LineLocs.Last();
}
void CloseMember!(FuToken expected, FuMember!? member)
{
if (member != null)
SetMemberEnd(member);
Expect(expected);
}
void CloseContainer!(FuContainerType! type)
{
type.EndLine = GetCurrentLine();
type.EndColumn = this.Loc - this.Host.Program.LineLocs.Last();
Expect(FuToken.RightBrace);
}
FuReturn# ParseReturn!(FuMethod!? method)
{
FuReturn# result = new FuReturn { Loc = this.TokenLoc };
NextToken();
if (!See(FuToken.Semicolon))
result.Value = ParseExpr();
CloseMember(FuToken.Semicolon, method);
return result;
}
FuSwitch# ParseSwitch!()
{
FuSwitch# result = new FuSwitch { Loc = this.TokenLoc };
Expect(FuToken.Switch);
result.Value = ParseParenthesized();
Expect(FuToken.LeftBrace);
FuCondCompletionStatement! outerLoopOrSwitch = this.CurrentLoopOrSwitch;
this.CurrentLoopOrSwitch = result;
while (Eat(FuToken.Case)) {
result.Cases.Add();
FuCase! kase = result.Cases.Last();
do {
FuExpr# expr = ParseExpr();
if (See(FuToken.Id))
expr = ParseVar(expr, false);
if (Eat(FuToken.When))
expr = new FuBinaryExpr { Loc = this.TokenLoc, Left = expr, Op = FuToken.When, Right = ParseExpr() };
kase.Values.Add(expr);
Expect(FuToken.Colon);
} while (Eat(FuToken.Case));
if (See(FuToken.Default)) {
ReportError("Please remove 'case' before 'default'");
break;
}
while (!See(FuToken.EndOfFile)) {
kase.Body.Add(ParseStatement());
switch (this.CurrentToken) {
case FuToken.RightBrace:
case FuToken.Case:
case FuToken.Default:
break;
default:
continue;
}
break;
}
}
if (result.Cases.Count == 0)
ReportError("Switch with no cases");
if (Eat(FuToken.Default)) {
Expect(FuToken.Colon);
do {
if (See(FuToken.EndOfFile))
break;
result.DefaultBody.Add(ParseStatement());
} while (!See(FuToken.RightBrace));
}
Expect(FuToken.RightBrace);
this.CurrentLoopOrSwitch = outerLoopOrSwitch;
return result;
}
FuThrow# ParseThrow!()
{
FuThrow# result = new FuThrow { Loc = this.TokenLoc };
Expect(FuToken.Throw);
result.Class = new FuSymbolReference();
ParseName(result.Class);
ExpectOrSkip(FuToken.LeftParenthesis);
result.Message = See(FuToken.RightParenthesis) ? null : ParseExpr();
Expect(FuToken.RightParenthesis);
Expect(FuToken.Semicolon);
return result;
}
FuWhile# ParseWhile!()
{
FuWhile# result = new FuWhile { Loc = this.TokenLoc };
Expect(FuToken.While);
result.Cond = ParseParenthesized();
ParseLoopBody(result);
return result;
}
FuStatement# ParseStatement!()
{
switch (this.CurrentToken) {
case FuToken.LeftBrace:
return ParseBlock(null);
case FuToken.Assert:
return ParseAssert();
case FuToken.Break:
return ParseBreak();
case FuToken.Const:
return ParseConst(FuVisibility.Private);
case FuToken.Continue:
return ParseContinue();
case FuToken.Do:
return ParseDoWhile();
case FuToken.For:
return ParseFor();
case FuToken.Foreach:
return ParseForeach();
case FuToken.If:
return ParseIf();
case FuToken.Lock_:
return ParseLock();
case FuToken.Native:
return ParseNative();
case FuToken.Return:
return ParseReturn(null);
case FuToken.Switch:
return ParseSwitch();
case FuToken.Throw:
return ParseThrow();
case FuToken.While:
return ParseWhile();
default:
FuExpr# expr = ParseAssign(true, true);
Expect(FuToken.Semicolon);
return expr;
}
}
FuCallType ParseCallType!()
{
switch (this.CurrentToken) {
case FuToken.Static:
NextToken();
return FuCallType.Static;
case FuToken.Abstract:
NextToken();
return FuCallType.Abstract;
case FuToken.Virtual:
NextToken();
return FuCallType.Virtual;
case FuToken.Override:
NextToken();
return FuCallType.Override;
case FuToken.Sealed:
NextToken();
return FuCallType.Sealed;
default:
return FuCallType.Normal;
}
}
void ParseMethod!(FuClass! klass, FuMethod# method)
{
AddSymbol(klass, method);
method.Parameters.Parent = klass;
if (method.CallType != FuCallType.Static)
method.AddThis(klass, Eat(FuToken.ExclamationMark));
ExpectOrSkip(FuToken.LeftParenthesis);
if (!See(FuToken.RightParenthesis)) {
do {
FuCodeDoc#? doc = ParseDoc();
FuVar# param = ParseVar(ParseType(), true);
param.Documentation = doc;
AddSymbol(method.Parameters, param);
} while (Eat(FuToken.Comma));
}
Expect(FuToken.RightParenthesis);
FuCodeDoc#? throwsDoc = ParseDoc();
if (Eat(FuToken.Throws)) {
do {
FuThrowsDeclaration# decl = new FuThrowsDeclaration();
if (throwsDoc == null)
decl.Documentation = ParseDoc();
else if (method.Throws.Count > 0) {
ReportError("Exception documentation must follow the 'throws' keyword");
decl.Documentation = null;
}
else
decl.Documentation = throwsDoc;
ParseName(decl);
method.Throws.Add(decl);
} while (Eat(FuToken.Comma));
}
if (method.CallType == FuCallType.Abstract)
CloseMember(FuToken.Semicolon, method);
else if (See(FuToken.FatArrow))
method.Body = ParseReturn(method);
else if (Check(FuToken.LeftBrace))
method.Body = ParseBlock(method);
}
void ReportFormerError(int line, int column, int length, string message)
{
this.Host.ReportError(this.Host.Program.SourceFiles.Last().Filename, line, column, column + length, message);
}
void ReportCallTypeError(int line, int column, string kind, FuCallType callType)
{
string callTypeString = FuMethod.CallTypeToString(callType);
ReportFormerError(line, column, callTypeString.Length, $"{kind} cannot be {callTypeString}");
}
void ParseClass!(FuCodeDoc# doc, int line, int column, bool isPublic, FuCallType callType)
{
Expect(FuToken.Class);
FuClass# klass = new FuClass { Documentation = doc, StartLine = line, StartColumn = column, IsPublic = isPublic, CallType = callType };
if (ParseName(klass))
AddSymbol(this.Host.Program, klass);
if (Eat(FuToken.Colon))
ParseName(klass.BaseClass);
Expect(FuToken.LeftBrace);
while (!See(FuToken.RightBrace) && !See(FuToken.EndOfFile)) {
doc = ParseDoc();
line = GetCurrentLine();
column = GetTokenColumn();
FuVisibility visibility;
switch (this.CurrentToken) {
case FuToken.Internal:
visibility = FuVisibility.Internal;
NextToken();
break;
case FuToken.Protected:
visibility = FuVisibility.Protected;
NextToken();
break;
case FuToken.Public:
visibility = FuVisibility.Public;
NextToken();
break;
case FuToken.Native:
klass.AddNative(ParseNative());
continue;
default:
visibility = FuVisibility.Private;
break;
}
if (See(FuToken.Const)) {
// const
FuConst# konst = ParseConst(visibility);
konst.StartLine = line;
konst.StartColumn = column;
konst.Documentation = doc;
AddSymbol(klass, konst);
continue;
}
int callTypeLine = GetCurrentLine();
int callTypeColumn = GetTokenColumn();
callType = ParseCallType();
FuExpr# type = Eat(FuToken.Void) ? this.Host.Program.System.VoidType : ParseType();
if (See(FuToken.LeftBrace) && type is FuCallExpr call) {
// constructor
if (call.Method.Name != klass.Name)
ReportError("Method with no return type");
else {
if (klass.CallType == FuCallType.Static)
ReportError("Constructor in a static class");
if (callType != FuCallType.Normal)
ReportCallTypeError(callTypeLine, callTypeColumn, "Constructor", callType);
if (call.Arguments.Count != 0)
ReportError("Constructor parameters not supported");
if (klass.Constructor != null)
ReportError($"Duplicate constructor, already defined in line {this.Host.Program.GetLine(klass.Constructor.Loc) + 1}");
}
if (visibility == FuVisibility.Private)
visibility = FuVisibility.Internal; // TODO
klass.Constructor = new FuMethodBase { StartLine = line, StartColumn = column, Loc = call.Loc, Documentation = doc,
Visibility = visibility, Parent = klass, Type = this.Host.Program.System.VoidType, Name = klass.Name };
klass.Constructor.Parameters.Parent = klass;
klass.Constructor.AddThis(klass, true);
klass.Constructor.Body = ParseBlock(klass.Constructor);
continue;
}
bool foundName = IsFindName();
int loc = this.TokenLoc;
string() name = this.StringValue;
if (!Expect(FuToken.Id))
continue;
if (See(FuToken.LeftParenthesis) || See(FuToken.ExclamationMark)) {
// method
// \ class | static | normal | abstract | sealed
// method \| | | |
// --------+--------+--------+----------+-------
// static | + | + | + | +
// normal | - | + | + | +
// abstract| - | - | + | -
// virtual | - | + | + | -
// override| - | + | + | +
// sealed | - | + | + | +
if (callType == FuCallType.Static || klass.CallType == FuCallType.Abstract) {
// ok
}
else if (klass.CallType == FuCallType.Static)
ReportError("Only static methods allowed in a static class");
else if (callType == FuCallType.Abstract)
ReportFormerError(callTypeLine, callTypeColumn, "abstract".Length, "Abstract methods allowed only in an abstract class");
else if (klass.CallType == FuCallType.Sealed && callType == FuCallType.Virtual)
ReportFormerError(callTypeLine, callTypeColumn, "virtual".Length, "Virtual methods disallowed in a sealed class");
if (visibility == FuVisibility.Private && callType != FuCallType.Static && callType != FuCallType.Normal)
ReportCallTypeError(callTypeLine, callTypeColumn, "Private method", callType);
FuMethod# method = new FuMethod { StartLine = line, StartColumn = column, Loc = loc, Documentation = doc,
Visibility = visibility, CallType = callType, TypeExpr = type, Name = name };
ParseMethod(klass, method);
if (foundName)
this.FoundName = method;
continue;
}
// field
if (callType != FuCallType.Normal)
ReportCallTypeError(callTypeLine, callTypeColumn, "Field", callType);
if (type == this.Host.Program.System.VoidType)
ReportError("Field cannot be void");
FuField# field = new FuField { StartLine = line, StartColumn = column, Loc = loc, Documentation = doc,
Visibility = visibility, TypeExpr = type, Name = name, Value = ParseInitializer() };
AddSymbol(klass, field);
CloseMember(FuToken.Semicolon, field);
if (foundName)
this.FoundName = field;
}
CloseContainer(klass);
}
void ParseEnum!(FuCodeDoc# doc, int line, int column, bool isPublic)
{
Expect(FuToken.Enum);
bool flags = Eat(FuToken.Asterisk);
FuEnum# enu = this.Host.Program.System.NewEnum(flags);
enu.Documentation = doc;
enu.StartLine = line;
enu.StartColumn = column;
enu.IsPublic = isPublic;
if (ParseName(enu))
AddSymbol(this.Host.Program, enu);
Expect(FuToken.LeftBrace);
do {
FuConst# konst = new FuConst { Visibility = FuVisibility.Public, Documentation = ParseDoc(), Type = enu, VisitStatus = FuVisitStatus.NotYet };
konst.StartLine = GetCurrentLine();
konst.StartColumn = GetTokenColumn();
ParseName(konst);
if (Eat(FuToken.Assign))
konst.Value = ParseExpr();
else if (flags)
ReportError("enum* symbol must be assigned a value");
AddSymbol(enu, konst);
SetMemberEnd(konst);
} while (Eat(FuToken.Comma));
CloseContainer(enu);
}
public void Parse!(string filename, byte[] input, int inputLength)
{
Open(filename, input, inputLength);
while (!See(FuToken.EndOfFile)) {
FuCodeDoc# doc = ParseDoc();
int line = GetCurrentLine();
int column = GetTokenColumn();
bool isPublic = Eat(FuToken.Public);
switch (this.CurrentToken) {
// class
case FuToken.Class:
ParseClass(doc, line, column, isPublic, FuCallType.Normal);
break;
case FuToken.Abstract:
case FuToken.Sealed:
case FuToken.Static:
ParseClass(doc, line, column, isPublic, ParseCallType());
break;
// enum
case FuToken.Enum:
ParseEnum(doc, line, column, isPublic);
break;
// native
case FuToken.Native:
this.Host.Program.TopLevelNatives.Add(ParseNative().Content);
break;
default:
ReportError("Expected class or enum");
NextToken();
break;
}
}
}
}