Browse Source

Merge pull request #11 from AnnulusGames/fix-parser

Fix parser/compiler
Annulus Games 1 year ago
parent
commit
5f2ef4ae19

+ 56 - 5
src/Lua/CodeAnalysis/Compilation/LuaCompiler.cs

@@ -190,6 +190,11 @@ public sealed class LuaCompiler : ISyntaxNodeVisitor<ScopeCompilationContext, bo
         return true;
         return true;
     }
     }
 
 
+    public bool VisitGroupedExpressionNode(GroupedExpressionNode node, ScopeCompilationContext context)
+    {
+        return node.Expression.Accept(this, context);
+    }
+
     // table
     // table
     public bool VisitTableConstructorExpressionNode(TableConstructorExpressionNode node, ScopeCompilationContext context)
     public bool VisitTableConstructorExpressionNode(TableConstructorExpressionNode node, ScopeCompilationContext context)
     {
     {
@@ -545,7 +550,7 @@ public sealed class LuaCompiler : ISyntaxNodeVisitor<ScopeCompilationContext, bo
     // function declaration
     // function declaration
     public bool VisitFunctionDeclarationExpressionNode(FunctionDeclarationExpressionNode node, ScopeCompilationContext context)
     public bool VisitFunctionDeclarationExpressionNode(FunctionDeclarationExpressionNode node, ScopeCompilationContext context)
     {
     {
-        var funcIndex = CompileFunctionProto(ReadOnlyMemory<char>.Empty, context, node.ParameterNodes, node.Nodes, node.HasVariableArguments);
+        var funcIndex = CompileFunctionProto(ReadOnlyMemory<char>.Empty, context, node.ParameterNodes, node.Nodes, node.HasVariableArguments, false);
 
 
         // push closure instruction
         // push closure instruction
         context.PushInstruction(Instruction.Closure(context.StackPosition, funcIndex), node.Position, true);
         context.PushInstruction(Instruction.Closure(context.StackPosition, funcIndex), node.Position, true);
@@ -562,7 +567,7 @@ public sealed class LuaCompiler : ISyntaxNodeVisitor<ScopeCompilationContext, bo
         });
         });
 
 
         // compile function
         // compile function
-        var funcIndex = CompileFunctionProto(node.Name, context, node.ParameterNodes, node.Nodes, node.HasVariableArguments);
+        var funcIndex = CompileFunctionProto(node.Name, context, node.ParameterNodes, node.Nodes, node.HasVariableArguments, false);
 
 
         // push closure instruction
         // push closure instruction
         context.PushInstruction(Instruction.Closure(context.StackPosition, funcIndex), node.Position, true);
         context.PushInstruction(Instruction.Closure(context.StackPosition, funcIndex), node.Position, true);
@@ -572,7 +577,7 @@ public sealed class LuaCompiler : ISyntaxNodeVisitor<ScopeCompilationContext, bo
 
 
     public bool VisitFunctionDeclarationStatementNode(FunctionDeclarationStatementNode node, ScopeCompilationContext context)
     public bool VisitFunctionDeclarationStatementNode(FunctionDeclarationStatementNode node, ScopeCompilationContext context)
     {
     {
-        var funcIndex = CompileFunctionProto(node.Name, context, node.ParameterNodes, node.Nodes, node.HasVariableArguments);
+        var funcIndex = CompileFunctionProto(node.Name, context, node.ParameterNodes, node.Nodes, node.HasVariableArguments, false);
 
 
         // add closure
         // add closure
         var index = context.Function.GetConstantIndex(node.Name.ToString());
         var index = context.Function.GetConstantIndex(node.Name.ToString());
@@ -586,20 +591,61 @@ public sealed class LuaCompiler : ISyntaxNodeVisitor<ScopeCompilationContext, bo
         return true;
         return true;
     }
     }
 
 
