using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using Lua.Internal; using Lua.CodeAnalysis.Syntax.Nodes; namespace Lua.CodeAnalysis.Syntax; public ref struct Parser { public string? ChunkName { get; init; } PooledList tokens; [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Add(SyntaxToken token) => tokens.Add(token); [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Dispose() { tokens.Dispose(); } public LuaSyntaxTree Parse() { using var root = new PooledList(64); var enumerator = new SyntaxTokenEnumerator(tokens.AsSpan()); while (enumerator.MoveNext()) { if (enumerator.Current.Type is SyntaxTokenType.EndOfLine or SyntaxTokenType.SemiColon) continue; var node = ParseStatement(ref enumerator); root.Add(node); } var tree = new LuaSyntaxTree(root.AsSpan().ToArray()); Dispose(); return tree; } StatementNode ParseStatement(ref SyntaxTokenEnumerator enumerator) { switch (enumerator.Current.Type) { case SyntaxTokenType.Identifier: { var firstExpression = ParseExpression(ref enumerator, OperatorPrecedence.NonOperator); switch (firstExpression) { case CallFunctionExpressionNode callFunctionExpression: return new CallFunctionStatementNode(callFunctionExpression); case CallTableMethodExpressionNode callTableMethodExpression: return new CallTableMethodStatementNode(callTableMethodExpression); default: if (enumerator.GetNext(true).Type is SyntaxTokenType.Comma or SyntaxTokenType.Assignment) { // skip ',' MoveNextWithValidation(ref enumerator); enumerator.SkipEoL(); return ParseAssignmentStatement(firstExpression, ref enumerator); } break; } } break; case SyntaxTokenType.Return: return ParseReturnStatement(ref enumerator); case SyntaxTokenType.Do: return ParseDoStatement(ref enumerator); case SyntaxTokenType.Goto: return ParseGotoStatement(ref enumerator); case SyntaxTokenType.Label: return new LabelStatementNode(enumerator.Current.Text, enumerator.Current.Position); case SyntaxTokenType.If: return ParseIfStatement(ref enumerator); case SyntaxTokenType.While: return ParseWhileStatement(ref enumerator); case SyntaxTokenType.Repeat: return ParseRepeatStatement(ref enumerator); case SyntaxTokenType.For: { // skip 'for' keyword var forToken = enumerator.Current; MoveNextWithValidation(ref enumerator); enumerator.SkipEoL(); if (enumerator.GetNext(true).Type is SyntaxTokenType.Assignment) { return ParseNumericForStatement(ref enumerator, forToken); } else { return ParseGenericForStatement(ref enumerator, forToken); } } case SyntaxTokenType.Break: return new BreakStatementNode(enumerator.Current.Position); case SyntaxTokenType.Local: { // skip 'local' keyword CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.Local, out var localToken); // local function if (enumerator.Current.Type is SyntaxTokenType.Function) { // skip 'function' keyword CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.Function, out var functionToken); enumerator.SkipEoL(); return ParseLocalFunctionDeclarationStatement(ref enumerator, functionToken); } CheckCurrent(ref enumerator, SyntaxTokenType.Identifier); var nextType = enumerator.GetNext().Type; if (nextType is SyntaxTokenType.Comma or SyntaxTokenType.Assignment) { return ParseLocalAssignmentStatement(ref enumerator, localToken); } else if (nextType is SyntaxTokenType.EndOfLine or SyntaxTokenType.SemiColon) { return new LocalAssignmentStatementNode([new IdentifierNode(enumerator.Current.Text, enumerator.Current.Position)], [], localToken.Position); } } break; case SyntaxTokenType.Function: { // skip 'function' keyword CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.Function, out var functionToken); enumerator.SkipEoL(); if (enumerator.GetNext(true).Type is SyntaxTokenType.Dot or SyntaxTokenType.Colon) { return ParseTableMethodDeclarationStatement(ref enumerator, functionToken); } else { return ParseFunctionDeclarationStatement(ref enumerator, functionToken); } } } LuaParseException.UnexpectedToken(ChunkName, enumerator.Current.Position, enumerator.Current); return default!; } ReturnStatementNode ParseReturnStatement(ref SyntaxTokenEnumerator enumerator) { // skip 'return' keyword CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.Return, out var returnToken); // parse parameters var expressions = ParseExpressionList(ref enumerator); return new ReturnStatementNode(expressions, returnToken.Position); } DoStatementNode ParseDoStatement(ref SyntaxTokenEnumerator enumerator) { // check 'do' keyword CheckCurrent(ref enumerator, SyntaxTokenType.Do); var doToken = enumerator.Current; // parse statements var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End); return new DoStatementNode(statements, doToken.Position); } GotoStatementNode ParseGotoStatement(ref SyntaxTokenEnumerator enumerator) { // skip 'goto' keyword CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.Goto, out var gotoToken); CheckCurrent(ref enumerator, SyntaxTokenType.Identifier); return new GotoStatementNode(enumerator.Current.Text, gotoToken.Position); } AssignmentStatementNode ParseAssignmentStatement(ExpressionNode firstExpression, ref SyntaxTokenEnumerator enumerator) { // parse leftNodes using var leftNodes = new PooledList(8); leftNodes.Add(firstExpression); while (enumerator.Current.Type == SyntaxTokenType.Comma) { // skip ',' MoveNextWithValidation(ref enumerator); enumerator.SkipEoL(); // parse identifier CheckCurrent(ref enumerator, SyntaxTokenType.Identifier); leftNodes.Add(ParseExpression(ref enumerator, OperatorPrecedence.NonOperator)); MoveNextWithValidation(ref enumerator); enumerator.SkipEoL(); } // skip '=' if (enumerator.Current.Type is not SyntaxTokenType.Assignment) { enumerator.MovePrevious(); return new AssignmentStatementNode(leftNodes.AsSpan().ToArray(), [], firstExpression.Position); } MoveNextWithValidation(ref enumerator); // parse expressions var expressions = ParseExpressionList(ref enumerator); return new AssignmentStatementNode(leftNodes.AsSpan().ToArray(), expressions, firstExpression.Position); } LocalAssignmentStatementNode ParseLocalAssignmentStatement(ref SyntaxTokenEnumerator enumerator, SyntaxToken localToken) { // parse identifiers var identifiers = ParseIdentifierList(ref enumerator); // skip '=' if (enumerator.Current.Type is not SyntaxTokenType.Assignment) { enumerator.MovePrevious(); return new LocalAssignmentStatementNode(identifiers, [], localToken.Position); } MoveNextWithValidation(ref enumerator); // parse expressions var expressions = ParseExpressionList(ref enumerator); return new LocalAssignmentStatementNode(identifiers, expressions, localToken.Position); } IfStatementNode ParseIfStatement(ref SyntaxTokenEnumerator enumerator) { // skip 'if' keyword CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.If, out var ifToken); enumerator.SkipEoL(); // parse condition var condition = ParseExpression(ref enumerator, GetPrecedence(enumerator.Current.Type)); MoveNextWithValidation(ref enumerator); enumerator.SkipEoL(); // skip 'then' keyword CheckCurrent(ref enumerator, SyntaxTokenType.Then); using var builder = new PooledList(64); using var elseIfBuilder = new PooledList(64); IfStatementNode.ConditionAndThenNodes ifNodes = default!; StatementNode[] elseNodes = []; // if = 0, elseif = 1, else = 2 var state = 0; // parse statements while (true) { if (!enumerator.MoveNext()) { LuaParseException.ExpectedToken(ChunkName, enumerator.Current.Position, SyntaxTokenType.End); } var tokenType = enumerator.Current.Type; if (tokenType is SyntaxTokenType.EndOfLine or SyntaxTokenType.SemiColon) { continue; } if (tokenType is SyntaxTokenType.ElseIf or SyntaxTokenType.Else or SyntaxTokenType.End) { switch (state) { case 0: ifNodes = new() { ConditionNode = condition, ThenNodes = builder.AsSpan().ToArray(), }; builder.Clear(); break; case 1: elseIfBuilder.Add(new() { ConditionNode = condition, ThenNodes = builder.AsSpan().ToArray(), }); builder.Clear(); break; case 2: elseNodes = builder.AsSpan().ToArray(); break; } if (tokenType is SyntaxTokenType.ElseIf) { // skip 'elseif' keywords MoveNextWithValidation(ref enumerator); enumerator.SkipEoL(); // parse condition condition = ParseExpression(ref enumerator, GetPrecedence(enumerator.Current.Type)); MoveNextWithValidation(ref enumerator); enumerator.SkipEoL(); // skip 'then' keyword CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.Then, out _); enumerator.SkipEoL(); // set elseif state state = 1; } else if (tokenType is SyntaxTokenType.Else) { // skip 'else' keywords MoveNextWithValidation(ref enumerator); enumerator.SkipEoL(); // set else state state = 2; } else if (tokenType is SyntaxTokenType.End) { goto RETURN; } } var node = ParseStatement(ref enumerator); builder.Add(node); } RETURN: return new IfStatementNode(ifNodes, elseIfBuilder.AsSpan().ToArray(), elseNodes, ifToken.Position); } WhileStatementNode ParseWhileStatement(ref SyntaxTokenEnumerator enumerator) { // skip 'while' keyword CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.While, out var whileToken); enumerator.SkipEoL(); // parse condition var condition = ParseExpression(ref enumerator, GetPrecedence(enumerator.Current.Type)); MoveNextWithValidation(ref enumerator); enumerator.SkipEoL(); // skip 'do' keyword CheckCurrent(ref enumerator, SyntaxTokenType.Do); // parse statements var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End); return new WhileStatementNode(condition, statements, whileToken.Position); } RepeatStatementNode ParseRepeatStatement(ref SyntaxTokenEnumerator enumerator) { // skip 'repeat' keyword CheckCurrent(ref enumerator, SyntaxTokenType.Repeat); var repeatToken = enumerator.Current; // parse statements var statements = ParseStatementList(ref enumerator, SyntaxTokenType.Until); // skip 'until keyword' CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.Until, out _); enumerator.SkipEoL(); // parse condition var condition = ParseExpression(ref enumerator, GetPrecedence(enumerator.Current.Type)); return new RepeatStatementNode(condition, statements, repeatToken.Position); } NumericForStatementNode ParseNumericForStatement(ref SyntaxTokenEnumerator enumerator, SyntaxToken forToken) { // parse variable name CheckCurrent(ref enumerator, SyntaxTokenType.Identifier); var varName = enumerator.Current.Text; MoveNextWithValidation(ref enumerator); enumerator.SkipEoL(); // skip '=' CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.Assignment, out _); enumerator.SkipEoL(); // parse initial value var initialValueNode = ParseExpression(ref enumerator, OperatorPrecedence.NonOperator); MoveNextWithValidation(ref enumerator); enumerator.SkipEoL(); // skip ',' CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.Comma, out _); enumerator.SkipEoL(); // parse limit var limitNode = ParseExpression(ref enumerator, OperatorPrecedence.NonOperator); MoveNextWithValidation(ref enumerator); enumerator.SkipEoL(); // parse stepNode ExpressionNode? stepNode = null; if (enumerator.Current.Type is SyntaxTokenType.Comma) { // skip ',' enumerator.MoveNext(); // parse step stepNode = ParseExpression(ref enumerator, OperatorPrecedence.NonOperator); MoveNextWithValidation(ref enumerator); enumerator.SkipEoL(); } // skip 'do' keyword CheckCurrent(ref enumerator, SyntaxTokenType.Do); // parse statements var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End); return new NumericForStatementNode(varName, initialValueNode, limitNode, stepNode, statements, forToken.Position); } GenericForStatementNode ParseGenericForStatement(ref SyntaxTokenEnumerator enumerator, SyntaxToken forToken) { var identifiers = ParseIdentifierList(ref enumerator); enumerator.SkipEoL(); // skip 'in' keyword CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.In, out _); enumerator.SkipEoL(); var expression = ParseExpression(ref enumerator, OperatorPrecedence.NonOperator); MoveNextWithValidation(ref enumerator); enumerator.SkipEoL(); // skip 'do' keyword CheckCurrent(ref enumerator, SyntaxTokenType.Do); // parse statements var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End); return new GenericForStatementNode(identifiers, expression, statements, forToken.Position); } FunctionDeclarationStatementNode ParseFunctionDeclarationStatement(ref SyntaxTokenEnumerator enumerator, SyntaxToken functionToken) { var (Name, Identifiers, Statements, HasVariableArgments) = ParseFunctionDeclarationCore(ref enumerator, false); return new FunctionDeclarationStatementNode(Name, Identifiers, Statements, HasVariableArgments, functionToken.Position); } LocalFunctionDeclarationStatementNode ParseLocalFunctionDeclarationStatement(ref SyntaxTokenEnumerator enumerator, SyntaxToken functionToken) { var (Name, Identifiers, Statements, HasVariableArgments) = ParseFunctionDeclarationCore(ref enumerator, false); return new LocalFunctionDeclarationStatementNode(Name, Identifiers, Statements, HasVariableArgments, functionToken.Position); } (ReadOnlyMemory Name, IdentifierNode[] Identifiers, StatementNode[] Statements, bool HasVariableArgments) ParseFunctionDeclarationCore(ref SyntaxTokenEnumerator enumerator, bool isAnonymous) { ReadOnlyMemory name; if (isAnonymous) { name = ReadOnlyMemory.Empty; } else { // parse function name CheckCurrent(ref enumerator, SyntaxTokenType.Identifier); name = enumerator.Current.Text; MoveNextWithValidation(ref enumerator); enumerator.SkipEoL(); } // skip '(' CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.LParen, out _); enumerator.SkipEoL(); // parse parameters var identifiers = enumerator.Current.Type is SyntaxTokenType.Identifier ? ParseIdentifierList(ref enumerator) : []; // check variable arguments var hasVarArg = enumerator.Current.Type is SyntaxTokenType.VarArg; if (hasVarArg) enumerator.MoveNext(); // skip ')' CheckCurrent(ref enumerator, SyntaxTokenType.RParen); // parse statements var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End); return (name, identifiers, statements, hasVarArg); } TableMethodDeclarationStatementNode ParseTableMethodDeclarationStatement(ref SyntaxTokenEnumerator enumerator, SyntaxToken functionToken) { using var names = new PooledList(32); var hasSelfParameter = false; while (true) { CheckCurrent(ref enumerator, SyntaxTokenType.Identifier); names.Add(new IdentifierNode(enumerator.Current.Text, enumerator.Current.Position)); MoveNextWithValidation(ref enumerator); enumerator.SkipEoL(); if (enumerator.Current.Type is SyntaxTokenType.Dot or SyntaxTokenType.Colon) { if (hasSelfParameter) { LuaParseException.UnexpectedToken(ChunkName, enumerator.Current.Position, enumerator.Current); } hasSelfParameter = enumerator.Current.Type is SyntaxTokenType.Colon; MoveNextWithValidation(ref enumerator); enumerator.SkipEoL(); } else if (enumerator.Current.Type is SyntaxTokenType.LParen) { // skip '(' MoveNextWithValidation(ref enumerator); enumerator.SkipEoL(); break; } } // parse parameters var identifiers = enumerator.Current.Type is SyntaxTokenType.Identifier ? ParseIdentifierList(ref enumerator) : []; // check variable arguments var hasVarArg = enumerator.Current.Type is SyntaxTokenType.VarArg; if (hasVarArg) enumerator.MoveNext(); // skip ')' CheckCurrent(ref enumerator, SyntaxTokenType.RParen); // parse statements var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End); return new TableMethodDeclarationStatementNode(names.AsSpan().ToArray(), identifiers, statements, hasVarArg, hasSelfParameter, functionToken.Position); } bool TryParseExpression(ref SyntaxTokenEnumerator enumerator, OperatorPrecedence precedence, [NotNullWhen(true)] out ExpressionNode? result) { result = enumerator.Current.Type switch { SyntaxTokenType.Identifier => enumerator.GetNext(true).Type switch { SyntaxTokenType.LParen or SyntaxTokenType.String => ParseCallFunctionExpression(ref enumerator, null), SyntaxTokenType.LSquare or SyntaxTokenType.Dot or SyntaxTokenType.Colon => ParseTableAccessExpression(ref enumerator, null), _ => new IdentifierNode(enumerator.Current.Text, enumerator.Current.Position), }, SyntaxTokenType.Number => new NumericLiteralNode(double.Parse(enumerator.Current.Text.Span), enumerator.Current.Position), SyntaxTokenType.String => new StringLiteralNode(enumerator.Current.Text.ToString(), enumerator.Current.Position), SyntaxTokenType.True => new BooleanLiteralNode(true, enumerator.Current.Position), SyntaxTokenType.False => new BooleanLiteralNode(false, enumerator.Current.Position), SyntaxTokenType.Nil => new NilLiteralNode(enumerator.Current.Position), SyntaxTokenType.VarArg => new VariableArgumentsExpressionNode(enumerator.Current.Position), SyntaxTokenType.Subtraction => ParseMinusNumber(ref enumerator), SyntaxTokenType.Not or SyntaxTokenType.Length => ParseUnaryExpression(ref enumerator, enumerator.Current), SyntaxTokenType.LParen => ParseGroupedExpression(ref enumerator), SyntaxTokenType.LCurly => ParseTableConstructorExpression(ref enumerator), SyntaxTokenType.Function => ParseFunctionDeclarationExpression(ref enumerator), _ => null, }; if (result == null) return false; // nested table access & function call RECURSIVE: enumerator.SkipEoL(); var nextType = enumerator.GetNext().Type; if (nextType is SyntaxTokenType.LSquare or SyntaxTokenType.Dot or SyntaxTokenType.Colon) { MoveNextWithValidation(ref enumerator); result = ParseTableAccessExpression(ref enumerator, result); goto RECURSIVE; } else if (nextType is SyntaxTokenType.LParen or SyntaxTokenType.String or SyntaxTokenType.LCurly) { MoveNextWithValidation(ref enumerator); result = ParseCallFunctionExpression(ref enumerator, result); goto RECURSIVE; } // binary expression while (true) { var opPrecedence = GetPrecedence(enumerator.GetNext().Type); if (precedence >= opPrecedence) break; MoveNextWithValidation(ref enumerator); result = ParseBinaryExpression(ref enumerator, opPrecedence, result); enumerator.SkipEoL(); } return true; } ExpressionNode ParseExpression(ref SyntaxTokenEnumerator enumerator, OperatorPrecedence precedence) { if (!TryParseExpression(ref enumerator, precedence, out var result)) { throw new LuaParseException(ChunkName, enumerator.Current.Position, "Unexpected token <{enumerator.Current.Type}>"); } return result; } ExpressionNode ParseMinusNumber(ref SyntaxTokenEnumerator enumerator) { var token = enumerator.Current; if (enumerator.GetNext(true).Type is SyntaxTokenType.Number) { enumerator.MoveNext(); enumerator.SkipEoL(); return new NumericLiteralNode(-double.Parse(enumerator.Current.Text.Span), token.Position); } else { return ParseUnaryExpression(ref enumerator, token); } } UnaryExpressionNode ParseUnaryExpression(ref SyntaxTokenEnumerator enumerator, SyntaxToken operatorToken) { var operatorType = enumerator.Current.Type switch { SyntaxTokenType.Subtraction => UnaryOperator.Negate, SyntaxTokenType.Not => UnaryOperator.Not, SyntaxTokenType.Length => UnaryOperator.Length, _ => throw new LuaParseException(ChunkName, operatorToken.Position, $"unexpected symbol near '{enumerator.Current.Text}'"), }; MoveNextWithValidation(ref enumerator); var right = ParseExpression(ref enumerator, OperatorPrecedence.Unary); return new UnaryExpressionNode(operatorType, right, operatorToken.Position); } BinaryExpressionNode ParseBinaryExpression(ref SyntaxTokenEnumerator enumerator, OperatorPrecedence precedence, ExpressionNode left) { var operatorToken = enumerator.Current; var operatorType = operatorToken.Type switch { SyntaxTokenType.Addition => BinaryOperator.Addition, SyntaxTokenType.Subtraction => BinaryOperator.Subtraction, SyntaxTokenType.Multiplication => BinaryOperator.Multiplication, SyntaxTokenType.Division => BinaryOperator.Division, SyntaxTokenType.Modulo => BinaryOperator.Modulo, SyntaxTokenType.Exponentiation => BinaryOperator.Exponentiation, SyntaxTokenType.Equality => BinaryOperator.Equality, SyntaxTokenType.Inequality => BinaryOperator.Inequality, SyntaxTokenType.LessThan => BinaryOperator.LessThan, SyntaxTokenType.LessThanOrEqual => BinaryOperator.LessThanOrEqual, SyntaxTokenType.GreaterThan => BinaryOperator.GreaterThan, SyntaxTokenType.GreaterThanOrEqual => BinaryOperator.GreaterThanOrEqual, SyntaxTokenType.And => BinaryOperator.And, SyntaxTokenType.Or => BinaryOperator.Or, SyntaxTokenType.Concat => BinaryOperator.Concat, _ => throw new LuaParseException(ChunkName, enumerator.Current.Position, $"unexpected symbol near '{enumerator.Current.Text}'"), }; enumerator.SkipEoL(); MoveNextWithValidation(ref enumerator); enumerator.SkipEoL(); var right = ParseExpression(ref enumerator, precedence); return new BinaryExpressionNode(operatorType, left, right, operatorToken.Position); } TableConstructorExpressionNode ParseTableConstructorExpression(ref SyntaxTokenEnumerator enumerator) { CheckCurrent(ref enumerator, SyntaxTokenType.LCurly); var startToken = enumerator.Current; using var items = new PooledList(16); while (enumerator.MoveNext()) { var currentToken = enumerator.Current; switch (currentToken.Type) { case SyntaxTokenType.RCurly: goto RETURN; case SyntaxTokenType.EndOfLine: case SyntaxTokenType.SemiColon: case SyntaxTokenType.Comma: continue; case SyntaxTokenType.LSquare: // general style ([key] = value) enumerator.MoveNext(); var keyExpression = ParseExpression(ref enumerator, OperatorPrecedence.NonOperator); enumerator.MoveNext(); // skip '] =' CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.RSquare, out _); CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.Assignment, out _); var valueExpression = ParseExpression(ref enumerator, OperatorPrecedence.NonOperator); items.Add(new GeneralTableConstructorField(keyExpression, valueExpression, currentToken.Position)); break; case SyntaxTokenType.Identifier when enumerator.GetNext(true).Type is SyntaxTokenType.Assignment: // record style (key = value) var name = enumerator.Current.Text; // skip key and '=' enumerator.MoveNext(); enumerator.MoveNext(); var expression = ParseExpression(ref enumerator, OperatorPrecedence.NonOperator); items.Add(new RecordTableConstructorField(name.ToString(), expression, currentToken.Position)); break; default: // list style items.Add(new ListTableConstructorField(ParseExpression(ref enumerator, OperatorPrecedence.NonOperator), currentToken.Position)); break; } } RETURN: return new TableConstructorExpressionNode(items.AsSpan().ToArray(), startToken.Position); } ExpressionNode ParseTableAccessExpression(ref SyntaxTokenEnumerator enumerator, ExpressionNode? parentTable) { IdentifierNode? identifier = null; if (parentTable == null) { // parse identifier CheckCurrent(ref enumerator, SyntaxTokenType.Identifier); identifier = new IdentifierNode(enumerator.Current.Text, enumerator.Current.Position); MoveNextWithValidation(ref enumerator); enumerator.SkipEoL(); } ExpressionNode result; var current = enumerator.Current; if (current.Type is SyntaxTokenType.LSquare) { // indexer access -- table[key] // skip '[' MoveNextWithValidation(ref enumerator); enumerator.SkipEoL(); // parse key expression var keyExpression = ParseExpression(ref enumerator, OperatorPrecedence.NonOperator); MoveNextWithValidation(ref enumerator); enumerator.SkipEoL(); // check ']' CheckCurrent(ref enumerator, SyntaxTokenType.RSquare); result = new TableIndexerAccessExpressionNode(identifier ?? parentTable!, keyExpression, current.Position); } else if (current.Type is SyntaxTokenType.Dot) { // member access -- table.key // skip '.' MoveNextWithValidation(ref enumerator); enumerator.SkipEoL(); // parse identifier CheckCurrent(ref enumerator, SyntaxTokenType.Identifier); var key = enumerator.Current.Text.ToString(); result = new TableMemberAccessExpressionNode(identifier ?? parentTable!, key, current.Position); } else if (current.Type is SyntaxTokenType.Colon) { // self method call -- table:method(arg0, arg1, ...) // skip ':' MoveNextWithValidation(ref enumerator); enumerator.SkipEoL(); // parse identifier CheckCurrent(ref enumerator, SyntaxTokenType.Identifier); var methodName = enumerator.Current.Text; MoveNextWithValidation(ref enumerator); enumerator.SkipEoL(); // parse arguments var arguments = ParseCallFunctionArguments(ref enumerator); result = new CallTableMethodExpressionNode(identifier ?? parentTable!, methodName.ToString(), arguments, current.Position); } else { LuaParseException.SyntaxError(ChunkName, current.Position, current); return null!; // dummy } return result; } GroupedExpressionNode ParseGroupedExpression(ref SyntaxTokenEnumerator enumerator) { // skip '(' CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.LParen, out var lParen); enumerator.SkipEoL(); var expression = ParseExpression(ref enumerator, GetPrecedence(enumerator.Current.Type)); MoveNextWithValidation(ref enumerator); // check ')' CheckCurrent(ref enumerator, SyntaxTokenType.RParen); return new GroupedExpressionNode(expression, lParen.Position); } ExpressionNode ParseCallFunctionExpression(ref SyntaxTokenEnumerator enumerator, ExpressionNode? function) { // parse name if (function == null) { CheckCurrent(ref enumerator, SyntaxTokenType.Identifier); function = new IdentifierNode(enumerator.Current.Text, enumerator.Current.Position); enumerator.MoveNext(); enumerator.SkipEoL(); } // parse parameters var parameters = ParseCallFunctionArguments(ref enumerator); return new CallFunctionExpressionNode(function, parameters); } FunctionDeclarationExpressionNode ParseFunctionDeclarationExpression(ref SyntaxTokenEnumerator enumerator) { // skip 'function' keyword CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.Function, out var functionToken); enumerator.SkipEoL(); var (_, Identifiers, Statements, HasVariableArgments) = ParseFunctionDeclarationCore(ref enumerator, true); return new FunctionDeclarationExpressionNode(Identifiers, Statements, HasVariableArgments, functionToken.Position); } ExpressionNode[] ParseCallFunctionArguments(ref SyntaxTokenEnumerator enumerator) { if (enumerator.Current.Type is SyntaxTokenType.String) { return [new StringLiteralNode(enumerator.Current.Text.ToString(), enumerator.Current.Position)]; } else if (enumerator.Current.Type is SyntaxTokenType.LCurly) { return [ParseTableConstructorExpression(ref enumerator)]; } // check and skip '(' CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.LParen, out _); ExpressionNode[] arguments; if (enumerator.Current.Type is SyntaxTokenType.RParen) { // parameterless arguments = []; } else { // parse arguments arguments = ParseExpressionList(ref enumerator); enumerator.SkipEoL(); MoveNextWithValidation(ref enumerator); enumerator.SkipEoL(); // check ')' CheckCurrent(ref enumerator, SyntaxTokenType.RParen); } return arguments; } ExpressionNode[] ParseExpressionList(ref SyntaxTokenEnumerator enumerator) { using var builder = new PooledList(8); while (true) { if (!TryParseExpression(ref enumerator, OperatorPrecedence.NonOperator, out var expression)) { enumerator.MovePrevious(); break; } builder.Add(expression); enumerator.SkipEoL(); if (enumerator.GetNext().Type != SyntaxTokenType.Comma) break; MoveNextWithValidation(ref enumerator); enumerator.SkipEoL(); if (!enumerator.MoveNext()) break; } return builder.AsSpan().ToArray(); } IdentifierNode[] ParseIdentifierList(ref SyntaxTokenEnumerator enumerator) { using var buffer = new PooledList(8); while (true) { if (enumerator.Current.Type != SyntaxTokenType.Identifier) break; var identifier = new IdentifierNode(enumerator.Current.Text, enumerator.Current.Position); buffer.Add(identifier); MoveNextWithValidation(ref enumerator); enumerator.SkipEoL(); if (enumerator.Current.Type != SyntaxTokenType.Comma) break; MoveNextWithValidation(ref enumerator); enumerator.SkipEoL(); } return buffer.AsSpan().ToArray(); } StatementNode[] ParseStatementList(ref SyntaxTokenEnumerator enumerator, SyntaxTokenType endToken) { using var statements = new PooledList(64); // parse statements while (enumerator.MoveNext()) { if (enumerator.Current.Type == endToken) break; if (enumerator.Current.Type is SyntaxTokenType.EndOfLine or SyntaxTokenType.SemiColon) continue; var node = ParseStatement(ref enumerator); statements.Add(node); } return statements.AsSpan().ToArray(); } void CheckCurrentAndSkip(ref SyntaxTokenEnumerator enumerator, SyntaxTokenType expectedToken, out SyntaxToken token) { CheckCurrent(ref enumerator, expectedToken); token = enumerator.Current; MoveNextWithValidation(ref enumerator); } [MethodImpl(MethodImplOptions.AggressiveInlining)] void CheckCurrent(ref SyntaxTokenEnumerator enumerator, SyntaxTokenType expectedToken) { if (enumerator.Current.Type != expectedToken) { LuaParseException.ExpectedToken(ChunkName, enumerator.Current.Position, expectedToken); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] void MoveNextWithValidation(ref SyntaxTokenEnumerator enumerator) { if (!enumerator.MoveNext()) LuaParseException.SyntaxError(ChunkName, enumerator.Current.Position, enumerator.Current); } [MethodImpl(MethodImplOptions.AggressiveInlining)] static OperatorPrecedence GetPrecedence(SyntaxTokenType type) { return type switch { SyntaxTokenType.Addition or SyntaxTokenType.Subtraction => OperatorPrecedence.Addition, SyntaxTokenType.Multiplication or SyntaxTokenType.Division or SyntaxTokenType.Modulo => OperatorPrecedence.Multiplication, SyntaxTokenType.Equality or SyntaxTokenType.Inequality or SyntaxTokenType.LessThan or SyntaxTokenType.LessThanOrEqual or SyntaxTokenType.GreaterThan or SyntaxTokenType.GreaterThanOrEqual => OperatorPrecedence.Relational, SyntaxTokenType.Concat => OperatorPrecedence.Concat, SyntaxTokenType.Exponentiation => OperatorPrecedence.Exponentiation, SyntaxTokenType.And => OperatorPrecedence.And, SyntaxTokenType.Or => OperatorPrecedence.Or, _ => OperatorPrecedence.NonOperator, }; } }