luaaaaah/Parser.cs

1640 lines
61 KiB
C#

using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace luaaaaah;
internal class Parser
{
public class ChunkNode(BlockNode block, CodeRegion startRegion, CodeRegion endRegion)
{
public BlockNode block = block;
public CodeRegion startRegion = startRegion, endRegion = endRegion;
}
public class BlockNode(List<StatNode> stats, CodeRegion startRegion, CodeRegion endRegion)
{
public List<StatNode> stats = stats;
public RetstatNode? retstat;
public CodeRegion startRegion = startRegion, endRegion = endRegion;
}
[JsonDerivedType(typeof(Semicolon), typeDiscriminator: "st Semicolon")]
[JsonDerivedType(typeof(Assignment), typeDiscriminator: "st Assignment")]
[JsonDerivedType(typeof(Functioncall), typeDiscriminator: "st Functioncall")]
[JsonDerivedType(typeof(Label), typeDiscriminator: "st Label")]
[JsonDerivedType(typeof(Break), typeDiscriminator: "st Break")]
[JsonDerivedType(typeof(Goto), typeDiscriminator: "st Goto")]
[JsonDerivedType(typeof(Do), typeDiscriminator: "st Do")]
[JsonDerivedType(typeof(While), typeDiscriminator: "st While")]
[JsonDerivedType(typeof(Repeat), typeDiscriminator: "st Repeat")]
[JsonDerivedType(typeof(If), typeDiscriminator: "st If")]
[JsonDerivedType(typeof(ForNumerical), typeDiscriminator: "st ForNum")]
[JsonDerivedType(typeof(ForGeneric), typeDiscriminator: "st ForGen")]
[JsonDerivedType(typeof(Function), typeDiscriminator: "st Function")]
[JsonDerivedType(typeof(LocalFunction), typeDiscriminator: "st LocalFunction")]
[JsonDerivedType(typeof(Local), typeDiscriminator: "st Local")]
public abstract class StatNode
{
public class Semicolon(CodeRegion region) : StatNode
{
public CodeRegion region = region;
}
public class Assignment(VarlistNode lhs, ExplistNode rhs, CodeRegion startRegion, CodeRegion endRegion) : StatNode
{
public VarlistNode lhs = lhs;
public ExplistNode rhs = rhs;
public CodeRegion startRegion = startRegion, endRegion = endRegion;
}
public class Functioncall(FunctioncallNode node) : StatNode
{
public FunctioncallNode node = node;
}
public class Label(string label, CodeRegion startRegion, CodeRegion endRegion) : StatNode
{
public CodeRegion startRegion = startRegion;
public CodeRegion endRegion = endRegion;
public string label = label;
}
public class Break(CodeRegion region) : StatNode
{
public CodeRegion region = region;
}
public class Goto(string label, CodeRegion startRegion, CodeRegion endRegion) : StatNode
{
public CodeRegion startRegion = startRegion;
public CodeRegion endRegion = endRegion;
public string label = label;
}
public class Do(BlockNode node, CodeRegion startRegion, CodeRegion endRegion) : StatNode
{
public CodeRegion startRegion = startRegion;
public CodeRegion endRegion = endRegion;
public BlockNode node = node;
}
public class While(ExpNode condition, BlockNode body, CodeRegion startRegion, CodeRegion endRegion) : StatNode
{
public ExpNode condition = condition;
public BlockNode body = body;
public CodeRegion startRegion = startRegion, endRegion = endRegion;
}
public class Repeat(ExpNode condition, BlockNode body, CodeRegion startRegion, CodeRegion endRegion) : StatNode
{
public ExpNode condition = condition;
public BlockNode body = body;
public CodeRegion startRegion = startRegion, endRegion = endRegion;
}
public class If(ExpNode condition, BlockNode body, List<ElseifNode> elseifs, CodeRegion startRegion, CodeRegion endRegion) : StatNode
{
public ExpNode condition = condition;
public BlockNode body = body;
public List<ElseifNode> elseifs = elseifs;
public BlockNode? else_;
public CodeRegion startRegion = startRegion, endRegion = endRegion;
}
public class ForNumerical(string variable, ExpNode start, ExpNode end, ExpNode? change, BlockNode body, CodeRegion startRegion, CodeRegion endRegion) : StatNode
{
public string variable = variable;
public ExpNode start = start;
public ExpNode end = end;
public ExpNode? change = change;
public BlockNode body = body;
public CodeRegion startRegion = startRegion, endRegion = endRegion;
}
public class ForGeneric(List<string> vars, ExplistNode exps, BlockNode body, CodeRegion startRegion, CodeRegion endRegion) : StatNode
{
public List<string> vars = vars;
public ExplistNode exps = exps;
public BlockNode body = body;
public CodeRegion startRegion = startRegion, endRegion = endRegion;
}
public class Function(FunctionNode node) : StatNode
{
public FunctionNode node = node;
}
public class LocalFunction(string name, FuncbodyNode body, CodeRegion startRegion, CodeRegion endRegion) : StatNode
{
public string name = name;
public FuncbodyNode body = body;
public CodeRegion startRegion = startRegion, endRegion = endRegion;
}
public class Local(AttnamelistNode attnames, ExplistNode? values, CodeRegion startRegion, CodeRegion endRegion) : StatNode
{
public AttnamelistNode attnames = attnames;
public ExplistNode? values = values;
public CodeRegion startRegion = startRegion, endRegion = endRegion;
}
}
public class RetstatNode(ExplistNode? values, CodeRegion startRegion, CodeRegion endRegion)
{
public ExplistNode? values = values;
public CodeRegion startRegion = startRegion, endRegion = endRegion;
}
public class FunctioncallNode(SuffixexpNode function, string? objectArg, ArgsNode args)
{
public SuffixexpNode function = function;
public string? objectArg = objectArg;
public ArgsNode args = args;
public CodeRegion startRegion = function.startRegion, endRegion = function.endRegion;
}
public class FunctionNode(FuncnameNode name, FuncbodyNode body, CodeRegion startRegion, CodeRegion endRegion)
{
public FuncnameNode name = name;
public FuncbodyNode body = body;
public CodeRegion startRegion = startRegion, endRegion = endRegion;
}
public class ExplistNode(List<ExpNode> exps, CodeRegion startRegion, CodeRegion endRegion)
{
public List<ExpNode> exps = exps;
public CodeRegion startRegion = startRegion, endRegion = endRegion;
}
public class VarlistNode(List<VarNode> vars, CodeRegion startRegion, CodeRegion endRegion)
{
public List<VarNode> vars = vars;
public CodeRegion startRegion = startRegion, endRegion = endRegion;
}
[JsonDerivedType(typeof(Normal), typeDiscriminator: "s Normal")]
[JsonDerivedType(typeof(Functioncall), typeDiscriminator: "s Functioncall")]
public abstract class SuffixexpNode(CodeRegion startRegion, CodeRegion endRegion)
{
public CodeRegion startRegion = startRegion, endRegion = endRegion;
public class Normal(SuffixexpFirstPart firstPart, List<SuffixexpSuffix> suffixes, CodeRegion startRegion, CodeRegion endRegion) : SuffixexpNode(startRegion, endRegion)
{
public SuffixexpFirstPart firstPart = firstPart;
public List<SuffixexpSuffix> suffixes = suffixes;
}
public class Functioncall(FunctioncallNode node) : SuffixexpNode(node.startRegion, node.endRegion)
{
public FunctioncallNode node = node;
}
}
[JsonDerivedType(typeof(Bracketed), typeDiscriminator: "a Bracketed")]
[JsonDerivedType(typeof(Tableconstructor), typeDiscriminator: "a Tableconstructor")]
[JsonDerivedType(typeof(Literal), typeDiscriminator: "a Literal")]
public abstract class ArgsNode(CodeRegion startRegion, CodeRegion endRegion)
{
public CodeRegion startRegion = startRegion, endRegion = endRegion;
public class Bracketed(ExplistNode? node, CodeRegion startRegion, CodeRegion endRegion) : ArgsNode(startRegion, endRegion)
{
public ExplistNode? node = node;
}
public class Tableconstructor(TableconstructorNode node, CodeRegion startRegion, CodeRegion endRegion) : ArgsNode(startRegion, endRegion)
{
public TableconstructorNode node = node;
}
public class Literal(string name, CodeRegion startRegion, CodeRegion endRegion) : ArgsNode(startRegion, endRegion)
{
public string name = name;
}
}
[JsonDerivedType(typeof(Nil), typeDiscriminator: "e Nil")]
[JsonDerivedType(typeof(False), typeDiscriminator: "e True")]
[JsonDerivedType(typeof(True), typeDiscriminator: "e False")]
[JsonDerivedType(typeof(Numeral), typeDiscriminator: "e Numeral")]
[JsonDerivedType(typeof(LiteralString), typeDiscriminator: "e Literal")]
[JsonDerivedType(typeof(Varargs), typeDiscriminator: "e Varargs")]
[JsonDerivedType(typeof(Functiondef), typeDiscriminator: "e Functiondef")]
[JsonDerivedType(typeof(Suffixexp), typeDiscriminator: "e Suffixexp")]
[JsonDerivedType(typeof(Tableconstructor), typeDiscriminator: "e Tableconstructor")]
[JsonDerivedType(typeof(Unop), typeDiscriminator: "e Unop")]
[JsonDerivedType(typeof(Binop), typeDiscriminator: "e Binop")]
public abstract class ExpNode
{
public class Nil(CodeRegion region) : ExpNode
{
public CodeRegion region = region;
}
public class False(CodeRegion region) : ExpNode
{
public CodeRegion region = region;
}
public class True(CodeRegion region) : ExpNode
{
public CodeRegion region = region;
}
public class Numeral(INumeral value, CodeRegion region) : ExpNode
{
public CodeRegion region = region;
public INumeral value = value;
}
public class LiteralString(string value, CodeRegion region) : ExpNode
{
public CodeRegion region = region;
public string value = value;
}
public class Varargs(CodeRegion region) : ExpNode
{
public CodeRegion region = region;
}
public class Functiondef(FuncbodyNode node, CodeRegion startRegion, CodeRegion endRegion) : ExpNode
{
public CodeRegion startRegion = startRegion;
public CodeRegion endRegion = endRegion;
public FuncbodyNode node = node;
}
public class Suffixexp(SuffixexpNode node) : ExpNode
{
public SuffixexpNode node = node;
}
public class Tableconstructor(TableconstructorNode node) : ExpNode
{
public TableconstructorNode node = node;
}
public class Unop(UnopNode node) : ExpNode
{
public UnopNode node = node;
}
public class Binop(BinopNode node) : ExpNode
{
public BinopNode node = node;
}
}
public class ElseifNode(ExpNode condition, BlockNode body, CodeRegion startRegion, CodeRegion endRegion)
{
public ExpNode condition = condition;
public BlockNode body = body;
public CodeRegion startRegion = startRegion, endRegion = endRegion;
}
public class FuncnameNode(string name, List<string> dottedNames, string? firstArg, CodeRegion startRegion, CodeRegion endRegion)
{
public string name = name;
public List<string> dottedNames = dottedNames;
public string? firstArg = firstArg;
public CodeRegion startRegion = startRegion, endRegion = endRegion;
}
public class FuncbodyNode(ParlistNode? pars, BlockNode body, CodeRegion startRegion, CodeRegion endRegion)
{
public ParlistNode? pars = pars;
public BlockNode body = body;
public CodeRegion startRegion = startRegion, endRegion = endRegion;
}
public class AttnamelistNode(List<AttnameNode> attnames, CodeRegion startRegion, CodeRegion endRegion)
{
public List<AttnameNode> attnames = attnames;
public CodeRegion startRegion = startRegion, endRegion = endRegion;
}
[JsonDerivedType(typeof(Name), typeDiscriminator: "v Name")]
[JsonDerivedType(typeof(Indexed), typeDiscriminator: "v Indexed")]
[JsonDerivedType(typeof(Member), typeDiscriminator: "v Member")]
public abstract class VarNode(CodeRegion startRegion, CodeRegion endRegion)
{
public CodeRegion startRegion = startRegion, endRegion = endRegion;
public class Name(string name, CodeRegion startRegion, CodeRegion endRegion) : VarNode(startRegion, endRegion)
{
public string name = name;
}
public class Indexed(IndexedVarNode node, CodeRegion startRegion, CodeRegion endRegion) : VarNode(startRegion, endRegion)
{
public IndexedVarNode node = node;
}
public class Member(MemberVarNode node, CodeRegion startRegion, CodeRegion endRegion) : VarNode(startRegion, endRegion)
{
public MemberVarNode node = node;
}
}
public class TableconstructorNode(FieldlistNode? exps, CodeRegion startRegion, CodeRegion endRegion)
{
public FieldlistNode? exps = exps;
public CodeRegion startRegion = startRegion, endRegion = endRegion;
}
public class UnopNode(UnopType type, ExpNode exp, CodeRegion startRegion, CodeRegion endRegion)
{
public UnopType type = type;
public ExpNode exp = exp;
public CodeRegion startRegion = startRegion, endRegion = endRegion;
}
public enum UnopType
{
Minus, LogicalNot, Length, BinaryNot,
}
public class BinopNode(ExpNode lhs, BinopType type, ExpNode rhs, CodeRegion startRegion, CodeRegion endRegion)
{
public ExpNode lhs = lhs;
public BinopType type = type;
public ExpNode rhs = rhs;
public CodeRegion startRegion = startRegion, endRegion = endRegion;
}
public enum BinopType
{
LogicalOr,
LogicalAnd,
Lt, Gt, LtEquals, GtEquals, NotEquals, Equals,
BinaryOr,
BinaryNot,
BinaryAnd,
Shl, Shr,
Concat,
Add, Sub,
Mul, Div, IntDiv, Mod,
Exp,
}
public class ParlistNode(List<string> names, bool hasVarargs, CodeRegion startRegion, CodeRegion endRegion)
{
public List<string> names = names;
public bool hasVarargs = hasVarargs;
public CodeRegion startRegion = startRegion, endRegion = endRegion;
}
public class AttnameNode(string name, string? attribute, CodeRegion startRegion, CodeRegion endRegion)
{
public string name = name;
public string? attribute = attribute;
public CodeRegion startRegion = startRegion, endRegion = endRegion;
}
public class IndexedVarNode(SuffixexpNode value, ExpNode index, CodeRegion startRegion, CodeRegion endRegion)
{
public SuffixexpNode value = value;
public ExpNode index = index;
public CodeRegion startRegion = startRegion, endRegion = endRegion;
}
public class MemberVarNode(SuffixexpNode value, string name, CodeRegion startRegion, CodeRegion endRegion)
{
public SuffixexpNode value = value;
public string name = name;
public CodeRegion startRegion = startRegion, endRegion = endRegion;
}
[JsonDerivedType(typeof(Name), typeDiscriminator: "sfp Name")]
[JsonDerivedType(typeof(BracketedExp), typeDiscriminator: "sfp BracketedExp")]
public abstract class SuffixexpFirstPart(CodeRegion startRegion, CodeRegion endRegion)
{
public CodeRegion startRegion = startRegion, endRegion = endRegion;
public class Name(string name, CodeRegion startRegion, CodeRegion endRegion) : SuffixexpFirstPart(startRegion, endRegion)
{
public string name = name;
}
public class BracketedExp(ExpNode node, CodeRegion startRegion, CodeRegion endRegion) : SuffixexpFirstPart(startRegion, endRegion)
{
public ExpNode node = node;
}
}
[JsonDerivedType(typeof(Dot))]
[JsonDerivedType(typeof(Indexed))]
[JsonDerivedType(typeof(Args))]
[JsonDerivedType(typeof(ArgsFirstArg))]
public abstract class SuffixexpSuffix(CodeRegion startRegion, CodeRegion endRegion)
{
public CodeRegion startRegion = startRegion, endRegion = endRegion;
public class Dot(string name, CodeRegion startRegion, CodeRegion endRegion) : SuffixexpSuffix(startRegion, endRegion)
{
public string name = name;
}
public class Indexed(ExpNode node, CodeRegion startRegion, CodeRegion endRegion) : SuffixexpSuffix(startRegion, endRegion)
{
public ExpNode node = node;
}
public class Args(ArgsNode node, CodeRegion startRegion, CodeRegion endRegion) : SuffixexpSuffix(startRegion, endRegion)
{
public ArgsNode node = node;
}
public class ArgsFirstArg(ArgsFirstArgNode node) : SuffixexpSuffix(node.startRegion, node.endRegion)
{
public ArgsFirstArgNode node = node;
}
}
public class FieldlistNode(List<FieldNode> exps, CodeRegion startRegion, CodeRegion endRegion)
{
public List<FieldNode> exps = exps;
public CodeRegion startRegion = startRegion, endRegion = endRegion;
}
public class ArgsFirstArgNode(string name, ArgsNode rest, CodeRegion startRegion, CodeRegion endRegion)
{
public string name = name;
public ArgsNode rest = rest;
public CodeRegion startRegion = startRegion, endRegion = endRegion;
}
[JsonDerivedType(typeof(IndexedAssignment))]
[JsonDerivedType(typeof(Assignment))]
[JsonDerivedType(typeof(Exp))]
public abstract class FieldNode(CodeRegion startRegion, CodeRegion endRegion)
{
public CodeRegion startRegion = startRegion, endRegion = endRegion;
public class IndexedAssignment(IndexedAssignmentNode node) : FieldNode(node.startRegion, node.endRegion)
{
public IndexedAssignmentNode node = node;
}
public class Assignment(FieldAssignmentNode node) : FieldNode(node.startRegion, node.endRegion)
{
public FieldAssignmentNode node = node;
}
public class Exp(ExpNode node, CodeRegion startRegion, CodeRegion endRegion) : FieldNode(startRegion, endRegion)
{
public ExpNode node = node;
}
}
public class IndexedAssignmentNode(ExpNode index, ExpNode rhs, CodeRegion startRegion, CodeRegion endRegion)
{
public ExpNode index = index;
public ExpNode rhs = rhs;
public CodeRegion startRegion = startRegion, endRegion = endRegion;
}
public class FieldAssignmentNode(string lhs, ExpNode rhs, CodeRegion startRegion, CodeRegion endRegion)
{
public string lhs = lhs;
public ExpNode rhs = rhs;
public CodeRegion startRegion = startRegion, endRegion = endRegion;
}
public int index;
public ChunkNode Parse(Token[] tokens)
{
return ParseChunk(tokens);
}
public ChunkNode ParseChunk(Token[] tokens)
{
BlockNode body = ParseBlock(tokens);
return new ChunkNode(block: body, startRegion: body.startRegion, endRegion: body.endRegion);
}
public BlockNode ParseBlock(Token[] tokens)
{
CodeRegion startRegion = tokens[index].region;
List<StatNode> stats = [];
while(index < tokens.Length &&
tokens[index].type != TokenType.Return &&
tokens[index].type != TokenType.End &&
tokens[index].type != TokenType.Elseif &&
tokens[index].type != TokenType.Else &&
tokens[index].type != TokenType.Until)
{
stats.Add(ParseStat(tokens));
}
BlockNode ret = new(stats: stats, startRegion: startRegion, endRegion: (stats.Count == 0 && index > 0) ? startRegion : tokens[index - 1].region);
if(index < tokens.Length && tokens[index].type == TokenType.Return)
{
ret.retstat = ParseRetstat(tokens);
}
return ret;
}
public StatNode ParseStat(Token[] tokens)
{
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}");
}
CodeRegion startRegion = tokens[index].region;
switch(tokens[index].type)
{
case TokenType.Semicolon:
{
index += 1;
return new StatNode.Semicolon(startRegion);
}
case TokenType.Break:
{
index += 1;
return new StatNode.Break(startRegion);
}
case TokenType.Goto:
{
index += 1;
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected name for goto at {startRegion}");
}
if(tokens[index].type != TokenType.Name)
{
throw new Exception($"{tokens[index].region}: Expected name for goto, got {tokens[index].type}");
}
StatNode.Goto ret = new(label: ((Token.StringData)tokens[index].data!).data, startRegion: startRegion, endRegion: tokens[index].region);
index += 1;
return ret;
}
case TokenType.Do:
{
index += 1;
BlockNode body = ParseBlock(tokens);
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected end for `do` at {startRegion}");
}
if(tokens[index].type != TokenType.End)
{
throw new Exception($"{tokens[index].region}: Expected `end` to close `do` at {startRegion}, got {tokens[index].type}");
}
StatNode.Do ret = new(node: body, startRegion: startRegion, endRegion: tokens[index].region);
index += 1;
return ret;
}
case TokenType.While:
{
index += 1;
ExpNode condition = ParseExp(tokens);
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected `do` after condition of while loop starting at {startRegion}");
}
if(tokens[index].type != TokenType.Do)
{
throw new Exception($"{tokens[index].region}: Expected `do` after condition of while starting at {startRegion}, got {tokens[index].type}");
}
index += 1;
BlockNode body = ParseBlock(tokens);
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected `end` after body of while loop starting at {startRegion}");
}
CodeRegion endRegion = tokens[index].region;
index += 1;
return new StatNode.While(condition: condition, body: body, startRegion: startRegion, endRegion: endRegion);
}
case TokenType.Repeat:
{
index += 1;
BlockNode body = ParseBlock(tokens);
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected `until` after body of until loop starting at {startRegion}");
}
if(tokens[index].type != TokenType.Until)
{
throw new Exception($"{tokens[index].region}: Expected `until` after block of until loop starting at {startRegion}, got {tokens[index].type}");
}
index += 1;
ExpNode conditon = ParseExp(tokens);
return new StatNode.Repeat(condition: conditon, body: body, startRegion: startRegion, endRegion: tokens[index - 1].region);
}
case TokenType.If:
{
index += 1;
ExpNode condition = ParseExp(tokens);
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected `then` after condition of if starting at {startRegion}");
}
if(tokens[index].type != TokenType.Then)
{
throw new Exception($"{tokens[index].region}: Expected `then` after condition of if starting at {startRegion}, got {tokens[index].type}");
}
index += 1;
BlockNode body = ParseBlock(tokens);
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected `end` after body of if starting at {startRegion}");
}
List<ElseifNode> elseifs = [];
while(tokens[index].type == TokenType.Elseif)
{
CodeRegion elseifStartRegion = tokens[index].region;
index += 1;
ExpNode elseifCondition = ParseExp(tokens);
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected `then` after condition of elseif starting at {elseifStartRegion}");
}
if(tokens[index].type != TokenType.Then)
{
throw new Exception($"{tokens[index].region}: Expected `then` after condition of elseif starting at {elseifStartRegion}, got {tokens[index].type}");
}
index += 1;
BlockNode elseifBody = ParseBlock(tokens);
elseifs.Add(new(condition: elseifCondition, body: elseifBody, startRegion: elseifStartRegion, endRegion: elseifBody.endRegion));
}
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected `end` after else-ifs of if starting at {startRegion}");
}
StatNode.If ret = new(condition: condition, body: body, elseifs: elseifs, startRegion: startRegion, endRegion: tokens[index - 1].region);
if(tokens[index].type == TokenType.Else)
{
index += 1;
ret.else_ = ParseBlock(tokens);
}
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected `end` to close if starting at {startRegion}");
}
if(tokens[index].type != TokenType.End)
{
throw new Exception($"{tokens[index].region}: Expected `end` to close if starting at {startRegion}, got {tokens[index].type}");
}
ret.endRegion = tokens[index].region;
index += 1;
return ret;
}
case TokenType.For:
{
index += 1;
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected name after for at {startRegion}");
}
if(tokens[index].type != TokenType.Name)
{
throw new Exception($"{tokens[index].region}: Expected name after for at {startRegion}, got {tokens[index].type}");
}
string variable = ((Token.StringData)tokens[index].data!).data;
index += 1;
switch(tokens[index].type)
{
case TokenType.Equals:
{
index += 1;
ExpNode start = ParseExp(tokens);
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected `,` after start value of numerical for loop starting at {startRegion}");
}
if(tokens[index].type != TokenType.Comma)
{
throw new Exception($"{tokens[index].region}: Expected `,` after start value of for loop starting at {startRegion}, got {tokens[index].type}");
}
index += 1;
ExpNode end = ParseExp(tokens);
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected `do` or `,` after end value of numerical for loop starting at {startRegion}");
}
ExpNode? change = null;
if(tokens[index].type == TokenType.Comma)
{
index += 1;
change = ParseExp(tokens);
}
if(index >= tokens.Length)
{
string t = (change == null) ? "`do` or `,` after end value" : "`do` after change value";
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected {t} of numerical for loop starting at {startRegion}");
}
if(tokens[index].type != TokenType.Do)
{
string t = (change == null) ? "`do` or `,` after end value" : "`do` after change value";
throw new Exception($"{tokens[index].region}: Expected {t} of numerical for loop starting at {startRegion}, got {tokens[index].type}");
}
index += 1;
BlockNode body = ParseBlock(tokens);
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected `end` to close numerical for loop starting at {startRegion}");
}
if(tokens[index].type != TokenType.End)
{
throw new Exception($"{tokens[index].region}: Expected `end` to close numerical for loop starting at {startRegion}, got {tokens[index].type}");
}
CodeRegion endRegion = tokens[index].region;
index += 1;
return new StatNode.ForNumerical(variable: variable, start: start, end: end, change: change, body: body, startRegion: startRegion, endRegion: endRegion);
}
case TokenType.Comma:
{
List<string> names = [variable];
while(tokens[index].type == TokenType.Comma)
{
index += 1;
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected another name in name list of for-in loop starting at {startRegion}");
}
if(tokens[index].type != TokenType.Name)
{
throw new Exception($"{tokens[index].region}: Expected another name in name list of for-in loop starting at {startRegion}, got {tokens[index].type}");
}
names.Add(((Token.StringData)tokens[index].data!).data);
index += 1;
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected `,` or `in` in for-in loop starting at {startRegion}");
}
}
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected `in` after name list of for-in loop starting at {startRegion}");
}
if(tokens[index].type != TokenType.In)
{
throw new Exception($"{tokens[index].region}: Expected `in` after name list of for-in loop starting at {startRegion}, got {tokens[index].type}");
}
index += 1;
ExplistNode exps = ParseExplist(tokens);
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected `do` after exp list of for-in loop starting at {startRegion}");
}
if(tokens[index].type != TokenType.Do)
{
throw new Exception($"{tokens[index].region}: Expected `do` after exp list of for-in loop starting at {startRegion}, got {tokens[index].type}");
}
index += 1;
BlockNode body = ParseBlock(tokens);
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected `end` to close for-in loop starting at {startRegion}");
}
if(tokens[index].type != TokenType.End)
{
throw new Exception($"{tokens[index].region}: Expected `end` to close for-in loop starting at {startRegion}, got {tokens[index].type}");
}
CodeRegion endRegion = tokens[index].region;
index += 1;
return new StatNode.ForGeneric(vars: names, exps: exps, body: body, startRegion: startRegion, endRegion: endRegion);
}
case TokenType.In:
{
index += 1;
ExplistNode exps = ParseExplist(tokens);
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected `do` after exp list of for-in loop starting at {startRegion}");
}
if(tokens[index].type != TokenType.Do)
{
throw new Exception($"{tokens[index].region}: Expected `do` after exp list of for-in loop starting at {startRegion}, got {tokens[index].type}");
}
index += 1;
BlockNode body = ParseBlock(tokens);
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected `end` to close for-in loop starting at {startRegion}");
}
if(tokens[index].type != TokenType.End)
{
throw new Exception($"{tokens[index].region}: Expected `end` to close for-in loop starting at {startRegion}, got {tokens[index].type}");
}
CodeRegion endRegion = tokens[index].region;
index += 1;
return new StatNode.ForGeneric(vars: [variable], exps: exps, body: body, startRegion: startRegion, endRegion: endRegion);
}
default:
{
throw new Exception($"{tokens[index].type}: Expected either `=`, `,` or `in` after first name of for loop {startRegion}, got {tokens[index].type}");
}
}
}
case TokenType.Function:
{
index += 1;
FuncnameNode name = ParseFuncname(tokens);
FuncbodyNode body = ParseFuncbody(tokens);
return new StatNode.Function(new(name: name, body: body, startRegion: startRegion, endRegion: body.endRegion));
}
case TokenType.Local:
{
index += 1;
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length} after `local` at {startRegion}");
}
if(tokens[index].type == TokenType.Function)
{
index += 1;
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected name of local function starting at {startRegion}");
}
if(tokens[index].type != TokenType.Name)
{
throw new Exception($"{tokens[index].region}: Expected name of local function starting at {startRegion}, got {tokens[index].type}");
}
string name = ((Token.StringData)tokens[index].data!).data;
index += 1;
FuncbodyNode body = ParseFuncbody(tokens);
return new StatNode.LocalFunction(name: name, body: body, startRegion: startRegion, endRegion: body.endRegion);
}
else
{
AttnamelistNode attnames = ParseAttnamelist(tokens);
StatNode.Local ret = new(attnames: attnames, values: null, startRegion: startRegion, endRegion: attnames.endRegion);
if(index < tokens.Length && tokens[index].type == TokenType.Equals)
{
index += 1;
ret.values = ParseExplist(tokens);
ret.endRegion = ret.values.endRegion;
}
return ret;
}
}
case TokenType.ColonColon:
{
index += 1;
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected name of label starting at {startRegion}");
}
if(tokens[index].type != TokenType.Name)
{
throw new Exception($"{tokens[index].region}: Expected name of label starting at {startRegion}, got {tokens[index].type}");
}
string name = ((Token.StringData)tokens[index].data!).data;
index += 1;
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected `::` after label name starting at {startRegion}");
}
if(tokens[index].type != TokenType.ColonColon)
{
throw new Exception($"{tokens[index].region}: Expected `::` after label name starting at {startRegion}, got {tokens[index].type}");
}
CodeRegion endRegion = tokens[index].region;
index += 1;
return new StatNode.Label(label: name, startRegion: startRegion, endRegion: endRegion);
}
case TokenType.Name:
case TokenType.RoundOpen:
{
SuffixexpNode suffixExp = ParseSuffixExp(tokens);
if(index >= tokens.Length)
{
if(suffixExp is SuffixexpNode.Normal)
{
throw new Exception($"{startRegion}: Expected function call, got normal suffix expression");
}
if(suffixExp is SuffixexpNode.Functioncall functioncall)
{
return new StatNode.Functioncall(node: functioncall.node);
}
}
else
{
switch(tokens[index].type)
{
case TokenType.Equals:
{
index += 1;
List<VarNode> lhs = [SuffixExpToVar(suffixExp)];
ExplistNode rhs = ParseExplist(tokens);
return new StatNode.Assignment(lhs: new(vars: lhs, startRegion: startRegion, endRegion: suffixExp.endRegion), rhs: rhs, startRegion: startRegion, endRegion: rhs.endRegion);
}
case TokenType.Comma:
{
List<VarNode> vars = [SuffixExpToVar(suffixExp)];
while(index < tokens.Length && tokens[index].type == TokenType.Comma)
{
index += 1;
vars.Add(ParseVar(tokens));
}
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected `=` for assignment starting at {startRegion}");
}
if(tokens[index].type != TokenType.Equals)
{
throw new Exception($"{tokens[index].region}: Expected `=` for assignment starting at {startRegion}, got {tokens[index].type}");
}
index += 1;
VarlistNode varlistNode = new(vars: vars, startRegion: startRegion, endRegion: vars[^1].endRegion);
ExplistNode rhs = ParseExplist(tokens);
return new StatNode.Assignment(lhs: varlistNode, rhs: rhs, startRegion: startRegion, endRegion: rhs.endRegion);
}
}
if(suffixExp is SuffixexpNode.Normal)
{
throw new Exception($"{startRegion}: Expected function call, got normal suffix expression");
}
if(suffixExp is SuffixexpNode.Functioncall functioncall)
{
return new StatNode.Functioncall(node: functioncall.node);
}
}
}
break;
default:
{
throw new Exception($"Unexpected token {tokens[index]} at {startRegion}");
}
}
throw new NotImplementedException();
}
private VarNode ParseVar(Token[] tokens)
{
return SuffixExpToVar(ParseSuffixExp(tokens));
}
private static VarNode SuffixExpToVar(SuffixexpNode suffixExp)
{
if(suffixExp is not SuffixexpNode.Normal normal)
{
throw new Exception($"Expected a normal suffix expression to convert to var at {suffixExp.startRegion}-{suffixExp.endRegion}");
}
if(normal.suffixes.Count == 0)
{
if(normal.firstPart is not SuffixexpFirstPart.Name name)
{
throw new Exception($"Expected a name as first part of suffix expression to convert to var at {normal.firstPart.startRegion}-{normal.firstPart.endRegion}");
}
return new VarNode.Name(name: name.name, startRegion: suffixExp.startRegion, endRegion: suffixExp.endRegion);
}
SuffixexpSuffix last = normal.suffixes[^1];
_ = normal.suffixes.Remove(last);
return last switch
{
SuffixexpSuffix.Dot dot => new VarNode.Member(node: new(name: dot.name, value: normal, startRegion: suffixExp.startRegion, endRegion: suffixExp.endRegion), startRegion: suffixExp.startRegion, endRegion: dot.endRegion),
SuffixexpSuffix.Indexed indexed => new VarNode.Indexed(node: new(index: indexed.node, value: normal, startRegion: suffixExp.startRegion, endRegion: suffixExp.endRegion), startRegion: suffixExp.startRegion, endRegion: indexed.endRegion),
_ => throw new Exception($"Expected dot or indexed suffix expression to convert to var at {last.startRegion}-{last.endRegion}")
};
}
private SuffixexpNode ParseSuffixExp(Token[] tokens)
{
// primaryexp { '.' 'Name' | '[' exp']' | ':' 'Name' args | args }
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}");
}
CodeRegion startRegion = tokens[index].region;
SuffixexpFirstPart firstPart;
switch(tokens[index].type)
{
case TokenType.Name:
{
string name = ((Token.StringData)tokens[index].data!).data;
index += 1;
firstPart = new SuffixexpFirstPart.Name(name, startRegion, startRegion);
}
break;
case TokenType.RoundOpen:
{
index += 1;
ExpNode inner = ParseExp(tokens);
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected `)` to close bracketed expression starting at {startRegion}");
}
if(tokens[index].type != TokenType.RoundClosed)
{
throw new Exception($"{tokens[index].region}: Expected `)` to close bracketed expression at {startRegion}, got {tokens[index].type}");
}
firstPart = new SuffixexpFirstPart.BracketedExp(node: inner, startRegion: startRegion, endRegion: tokens[index].region);
index += 1;
}
break;
default:
throw new Exception($"{startRegion}: Expected either `)` or name as first part of suffix-expression, got {tokens[index].type}");
}
List<SuffixexpSuffix> suffixes = [];
bool shouldContinue = true;
while(shouldContinue && index < tokens.Length)
{
CodeRegion suffixStartRegion = tokens[index].region;
switch(tokens[index].type)
{
case TokenType.Dot:
{
index += 1;
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected name in dotted suffix of suffix expression starting at {startRegion}");
}
if(tokens[index].type != TokenType.Name)
{
throw new Exception($"{tokens[index].region}: Expected name in dotted suffix of suffix expression at {startRegion}, got {tokens[index].type}");
}
CodeRegion suffixEndRegion = tokens[index].region;
string name = ((Token.StringData)tokens[index].data!).data;
index += 1;
suffixes.Add(new SuffixexpSuffix.Dot(name, startRegion: suffixStartRegion, endRegion: suffixEndRegion));
}
break;
case TokenType.SquareOpen:
{
index += 1;
ExpNode inner = ParseExp(tokens);
suffixes.Add(new SuffixexpSuffix.Indexed(node: inner, startRegion: suffixStartRegion, endRegion: tokens[index - 1].region));
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected `]` to close indexed suffix of suffix-expression starting at {suffixStartRegion}");
}
if(tokens[index].type != TokenType.SquareClosed)
{
throw new Exception($"{tokens[index].region}: Expected `]` to close indexed suffix of suffix-expression at {suffixStartRegion}, got {tokens[index].type}");
}
index += 1;
}
break;
case TokenType.Colon:
{
index += 1;
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected name as first arg after `:` in args suffix in suffix-expression starting at {suffixStartRegion}");
}
if(tokens[index].type != TokenType.Name)
{
throw new Exception($"{tokens[index].region}: Expected name as first arg after `:` in args suffix in suffix-expression at {suffixStartRegion}, got {tokens[index].type}");
}
string name = ((Token.StringData)tokens[index].data!).data;
index += 1;
ArgsNode args = ParseArgs(tokens);
suffixes.Add(new SuffixexpSuffix.ArgsFirstArg(new(name, rest: args, startRegion: suffixStartRegion, endRegion: args.endRegion)));
}
break;
case TokenType.RoundOpen:
case TokenType.CurlyOpen:
case TokenType.StringLiteral:
{
ArgsNode args = ParseArgs(tokens);
suffixes.Add(new SuffixexpSuffix.Args(node: args, startRegion: suffixStartRegion, endRegion: args.endRegion));
}
break;
default:
{
shouldContinue = false;
}
break;
}
}
CodeRegion endRegion;
if(suffixes.Count > 0)
{
endRegion = suffixes[^1].endRegion;
SuffixexpNode? ret = suffixes[^1] switch
{
SuffixexpSuffix.Args args => new SuffixexpNode.Functioncall(
node: new(
function: new SuffixexpNode.Normal(firstPart, suffixes[..^1], startRegion, args.endRegion),
args: args.node,
objectArg: null
)
),
SuffixexpSuffix.ArgsFirstArg node => new SuffixexpNode.Functioncall(
node: new(
function: new SuffixexpNode.Normal(firstPart: firstPart, suffixes: suffixes[..^1], startRegion, node.endRegion),
objectArg: node.node.name,
args: node.node.rest
)
),
_ => null,
};
if(ret is not null)
{
return ret;
}
}
else
{
endRegion = firstPart.endRegion;
}
return new SuffixexpNode.Normal(firstPart: firstPart, suffixes: suffixes, startRegion: startRegion, endRegion: endRegion);
}
private ArgsNode ParseArgs(Token[] tokens)
{
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected `(`, `{{` or string to start args");
}
CodeRegion startRegion = tokens[index].region;
switch(tokens[index].type)
{
case TokenType.RoundOpen:
{
index += 1;
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected explist or `)` to continue args starting at {startRegion}");
}
if(tokens[index].type == TokenType.RoundClosed)
{
CodeRegion endRegion = tokens[index].region;
index += 1;
return new ArgsNode.Bracketed(null, startRegion: startRegion, endRegion: endRegion);
}
ExplistNode exps = ParseExplist(tokens);
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected `)` to close args starting at {startRegion}");
}
if(tokens[index].type != TokenType.RoundClosed)
{
throw new Exception($"{tokens[index].region}: Expected `)` to close args starting at {startRegion}, got {tokens[index].type}");
}
index += 1;
return new ArgsNode.Bracketed(node: exps, startRegion: startRegion, endRegion: exps.endRegion);
}
case TokenType.CurlyOpen:
{
TableconstructorNode node = ParseTableconstructor(tokens);
return new ArgsNode.Tableconstructor(node: node, startRegion: startRegion, endRegion: node.endRegion);
}
case TokenType.StringLiteral:
{
string value = ((Token.StringData)tokens[index].data!).data;
index += 1;
return new ArgsNode.Literal(name: value, startRegion: startRegion, endRegion: startRegion);
}
default:
throw new Exception($"{tokens[index].region}: Expected explist or `)` to continue args starting at {startRegion}");
}
}
private TableconstructorNode ParseTableconstructor(Token[] tokens)
{
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected `{{` to start tableconstructor");
}
CodeRegion startRegion = tokens[index].region;
if(tokens[index].type != TokenType.CurlyOpen)
{
throw new Exception($"{startRegion}: Expected `{{` to start tableconstructor, got {tokens[index].type}");
}
index += 1;
if(index < tokens.Length && tokens[index].type == TokenType.CurlyClosed)
{
CodeRegion emptyEndRegion = tokens[index].region;
index += 1;
return new TableconstructorNode(exps: null, startRegion: startRegion, endRegion: emptyEndRegion);
}
FieldlistNode fields = ParseFieldlist(tokens);
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected `}}` to close tableconstructor starting at {startRegion}");
}
if(tokens[index].type != TokenType.CurlyClosed)
{
throw new Exception($"{tokens[index].region}: Expected `}}` to close tableconstructor starting at {startRegion}, got {tokens[index].type}");
}
CodeRegion endRegion = tokens[index].region;
index += 1;
return new TableconstructorNode(exps: fields, startRegion: startRegion, endRegion: endRegion);
}
private FieldlistNode ParseFieldlist(Token[] tokens)
{
List<FieldNode> fields = [ParseField(tokens)];
while(index < tokens.Length && IsFieldsep(tokens[index]))
{
index += 1;
if(index < tokens.Length && tokens[index].type is TokenType.SquareOpen or
TokenType.Name or TokenType.Nil or TokenType.True or TokenType.False or TokenType.Numeral or TokenType.StringLiteral or
TokenType.DotDotDot or TokenType.CurlyOpen or TokenType.Function or TokenType.Minus or TokenType.Hash or TokenType.Not or TokenType.Nil or TokenType.RoundOpen)
{
fields.Add(ParseField(tokens));
}
}
// NOTE: Since at least 1 field is parsed the list accesses are safe
return new FieldlistNode(exps: fields, startRegion: fields[0].startRegion, endRegion: fields[^1].endRegion);
}
private static bool IsFieldsep(Token token) => token.type is TokenType.Comma or TokenType.Semicolon;
private FieldNode ParseField(Token[] tokens)
{
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected `[` or name to start field");
}
CodeRegion startRegion = tokens[index].region;
switch(tokens[index].type)
{
case TokenType.SquareOpen:
{
index += 1;
ExpNode indexNode = ParseExp(tokens);
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected `]` to close indexed field in indexed field assignment starting at {startRegion}");
}
if(tokens[index].type != TokenType.SquareClosed)
{
throw new Exception($"{tokens[index].region}: Expected `]` to close indexed field starting in indexed field assignment at {startRegion}, got {tokens[index].type}");
}
index += 1;
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected `=` to continue indexed field assignment starting at {startRegion}");
}
if(tokens[index].type != TokenType.Equals)
{
throw new Exception($"{tokens[index].region}: Expected `=` to continue indexed field assignment starting at {startRegion}, got {tokens[index].type}");
}
index += 1;
ExpNode rhs = ParseExp(tokens);
return new FieldNode.IndexedAssignment(node: new(index: indexNode, rhs: rhs, startRegion: startRegion, endRegion: tokens[index - 1].region));
}
case TokenType.Name:
{
if(index + 1 < tokens.Length && tokens[index + 1].type == TokenType.Equals)
{
string name = ((Token.StringData)tokens[index].data!).data;
index += 2;
ExpNode rhs = ParseExp(tokens);
return new FieldNode.Assignment(node: new(lhs: name, rhs: rhs, startRegion: startRegion, endRegion: tokens[index - 1].region));
}
ExpNode exp = ParseExp(tokens);
return new FieldNode.Exp(node: exp, startRegion: startRegion, endRegion: tokens[index - 1].region);
}
default:
{
ExpNode exp = ParseExp(tokens);
return new FieldNode.Exp(node: exp, startRegion: startRegion, endRegion: tokens[index - 1].region);
}
}
}
private AttnamelistNode ParseAttnamelist(Token[] tokens)
{
List<AttnameNode> attnames = [ParseAttname(tokens)];
while(index < tokens.Length && tokens[index].type == TokenType.Comma)
{
index += 1;
attnames.Add(ParseAttname(tokens));
}
// NOTE: Since at least 1 attname is parsed the list accesses are safe
return new(attnames: attnames, startRegion: attnames[0].startRegion, endRegion: attnames[^1].endRegion);
}
private AttnameNode ParseAttname(Token[] tokens)
{
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected name to start attname");
}
CodeRegion startRegion = tokens[index].region;
if(tokens[index].type != TokenType.Name)
{
throw new Exception($"{tokens[index].region}: Expected name to start attname at {startRegion}, got {tokens[index].type}");
}
string name = ((Token.StringData)tokens[index].data!).data;
index += 1;
if(index < tokens.Length && tokens[index].type == TokenType.Lt)
{
index += 1;
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected attribute name of attname starting at {startRegion}");
}
if(tokens[index].type != TokenType.Name)
{
throw new Exception($"{tokens[index].region}: Expected attribute name of attname at {startRegion}, got {tokens[index].type}");
}
string attribute = ((Token.StringData)tokens[index].data!).data;
index += 1;
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected `>` to close attribute of attname starting at {startRegion}");
}
if(tokens[index].type != TokenType.Gt)
{
throw new Exception($"{tokens[index].region}: Expected `>` to close attribute of attname starting at {startRegion}, got {tokens[index].type}");
}
CodeRegion endRegion = tokens[index].region;
index += 1;
return new AttnameNode(name: name, attribute: attribute, startRegion: startRegion, endRegion: endRegion);
}
return new AttnameNode(name: name, attribute: null, startRegion: startRegion, endRegion: startRegion);
}
private FuncbodyNode ParseFuncbody(Token[] tokens)
{
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected `(` to start funcbody");
}
CodeRegion startRegion = tokens[index].region;
if(tokens[index].type != TokenType.RoundOpen)
{
throw new Exception($"{tokens[index].region}: Expected `(` to start funcbody at {startRegion}, got {tokens[index].type}");
}
index += 1;
ParlistNode? pars;
if(index < tokens.Length && tokens[index].type == TokenType.RoundClosed)
{
index += 1;
pars = null;
}
else
{
pars = ParseParlist(tokens);
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected `)` to close parlist of funcbody starting at {startRegion}");
}
if(tokens[index].type != TokenType.RoundClosed)
{
throw new Exception($"{tokens[index].region}: Expected `)` to close parlist of funcbody at {startRegion}, got {tokens[index].type}");
}
index += 1;
}
BlockNode body = ParseBlock(tokens);
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected `end` to close funcbody starting at {startRegion}");
}
if(tokens[index].type != TokenType.End)
{
throw new Exception($"{tokens[index].region}: Expected `end` to close funcbody starting at {startRegion}, got {tokens[index].type}");
}
CodeRegion endRegion = tokens[index].region;
index += 1;
return new FuncbodyNode(pars: pars, body: body, startRegion: startRegion, endRegion: endRegion);
}
private ParlistNode ParseParlist(Token[] tokens)
{
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected `...` or name to start parlist");
}
CodeRegion startRegion = tokens[index].region;
if(tokens[index].type == TokenType.DotDotDot)
{
index += 1;
return new ParlistNode(names: [], hasVarargs: true, startRegion: startRegion, endRegion: startRegion);
}
if(tokens[index].type != TokenType.Name)
{
throw new Exception($"{startRegion}: Expected `...` or name to start parlist, got {tokens[index].type}");
}
List<string> names = [((Token.StringData)tokens[index].data!).data];
index += 1;
while(index < tokens.Length && tokens[index].type == TokenType.Comma)
{
index += 1;
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected `...` or name to continue parlist starting at {startRegion}");
}
switch(tokens[index].type)
{
case TokenType.Name:
{
names.Add(((Token.StringData)tokens[index].data!).data);
index += 1;
}
break;
case TokenType.DotDotDot:
{
CodeRegion endRegion = tokens[index].region;
index += 1;
return new ParlistNode(names: names, hasVarargs: true, startRegion: startRegion, endRegion: endRegion);
};
default:
{
throw new Exception($"{tokens[index].region}: Expected `...` or name to continue parlist starting at {startRegion}, got {tokens[index].type}");
}
}
}
return new ParlistNode(names: names, hasVarargs: false, startRegion: startRegion, endRegion: tokens[index - 1].region);
}
private FuncnameNode ParseFuncname(Token[] tokens)
{
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected name to start funcname");
}
CodeRegion startRegion = tokens[index].region;
if(tokens[index].type != TokenType.Name)
{
throw new Exception($"{startRegion}: Expected name to start funcname, got {tokens[index].type}");
}
string name = ((Token.StringData)tokens[index].data!).data;
index += 1;
List<string> dottedNames = [];
while(index < tokens.Length && tokens[index].type == TokenType.Dot)
{
index += 1;
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected name in dotted funcname starting at {startRegion}");
}
if(tokens[index].type != TokenType.Name)
{
throw new Exception($"{tokens[index].region}: Expected name in dotted funcname starting at {startRegion}, got {tokens[index].type}");
}
dottedNames.Add(((Token.StringData)tokens[index].data!).data);
index += 1;
}
if(index < tokens.Length && tokens[index].type == TokenType.Colon)
{
index += 1;
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected name as first arg name after `:` in funcname starting at {startRegion}");
}
if(tokens[index].type != TokenType.Name)
{
throw new Exception($"{tokens[index].region}: Expected name as first arg name after `:` in funcname starting at {startRegion}, got {tokens[index].type}");
}
string firstArg = ((Token.StringData)tokens[index].data!).data;
CodeRegion endRegion = tokens[index].region;
index += 1;
return new FuncnameNode(name: name, dottedNames: dottedNames, firstArg: firstArg, startRegion: startRegion, endRegion: endRegion);
}
return new FuncnameNode(name: name, dottedNames: dottedNames, firstArg: null, startRegion: startRegion, endRegion: tokens[index - 1].region);
}
private ExplistNode ParseExplist(Token[] tokens)
{
CodeRegion startRegion = tokens[index].region;
List<ExpNode> exps = [ParseExp(tokens)];
while(index < tokens.Length && tokens[index].type == TokenType.Comma)
{
index += 1;
exps.Add(ParseExp(tokens));
}
return new ExplistNode(exps: exps, startRegion: startRegion, endRegion: tokens[index - 1].region);
}
private ExpNode ParseExp(Token[] tokens)
{
ExpNode lhs = ParseExpPrimary(tokens);
return ParseExpPrecedence(tokens, lhs, 0);
}
private ExpNode ParseExpPrecedence(Token[] tokens, ExpNode lhs, int minPrecedence)
{
ExpNode currentLhs = lhs;
while(index < tokens.Length && IsBinop(tokens[index]))
{
CodeRegion startRegion = tokens[index].region;
int precedence = GetPrecedence(tokens[index]);
if(precedence < minPrecedence)
{
break;
}
BinopType op = GetBinopType(tokens[index]);
index += 1;
ExpNode rhs = ParseExpPrimary(tokens);
while(index < tokens.Length && IsBinop(tokens[index]) && (GetPrecedence(tokens[index]) > precedence || (GetPrecedence(tokens[index]) == precedence && IsRightAssociative(tokens[index]))))
{
int associativityBoost = (GetPrecedence(tokens[index]) == precedence) ? 0 : 1;
rhs = ParseExpPrecedence(tokens, lhs: rhs, minPrecedence: precedence + associativityBoost);
}
currentLhs = new ExpNode.Binop(node: new(lhs: currentLhs, type: op, rhs: rhs, startRegion: startRegion, endRegion: tokens[index - 1].region));
}
return currentLhs;
}
private static bool IsRightAssociative(Token token) => token.type is TokenType.DotDot or TokenType.Caret;
private static BinopType GetBinopType(Token token) => token.type switch
{
TokenType.Or => BinopType.LogicalOr,
TokenType.And => BinopType.LogicalAnd,
TokenType.Lt => BinopType.Lt,
TokenType.Gt => BinopType.Gt,
TokenType.LtEquals => BinopType.LtEquals,
TokenType.GtEquals => BinopType.GtEquals,
TokenType.LtLt => BinopType.Shl,
TokenType.GtGt => BinopType.Shr,
TokenType.TildeEquals => BinopType.NotEquals,
TokenType.EqualsEquals => BinopType.Equals,
TokenType.Pipe => BinopType.BinaryOr,
TokenType.Tilde => BinopType.BinaryNot,
TokenType.Ampersand => BinopType.BinaryAnd,
TokenType.DotDot => BinopType.Concat,
TokenType.Plus => BinopType.Add,
TokenType.Minus => BinopType.Sub,
TokenType.Star => BinopType.Mul,
TokenType.Slash => BinopType.Div,
TokenType.SlashSlash => BinopType.IntDiv,
TokenType.Percent => BinopType.Mod,
TokenType.Caret => BinopType.Exp,
_ => throw new Exception($"{token.region}: Expected binary operator with precedence, got {token.type}"),
};
private static int GetPrecedence(Token token) => token.type switch
{
TokenType.Or => 2,
TokenType.And => 4,
TokenType.Lt or TokenType.Gt or TokenType.LtEquals or TokenType.GtEquals or TokenType.TildeEquals or TokenType.EqualsEquals => 6,
TokenType.Pipe => 8,
TokenType.Tilde => 10,
TokenType.Ampersand => 12,
TokenType.LtLt or TokenType.GtGt => 14,
TokenType.DotDot => 16,
TokenType.Plus or TokenType.Minus => 18,
TokenType.Star or TokenType.Slash or TokenType.SlashSlash or TokenType.Percent => 20,
TokenType.Caret => 22,
_ => throw new Exception($"{token.region}: Expected binary operator with precedence, got {token.type}"),
};
private static bool IsBinop(Token token) => token.type switch
{
TokenType.Or or TokenType.And or TokenType.Lt or TokenType.Gt or TokenType.LtEquals or TokenType.GtEquals or TokenType.TildeEquals or TokenType.EqualsEquals or
TokenType.Pipe or TokenType.Tilde or TokenType.Ampersand or TokenType.LtLt or TokenType.GtGt or TokenType.DotDot or TokenType.Plus or TokenType.Minus or
TokenType.Star or TokenType.Slash or TokenType.SlashSlash or TokenType.Percent or TokenType.Caret => true,
_ => false
};
private ExpNode ParseExpPrimary(Token[] tokens)
{
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected primary expression (`nil`, `true`, `false`, numeral, string, `...`, `function`, `{{`, `#`, `not`, `~`)");
}
CodeRegion startRegion = tokens[index].region;
switch(tokens[index].type)
{
case TokenType.Nil:
{
index += 1;
return new ExpNode.Nil(region: startRegion);
}
case TokenType.True:
{
index += 1;
return new ExpNode.True(region: startRegion);
}
case TokenType.False:
{
index += 1;
return new ExpNode.False(region: startRegion);
}
case TokenType.Numeral:
{
INumeral numeral = ((Token.NumeralData)tokens[index].data!).numeral;
index += 1;
return new ExpNode.Numeral(value: numeral, region: startRegion);
}
case TokenType.StringLiteral:
{
string value = ((Token.StringData)tokens[index].data!).data;
index += 1;
return new ExpNode.LiteralString(value: value, region: startRegion);
}
case TokenType.DotDotDot:
{
index += 1;
return new ExpNode.Varargs(region: startRegion);
}
case TokenType.CurlyOpen:
{
TableconstructorNode inner = ParseTableconstructor(tokens);
return new ExpNode.Tableconstructor(node: inner);
}
case TokenType.Function:
{
index += 1;
FuncbodyNode body = ParseFuncbody(tokens);
return new ExpNode.Functiondef(node: body, startRegion: startRegion, endRegion: body.endRegion);
}
case TokenType.Minus:
{
index += 1;
ExpNode unop = ParseExp(tokens);
return new ExpNode.Unop(node: new(type: UnopType.Minus, exp: unop, startRegion: startRegion, endRegion: tokens[index - 1].region));
}
case TokenType.Hash:
{
index += 1;
ExpNode unop = ParseExp(tokens);
return new ExpNode.Unop(node: new(type: UnopType.Length, exp: unop, startRegion: startRegion, endRegion: tokens[index - 1].region));
}
case TokenType.Not:
{
index += 1;
ExpNode unop = ParseExp(tokens);
return new ExpNode.Unop(node: new(type: UnopType.LogicalNot, exp: unop, startRegion: startRegion, endRegion: tokens[index - 1].region));
}
case TokenType.Tilde:
{
index += 1;
ExpNode unop = ParseExp(tokens);
return new ExpNode.Unop(node: new(type: UnopType.BinaryNot, exp: unop, startRegion: startRegion, endRegion: tokens[index - 1].region));
}
default:
{
SuffixexpNode suffixexp = ParseSuffixExp(tokens);
return new ExpNode.Suffixexp(node: suffixexp);
}
}
}
private RetstatNode ParseRetstat(Token[] tokens)
{
if(index >= tokens.Length)
{
throw new Exception($"Index {index} out of bounds of {tokens.Length}, expected `return` to start retstat");
}
CodeRegion startRegion = tokens[index].region;
if(tokens[index].type != TokenType.Return)
{
throw new Exception($"{startRegion}: Expected `return` to start retstat, got {tokens[index].type}");
}
index += 1;
if(index >= tokens.Length)
{
return new RetstatNode(values: null, startRegion: startRegion, endRegion: startRegion);
}
if(tokens[index].type is TokenType.Semicolon or TokenType.Else or TokenType.Elseif or TokenType.End)
{
CodeRegion emptyEndRegion;
if(tokens[index].type == TokenType.Semicolon)
{
emptyEndRegion = tokens[index].region;
index += 1;
}
else
{
emptyEndRegion = startRegion;
}
return new RetstatNode(values: null, startRegion: startRegion, endRegion: emptyEndRegion);
}
ExplistNode values = ParseExplist(tokens);
CodeRegion endRegion;
if(index < tokens.Length && tokens[index].type == TokenType.Semicolon)
{
endRegion = tokens[index].region;
index += 1;
}
else
{
endRegion = values.endRegion;
}
return new RetstatNode(values: values, startRegion: startRegion, endRegion: endRegion);
}
}