-    int CompileFunctionProto(ReadOnlyMemory<char> functionName, ScopeCompilationContext context, IdentifierNode[] parameters, SyntaxNode[] statements, bool hasVarArg)
+    public bool VisitTableMethodDeclarationStatementNode(TableMethodDeclarationStatementNode node, ScopeCompilationContext context)
+    {
+        var funcIdentifier = node.MemberPath[^1];
+        var funcIndex = CompileFunctionProto(funcIdentifier.Name, context, node.ParameterNodes, node.Nodes, node.HasVariableArguments, node.HasSelfParameter);
+
+        // add closure
+        var index = context.Function.GetConstantIndex(funcIdentifier.Name.ToString());
+
+        var r = context.StackPosition;
+
+        // assign global variable
+        var first = node.MemberPath[0];
+        var tableIndex = LoadIdentifier(first.Name, context, first.Position, true);
+        
+        for (int i = 1; i < node.MemberPath.Length - 1; i++)
+        {
+            var member = node.MemberPath[i];
+            var constant = context.Function.GetConstantIndex(member.Name.ToString());
+            context.PushInstruction(Instruction.GetTable(context.StackPosition, tableIndex, (ushort)(constant + 256)), member.Position, true);
+            tableIndex = context.StackTopPosition;
+        }
+
+        // push closure instruction
+        var closureIndex = context.StackPosition;
+        context.PushInstruction(Instruction.Closure(closureIndex, funcIndex), node.Position, true);
+
+        // set table
+        context.PushInstruction(Instruction.SetTable(tableIndex, (ushort)(index + 256), closureIndex), funcIdentifier.Position);
+
+        context.StackPosition = r;
+        return true;
+    }
+
+    int CompileFunctionProto(ReadOnlyMemory<char> functionName, ScopeCompilationContext context, IdentifierNode[] parameters, SyntaxNode[] statements, bool hasVarArg, bool hasSelfParameter)
     {
     {
         using var funcContext = context.CreateChildFunction();
         using var funcContext = context.CreateChildFunction();
         funcContext.ChunkName = functionName.ToString();
         funcContext.ChunkName = functionName.ToString();
         funcContext.ParameterCount = parameters.Length;
         funcContext.ParameterCount = parameters.Length;
         funcContext.HasVariableArguments = hasVarArg;
         funcContext.HasVariableArguments = hasVarArg;
 
 
+        if (hasSelfParameter)
+        {
+            funcContext.Scope.AddLocalVariable("self".AsMemory(), new()
+            {
+                RegisterIndex = 0,
+            });
+        }
+
         // add arguments
         // add arguments
         for (int i = 0; i < parameters.Length; i++)
         for (int i = 0; i < parameters.Length; i++)
         {
         {
             var parameter = parameters[i];
             var parameter = parameters[i];
             funcContext.Scope.AddLocalVariable(parameter.Name, new()
             funcContext.Scope.AddLocalVariable(parameter.Name, new()
             {
             {
-                RegisterIndex = (byte)i
+                RegisterIndex = (byte)(i + (hasSelfParameter ? 1 : 0)),
             });
             });
         }
         }
 
 
@@ -935,6 +981,11 @@ public sealed class LuaCompiler : ISyntaxNodeVisitor<ScopeCompilationContext, bo
             {
             {
                 return variable.RegisterIndex;
                 return variable.RegisterIndex;
             }
             }
+            else if (p == variable.RegisterIndex)
+            {
+                context.StackPosition++;
+                return p;
+            }
             else
             else
             {
             {
                 context.PushInstruction(Instruction.Move(p, variable.RegisterIndex), sourcePosition, true);
                 context.PushInstruction(Instruction.Move(p, variable.RegisterIndex), sourcePosition, true);

+ 49 - 3
src/Lua/CodeAnalysis/Syntax/DisplayStringSyntaxVisitor.cs

@@ -1,5 +1,4 @@
 using System.Text;
 using System.Text;
-using Lua.CodeAnalysis.Syntax;
 using Lua.CodeAnalysis.Syntax.Nodes;
 using Lua.CodeAnalysis.Syntax.Nodes;
 
 
 namespace Lua.CodeAnalysis.Syntax;
 namespace Lua.CodeAnalysis.Syntax;
@@ -90,11 +89,9 @@ public sealed class DisplayStringSyntaxVisitor : ISyntaxNodeVisitor<DisplayStrin
 
 
     public bool VisitBinaryExpressionNode(BinaryExpressionNode node, Context context)
     public bool VisitBinaryExpressionNode(BinaryExpressionNode node, Context context)
     {
     {
-        context.Append("(");
         node.LeftNode.Accept(this, context);
         node.LeftNode.Accept(this, context);
         context.Append($" {node.OperatorType.ToDisplayString()} ");
         context.Append($" {node.OperatorType.ToDisplayString()} ");
         node.RightNode.Accept(this, context);
         node.RightNode.Accept(this, context);
-        context.Append(")");
         return true;
         return true;
     }
     }
 
 
@@ -193,6 +190,47 @@ public sealed class DisplayStringSyntaxVisitor : ISyntaxNodeVisitor<DisplayStrin
         return true;
         return true;
     }
     }
 
 
+    public bool VisitTableMethodDeclarationStatementNode(TableMethodDeclarationStatementNode node, Context context)
+    {
+        context.Append("function ");
+        
+        for (int i = 0; i < node.MemberPath.Length; i++)
+        {
+            context.Append(node.MemberPath[i].Name.ToString());
+
+            if (i == node.MemberPath.Length - 2 && node.HasSelfParameter)
+            {
+                context.Append(":");
+            }
+            else if (i != node.MemberPath.Length - 1)
+            {
+                context.Append(".");
+            }
+        }
+
+        context.Append("(");
+        AddStatementList(node.ParameterNodes, context);
+        if (node.HasVariableArguments)
+        {
+            if (node.ParameterNodes.Length > 0) context.Append(", ");
+            context.Append("...");
+        }
+        context.AppendLine(")");
+
+        using (context.BeginIndentScope())
+        {
+            foreach (var childNode in node.Nodes)
+            {
+                childNode.Accept(this, context);
+                context.AppendLine();
+            }
+        }
+
+        context.AppendLine("end");
+
+        return true;
+    }
+
     public bool VisitGenericForStatementNode(GenericForStatementNode node, Context context)
     public bool VisitGenericForStatementNode(GenericForStatementNode node, Context context)
     {
     {
         context.Append($"for ");
         context.Append($"for ");
@@ -497,4 +535,12 @@ public sealed class DisplayStringSyntaxVisitor : ISyntaxNodeVisitor<DisplayStrin
             if (i != nodes.Length - 1) context.Append(", ");
             if (i != nodes.Length - 1) context.Append(", ");
         }
         }
     }
     }
+
+    public bool VisitGroupedExpressionNode(GroupedExpressionNode node, Context context)
+    {
+        context.Append("(");
+        node.Expression.Accept(this, context);
+        context.Append(")");
+        return true;
+    }
 }
 }

+ 2 - 0
src/Lua/CodeAnalysis/Syntax/ISyntaxNodeVisitor.cs

@@ -10,6 +10,7 @@ public interface ISyntaxNodeVisitor<TContext, TResult>
     TResult VisitStringLiteralNode(StringLiteralNode node, TContext context);
     TResult VisitStringLiteralNode(StringLiteralNode node, TContext context);
     TResult VisitUnaryExpressionNode(UnaryExpressionNode node, TContext context);
     TResult VisitUnaryExpressionNode(UnaryExpressionNode node, TContext context);
     TResult VisitBinaryExpressionNode(BinaryExpressionNode node, TContext context);
     TResult VisitBinaryExpressionNode(BinaryExpressionNode node, TContext context);
+    TResult VisitGroupedExpressionNode(GroupedExpressionNode node, TContext context);
     TResult VisitIdentifierNode(IdentifierNode node, TContext context);
     TResult VisitIdentifierNode(IdentifierNode node, TContext context);
     TResult VisitDoStatementNode(DoStatementNode node, TContext context);
     TResult VisitDoStatementNode(DoStatementNode node, TContext context);
     TResult VisitFunctionDeclarationExpressionNode(FunctionDeclarationExpressionNode node, TContext context);
     TResult VisitFunctionDeclarationExpressionNode(FunctionDeclarationExpressionNode node, TContext context);
@@ -29,6 +30,7 @@ public interface ISyntaxNodeVisitor<TContext, TResult>
     TResult VisitNumericForStatementNode(NumericForStatementNode node, TContext context);
     TResult VisitNumericForStatementNode(NumericForStatementNode node, TContext context);
     TResult VisitGenericForStatementNode(GenericForStatementNode node, TContext context);
     TResult VisitGenericForStatementNode(GenericForStatementNode node, TContext context);
     TResult VisitTableConstructorExpressionNode(TableConstructorExpressionNode node, TContext context);
     TResult VisitTableConstructorExpressionNode(TableConstructorExpressionNode node, TContext context);
+    TResult VisitTableMethodDeclarationStatementNode(TableMethodDeclarationStatementNode node, TContext context);
     TResult VisitTableIndexerAccessExpressionNode(TableIndexerAccessExpressionNode node, TContext context);
     TResult VisitTableIndexerAccessExpressionNode(TableIndexerAccessExpressionNode node, TContext context);
     TResult VisitTableMemberAccessExpressionNode(TableMemberAccessExpressionNode node, TContext context);
     TResult VisitTableMemberAccessExpressionNode(TableMemberAccessExpressionNode node, TContext context);
     TResult VisitCallTableMethodExpressionNode(CallTableMethodExpressionNode node, TContext context);
     TResult VisitCallTableMethodExpressionNode(CallTableMethodExpressionNode node, TContext context);

+ 91 - 25
src/Lua/CodeAnalysis/Syntax/Lexer.cs

@@ -95,9 +95,6 @@ public ref struct Lexer
             case '}':
             case '}':
                 current = SyntaxToken.RCurly(position);
                 current = SyntaxToken.RCurly(position);
                 return true;
                 return true;
-            case '[':
-                current = SyntaxToken.LSquare(position);
-                return true;
             case ']':
             case ']':
                 current = SyntaxToken.RSquare(position);
                 current = SyntaxToken.RSquare(position);
                 return true;
                 return true;
@@ -108,14 +105,15 @@ public ref struct Lexer
                 // comment
                 // comment
                 if (c2 == '-')
                 if (c2 == '-')
                 {
                 {
+                    var pos = position;
                     Advance(1);
                     Advance(1);
 
 
                     // block comment
                     // block comment
-                    if (TryRead(offset, out var c3) && c3 == '[' &&
-                        TryRead(offset, out var c4) && c4 == '[')
+                    if (span.Length > offset + 1 && span[offset] is '[' && span[offset + 1] is '[' or '=')
                     {
                     {
-                        Advance(2);
-                        ReadUntilEndOfBlockComment(ref span, ref offset);
+                        Advance(1);
+                        (_, _, var isTerminated) = ReadUntilLongBracketEnd(ref span);
+                        if (!isTerminated) LuaParseException.UnfinishedLongComment(ChunkName, pos);
                     }
                     }
                     else // line comment
                     else // line comment
                     {
                     {
@@ -294,35 +292,62 @@ public ref struct Lexer
             return true;
             return true;
         }
         }
 
 
-        // string
+        // short string literal
         if (c1 is '"' or '\'')
         if (c1 is '"' or '\'')
         {
         {
             var quote = c1;
             var quote = c1;
             var stringStartOffset = offset;
             var stringStartOffset = offset;
 
 
+            var isTerminated = false;
             while (span.Length > offset)
             while (span.Length > offset)
             {
             {
                 var c = span[offset];
                 var c = span[offset];
-                if (c == quote) break;
+                if (c == quote)
+                {
+                    isTerminated = true;
+                    break;
+                }
 
 
                 if (c is '\n' or '\r')
                 if (c is '\n' or '\r')
                 {
                 {
-                    throw new LuaParseException(ChunkName, this.position, "error: Unterminated string");
+                    break;
                 }
                 }
 
 
-                // if (c is '\\')
-                // {
-
-                // }
-
                 Advance(1);
                 Advance(1);
             }
             }
 
 
+            if (!isTerminated)
+            {
+                throw new LuaParseException(ChunkName, this.position, "error: Unterminated string");
+            }
+
             current = SyntaxToken.String(Source[stringStartOffset..offset], position);
             current = SyntaxToken.String(Source[stringStartOffset..offset], position);
             Advance(1);
             Advance(1);
             return true;
             return true;
         }
         }
 
 
+        // long string literal
+        if (c1 is '[')
+        {
+            if (c2 is '[' or '=')
+            {
+                (var start, var end, var isTerminated) = ReadUntilLongBracketEnd(ref span);
+
+                if (!isTerminated)
+                {
+                    throw new LuaParseException(ChunkName, this.position, "error: Unterminated string");
+                }
+
+                current = SyntaxToken.String(Source[start..end], position);
+                return true;
+            }
+            else
+            {
+                current = SyntaxToken.LSquare(position);
+                return true;
+            }
+        }
+
         // identifier
         // identifier
         if (IsIdentifier(c1))
         if (IsIdentifier(c1))
         {
         {
@@ -397,24 +422,65 @@ public ref struct Lexer
         }
         }
     }
     }
 
 
-    [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    void ReadUntilEndOfBlockComment(ref ReadOnlySpan<char> span, ref int offset)
-    {
-        var start = position;
+    (int Start, int End, bool IsTerminated) ReadUntilLongBracketEnd(ref ReadOnlySpan<char> span)
+    {   
+        var c = span[offset];
+        var level = 0;
+        while (c is '=')
+        {
+            level++;
+            Advance(1);
+            c = span[offset];
+        }
 
 
-        while (span.Length > offset + 1)
+        Advance(1);
+
+        var startOffset = offset;
+        var endOffset = 0;
+        var isTerminated = false;
+
+        while (span.Length > offset + level + 1)
         {
         {
-            if (span[offset] is ']' &&
-                span[offset + 1] is ']')
+            var current = span[offset];
+
+            // skip first newline
+            if (offset == startOffset)
             {
             {
-                Advance(2);
-                return;
+                if (current == '\r')
+                {
+                    startOffset += 2;
+                    Advance(span[offset + 1] == '\n' ? 2 : 1);
+                    continue;
+                }
+                else if (current == '\n')
+                {
+                    startOffset++;
+                    Advance(1);
+                    continue;
+                }
+            }
+
+            if (current is ']')
+            {
+                endOffset = offset;
+
+                for (int i = 1; i <= level; i++)
+                {
+                    if (span[offset + i] is not '=') goto CONTINUE;
+                }
+
+                if (span[offset + level + 1] is not ']') goto CONTINUE;
+
+                Advance(level + 2);
+                isTerminated = true;
+                break;
             }
             }
 
 
+        CONTINUE:
             Advance(1);
             Advance(1);
         }
         }
 
 
-        LuaParseException.UnfinishedLongComment(ChunkName, start);
+        return (startOffset, endOffset, isTerminated);
     }
     }
 
 
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
     [MethodImpl(MethodImplOptions.AggressiveInlining)]

+ 9 - 0
src/Lua/CodeAnalysis/Syntax/Nodes/GroupedExpressionNode.cs

@@ -0,0 +1,9 @@
+namespace Lua.CodeAnalysis.Syntax.Nodes;
+
+public record GroupedExpressionNode(ExpressionNode Expression, SourcePosition Position) : ExpressionNode(Position)
+{
+    public override TResult Accept<TContext, TResult>(ISyntaxNodeVisitor<TContext, TResult> visitor, TContext context)
+    {
+        return visitor.VisitGroupedExpressionNode(this, context);
+    }
+}

+ 9 - 0
src/Lua/CodeAnalysis/Syntax/Nodes/TableMethodDeclarationStatementNode.cs

@@ -0,0 +1,9 @@
+namespace Lua.CodeAnalysis.Syntax.Nodes;
+
+public record TableMethodDeclarationStatementNode(IdentifierNode[] MemberPath, IdentifierNode[] ParameterNodes, StatementNode[] Nodes, bool HasVariableArguments, bool HasSelfParameter, SourcePosition Position) : StatementNode(Position)
+{
+    public override TResult Accept<TContext, TResult>(ISyntaxNodeVisitor<TContext, TResult> visitor, TContext context)
+    {
+        return visitor.VisitTableMethodDeclarationStatementNode(this, context);
+    }
+}

+ 115 - 83
src/Lua/CodeAnalysis/Syntax/Parser.cs

@@ -106,7 +106,11 @@ public ref struct Parser
                     // local function
                     // local function
                     if (enumerator.Current.Type is SyntaxTokenType.Function)
                     if (enumerator.Current.Type is SyntaxTokenType.Function)
                     {
                     {
-                        return ParseLocalFunctionDeclarationStatement(ref enumerator);
+                        // skip 'function' keyword
+                        CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.Function, out var functionToken);
+                        enumerator.SkipEoL();
+
+                        return ParseLocalFunctionDeclarationStatement(ref enumerator, functionToken);
                     }
                     }
 
 
                     CheckCurrent(ref enumerator, SyntaxTokenType.Identifier);
                     CheckCurrent(ref enumerator, SyntaxTokenType.Identifier);
@@ -124,7 +128,20 @@ public ref struct Parser
                 }
                 }
                 break;
                 break;
             case SyntaxTokenType.Function:
             case SyntaxTokenType.Function:
-                return ParseFunctionDeclarationStatement(ref enumerator);
+                {
+                    // 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);
         LuaParseException.UnexpectedToken(ChunkName, enumerator.Current.Position, enumerator.Current);
@@ -148,19 +165,10 @@ public ref struct Parser
         CheckCurrent(ref enumerator, SyntaxTokenType.Do);
         CheckCurrent(ref enumerator, SyntaxTokenType.Do);
         var doToken = enumerator.Current;
         var doToken = enumerator.Current;
 
 
-        using var statements = new PooledList<StatementNode>(64);
-
         // parse statements
         // parse statements
-        while (enumerator.MoveNext())
-        {
-            if (enumerator.Current.Type is SyntaxTokenType.End) break;
-            if (enumerator.Current.Type is SyntaxTokenType.EndOfLine or SyntaxTokenType.SemiColon) continue;
+        var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End);
 
 
-            var node = ParseStatement(ref enumerator);
-            statements.Add(node);
-        }
-
-        return new DoStatementNode(statements.AsSpan().ToArray(), doToken.Position);
+        return new DoStatementNode(statements, doToken.Position);
     }
     }
 
 
     GotoStatementNode ParseGotoStatement(ref SyntaxTokenEnumerator enumerator)
     GotoStatementNode ParseGotoStatement(ref SyntaxTokenEnumerator enumerator)
@@ -342,39 +350,22 @@ public ref struct Parser
         enumerator.SkipEoL();
         enumerator.SkipEoL();
 
 
         // skip 'do' keyword
         // skip 'do' keyword
-        CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.Do, out _);
-
-        using var statements = new PooledList<StatementNode>(64);
+        CheckCurrent(ref enumerator, SyntaxTokenType.Do);
 
 
         // parse statements
         // parse statements
-        while (enumerator.MoveNext())
-        {
-            if (enumerator.Current.Type is SyntaxTokenType.End) break;
-            if (enumerator.Current.Type is SyntaxTokenType.EndOfLine or SyntaxTokenType.SemiColon) continue;
-
-            var node = ParseStatement(ref enumerator);
-            statements.Add(node);
-        }
+        var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End);
 
 
-        return new WhileStatementNode(condition, statements.AsSpan().ToArray(), whileToken.Position);
+        return new WhileStatementNode(condition, statements, whileToken.Position);
     }
     }
 
 
     RepeatStatementNode ParseRepeatStatement(ref SyntaxTokenEnumerator enumerator)
     RepeatStatementNode ParseRepeatStatement(ref SyntaxTokenEnumerator enumerator)
     {
     {
         // skip 'repeat' keyword
         // skip 'repeat' keyword
-        CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.Repeat, out var repeatToken);
-
-        using var statements = new PooledList<StatementNode>(64);
+        CheckCurrent(ref enumerator, SyntaxTokenType.Repeat);
+        var repeatToken = enumerator.Current;
 
 
         // parse statements
         // parse statements
-        while (enumerator.MoveNext())
-        {
-            if (enumerator.Current.Type is SyntaxTokenType.Until) break;
-            if (enumerator.Current.Type is SyntaxTokenType.EndOfLine or SyntaxTokenType.SemiColon) continue;
-
-            var node = ParseStatement(ref enumerator);
-            statements.Add(node);
-        }
+        var statements = ParseStatementList(ref enumerator, SyntaxTokenType.Until);
 
 
         // skip 'until keyword'
         // skip 'until keyword'
         CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.Until, out _);
         CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.Until, out _);
@@ -383,7 +374,7 @@ public ref struct Parser
         // parse condition
         // parse condition
         var condition = ParseExpression(ref enumerator, GetPrecedence(enumerator.Current.Type));
         var condition = ParseExpression(ref enumerator, GetPrecedence(enumerator.Current.Type));
 
 
-        return new RepeatStatementNode(condition, statements.AsSpan().ToArray(), repeatToken.Position);
+        return new RepeatStatementNode(condition, statements, repeatToken.Position);
     }
     }
 
 
     NumericForStatementNode ParseNumericForStatement(ref SyntaxTokenEnumerator enumerator, SyntaxToken forToken)
     NumericForStatementNode ParseNumericForStatement(ref SyntaxTokenEnumerator enumerator, SyntaxToken forToken)
@@ -428,19 +419,10 @@ public ref struct Parser
         // skip 'do' keyword
         // skip 'do' keyword
         CheckCurrent(ref enumerator, SyntaxTokenType.Do);
         CheckCurrent(ref enumerator, SyntaxTokenType.Do);
 
 
-        using var statements = new PooledList<StatementNode>(64);
-
         // parse statements
         // parse statements
-        while (enumerator.MoveNext())
-        {
-            if (enumerator.Current.Type is SyntaxTokenType.End) break;
-            if (enumerator.Current.Type is SyntaxTokenType.EndOfLine or SyntaxTokenType.SemiColon) continue;
+        var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End);
 
 
-            var node = ParseStatement(ref enumerator);
-            statements.Add(node);
-        }
-
-        return new NumericForStatementNode(varName, initialValueNode, limitNode, stepNode, statements.AsSpan().ToArray(), forToken.Position);
+        return new NumericForStatementNode(varName, initialValueNode, limitNode, stepNode, statements, forToken.Position);
     }
     }
 
 
     GenericForStatementNode ParseGenericForStatement(ref SyntaxTokenEnumerator enumerator, SyntaxToken forToken)
     GenericForStatementNode ParseGenericForStatement(ref SyntaxTokenEnumerator enumerator, SyntaxToken forToken)
@@ -459,39 +441,26 @@ public ref struct Parser
         // skip 'do' keyword
         // skip 'do' keyword
         CheckCurrent(ref enumerator, SyntaxTokenType.Do);
         CheckCurrent(ref enumerator, SyntaxTokenType.Do);
 
 
-        using var statements = new PooledList<StatementNode>(64);
-
         // parse statements
         // parse statements
-        while (enumerator.MoveNext())
-        {
-            if (enumerator.Current.Type is SyntaxTokenType.End) break;
-            if (enumerator.Current.Type is SyntaxTokenType.EndOfLine or SyntaxTokenType.SemiColon) continue;
+        var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End);
 
 
-            var node = ParseStatement(ref enumerator);
-            statements.Add(node);
-        }
-
-        return new GenericForStatementNode(identifiers, expression, statements.AsSpan().ToArray(), forToken.Position);
+        return new GenericForStatementNode(identifiers, expression, statements, forToken.Position);
     }
     }
 
 
-    FunctionDeclarationStatementNode ParseFunctionDeclarationStatement(ref SyntaxTokenEnumerator enumerator)
+    FunctionDeclarationStatementNode ParseFunctionDeclarationStatement(ref SyntaxTokenEnumerator enumerator, SyntaxToken functionToken)
     {
     {
-        var (Name, Identifiers, Statements, HasVariableArgments, FunctionToken) = ParseFunctionDeclarationCore(ref enumerator, false);
-        return new FunctionDeclarationStatementNode(Name, Identifiers, Statements, HasVariableArgments, FunctionToken.Position);
+        var (Name, Identifiers, Statements, HasVariableArgments) = ParseFunctionDeclarationCore(ref enumerator, false);
+        return new FunctionDeclarationStatementNode(Name, Identifiers, Statements, HasVariableArgments, functionToken.Position);
     }
     }
 
 
-    LocalFunctionDeclarationStatementNode ParseLocalFunctionDeclarationStatement(ref SyntaxTokenEnumerator enumerator)
+    LocalFunctionDeclarationStatementNode ParseLocalFunctionDeclarationStatement(ref SyntaxTokenEnumerator enumerator, SyntaxToken functionToken)
     {
     {
-        var (Name, Identifiers, Statements, HasVariableArgments, FunctionToken) = ParseFunctionDeclarationCore(ref enumerator, false);
-        return new LocalFunctionDeclarationStatementNode(Name, Identifiers, Statements, HasVariableArgments, FunctionToken.Position);
+        var (Name, Identifiers, Statements, HasVariableArgments) = ParseFunctionDeclarationCore(ref enumerator, false);
+        return new LocalFunctionDeclarationStatementNode(Name, Identifiers, Statements, HasVariableArgments, functionToken.Position);
     }
     }
 
 
-    (ReadOnlyMemory<char> Name, IdentifierNode[] Identifiers, StatementNode[] Statements, bool HasVariableArgments, SyntaxToken FunctionToken) ParseFunctionDeclarationCore(ref SyntaxTokenEnumerator enumerator, bool isAnonymous)
+    (ReadOnlyMemory<char> Name, IdentifierNode[] Identifiers, StatementNode[] Statements, bool HasVariableArgments) ParseFunctionDeclarationCore(ref SyntaxTokenEnumerator enumerator, bool isAnonymous)
     {
     {
-        // skip 'function' keyword
-        CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.Function, out var functionToken);
-        enumerator.SkipEoL();
-
         ReadOnlyMemory<char> name;
         ReadOnlyMemory<char> name;
 
 
         if (isAnonymous)
         if (isAnonymous)
@@ -524,19 +493,60 @@ public ref struct Parser
         // skip ')'
         // skip ')'
         CheckCurrent(ref enumerator, SyntaxTokenType.RParen);
         CheckCurrent(ref enumerator, SyntaxTokenType.RParen);
 
 
-        using var statements = new PooledList<StatementNode>(64);
-
         // parse statements
         // parse statements
-        while (enumerator.MoveNext())
+        var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End);
+
+        return (name, identifiers, statements, hasVarArg);
+    }
+
+    TableMethodDeclarationStatementNode ParseTableMethodDeclarationStatement(ref SyntaxTokenEnumerator enumerator, SyntaxToken functionToken)
+    {
+        using var names = new PooledList<IdentifierNode>(32);
+        var hasSelfParameter = false;
+
+        while (true)
         {
         {
-            if (enumerator.Current.Type is SyntaxTokenType.End) break;
-            if (enumerator.Current.Type is SyntaxTokenType.EndOfLine or SyntaxTokenType.SemiColon) continue;
+            CheckCurrent(ref enumerator, SyntaxTokenType.Identifier);
+            names.Add(new IdentifierNode(enumerator.Current.Text, enumerator.Current.Position));
+            MoveNextWithValidation(ref enumerator);
+            enumerator.SkipEoL();
 
 
-            var node = ParseStatement(ref enumerator);
-            statements.Add(node);
+            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;
+            }
         }
         }
 
 
-        return (name, identifiers, statements.AsSpan().ToArray(), hasVarArg, functionToken);
+        // 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)
     bool TryParseExpression(ref SyntaxTokenEnumerator enumerator, OperatorPrecedence precedence, [NotNullWhen(true)] out ExpressionNode? result)
@@ -687,6 +697,7 @@ public ref struct Parser
                 case SyntaxTokenType.RCurly:
                 case SyntaxTokenType.RCurly:
                     goto RETURN;
                     goto RETURN;
                 case SyntaxTokenType.EndOfLine:
                 case SyntaxTokenType.EndOfLine:
+                case SyntaxTokenType.SemiColon:
                 case SyntaxTokenType.Comma:
                 case SyntaxTokenType.Comma:
                     continue;
                     continue;
                 case SyntaxTokenType.LSquare:
                 case SyntaxTokenType.LSquare:
@@ -801,10 +812,10 @@ public ref struct Parser
         return result;
         return result;
     }
     }
 
 
-    ExpressionNode ParseGroupedExpression(ref SyntaxTokenEnumerator enumerator)
+    GroupedExpressionNode ParseGroupedExpression(ref SyntaxTokenEnumerator enumerator)
     {
     {
         // skip '('
         // skip '('
-        CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.LParen, out _);
+        CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.LParen, out var lParen);
         enumerator.SkipEoL();
         enumerator.SkipEoL();
 
 
         var expression = ParseExpression(ref enumerator, GetPrecedence(enumerator.Current.Type));
         var expression = ParseExpression(ref enumerator, GetPrecedence(enumerator.Current.Type));
@@ -813,7 +824,7 @@ public ref struct Parser
         // check ')'
         // check ')'
         CheckCurrent(ref enumerator, SyntaxTokenType.RParen);
         CheckCurrent(ref enumerator, SyntaxTokenType.RParen);
 
 
-        return expression;
+        return new GroupedExpressionNode(expression, lParen.Position);
     }
     }
 
 
     ExpressionNode ParseCallFunctionExpression(ref SyntaxTokenEnumerator enumerator, ExpressionNode? function)
     ExpressionNode ParseCallFunctionExpression(ref SyntaxTokenEnumerator enumerator, ExpressionNode? function)
@@ -835,8 +846,12 @@ public ref struct Parser
 
 
     FunctionDeclarationExpressionNode ParseFunctionDeclarationExpression(ref SyntaxTokenEnumerator enumerator)
     FunctionDeclarationExpressionNode ParseFunctionDeclarationExpression(ref SyntaxTokenEnumerator enumerator)
     {
     {
-        var (_, Identifiers, Statements, HasVariableArgments, FunctionToken) = ParseFunctionDeclarationCore(ref enumerator, true);
-        return new FunctionDeclarationExpressionNode(Identifiers, Statements, HasVariableArgments, FunctionToken.Position);
+        // 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)
     ExpressionNode[] ParseCallFunctionArguments(ref SyntaxTokenEnumerator enumerator)
@@ -919,6 +934,23 @@ public ref struct Parser
         return buffer.AsSpan().ToArray();
         return buffer.AsSpan().ToArray();
     }
     }
 
 
+    StatementNode[] ParseStatementList(ref SyntaxTokenEnumerator enumerator, SyntaxTokenType endToken)
+    {
+        using var statements = new PooledList<StatementNode>(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)
     void CheckCurrentAndSkip(ref SyntaxTokenEnumerator enumerator, SyntaxTokenType expectedToken, out SyntaxToken token)
     {
     {
         CheckCurrent(ref enumerator, expectedToken);
         CheckCurrent(ref enumerator, expectedToken);