From b2c6f8cadd67035b15e40c3d1f4e3ca370136c8a Mon Sep 17 00:00:00 2001 From: Alexander Luzgarev Date: Thu, 16 Jul 2020 16:28:49 +0200 Subject: [PATCH 01/14] Implement compilation --- Parser/Compilation.cs | 8 ++ Parser/Emitting/Emitter.cs | 161 ++++++++++++++++++++++++++ Parser/Parser.csproj | 1 + Solution.sln | 8 +- cmc/Program.cs | 60 ++++++++++ cmc/cmc.csproj | 18 +++ cmi/Program.cs | 1 - examples/Directory.Build.props | 7 ++ examples/Directory.Build.targets | 16 +++ examples/helloworld/helloworld.cmproj | 8 ++ 10 files changed, 286 insertions(+), 2 deletions(-) create mode 100644 Parser/Emitting/Emitter.cs create mode 100644 cmc/Program.cs create mode 100644 cmc/cmc.csproj create mode 100644 examples/Directory.Build.props create mode 100644 examples/Directory.Build.targets create mode 100644 examples/helloworld/helloworld.cmproj diff --git a/Parser/Compilation.cs b/Parser/Compilation.cs index d566e7e..1a9756f 100644 --- a/Parser/Compilation.cs +++ b/Parser/Compilation.cs @@ -1,4 +1,5 @@ using Parser.Binding; +using Parser.Emitting; namespace Parser { @@ -16,6 +17,13 @@ namespace Parser return new Compilation(syntaxTree); } + public void Emit(string[] references, string outputPath) + { + var emitter = new Emitter(); + var boundProgram = GetBoundProgram(); + emitter.Emit(boundProgram, references, outputPath); + } + private BoundProgram GetBoundProgram() { return Binder.BindProgram(_syntaxTree); diff --git a/Parser/Emitting/Emitter.cs b/Parser/Emitting/Emitter.cs new file mode 100644 index 0000000..01b68e0 --- /dev/null +++ b/Parser/Emitting/Emitter.cs @@ -0,0 +1,161 @@ +using Mono.Cecil; +using Mono.Cecil.Cil; +using Parser.Binding; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Parser.Emitting +{ + public class Emitter + { + private Dictionary _knownTypes = new Dictionary(); + + private static TypeReference ResolveAndImportType( + string typeName, + List assemblies, + AssemblyDefinition assemblyDefinition) + { + var foundTypes = assemblies.SelectMany(a => a.Modules) + .SelectMany(m => m.Types) + .Where(t => t.FullName == typeName) + .ToArray(); + if (foundTypes.Length == 1) + { + var typeReference = assemblyDefinition.MainModule.ImportReference(foundTypes[0]); + return typeReference; + } + else if (foundTypes.Length == 0) + { + throw new Exception($"Cannot find type {typeName}"); + } + else + { + throw new Exception($"Ambiguous type {typeName}"); + } + } + + private static MethodReference ResolveAndImportMethod( + string typeName, + string methodName, + string[] parameterTypeNames, + List assemblies, + AssemblyDefinition assemblyDefinition) + { + var foundTypes = assemblies.SelectMany(a => a.Modules) + .SelectMany(m => m.Types) + .Where(t => t.FullName == typeName) + .ToArray(); + if (foundTypes.Length == 1) + { + var foundType = foundTypes[0]; + var methods = foundType.Methods.Where(m => m.Name == methodName); + + foreach (var method in methods) + { + if (method.Parameters.Count != parameterTypeNames.Length) + continue; + + var allParametersMatch = true; + + for (var i = 0; i < parameterTypeNames.Length; i++) + { + if (method.Parameters[i].ParameterType.FullName != parameterTypeNames[i]) + { + allParametersMatch = false; + break; + } + } + + if (!allParametersMatch) + continue; + + return assemblyDefinition.MainModule.ImportReference(method); + } + + throw new Exception($"Required method {typeName}.{methodName} not found."); + } + else if (foundTypes.Length == 0) + { + throw new Exception($"Required type {typeName} not found."); + } + else + { + throw new Exception($"Required type {typeName} is ambiguous."); + } + } + + public void Emit(BoundProgram program, string[] references, string outputFileName) + { + var assemblies = new List(); + + foreach (var reference in references) + { + try + { + var assembly = AssemblyDefinition.ReadAssembly(reference); + assemblies.Add(assembly); + } + catch (BadImageFormatException) + { + throw new Exception($"Invalid reference '{reference}'."); + } + } + + var moduleName = Path.GetFileNameWithoutExtension(outputFileName); + var assemblyName = new AssemblyNameDefinition( + name: moduleName, + version: new Version(1, 0)); + var assemblyDefinition = AssemblyDefinition.CreateAssembly( + assemblyName: assemblyName, + moduleName: moduleName, + kind: ModuleKind.Console); + var builtInTypes = new[] + { + "System.Object", + "System.Void" + }; + + // Resolve built-in types and methods. + foreach (var typeName in builtInTypes) + { + var typeReference = ResolveAndImportType(typeName, assemblies, assemblyDefinition); + _knownTypes.Add(typeName, typeReference); + } + + var objectType = _knownTypes["System.Object"]; + var voidType = _knownTypes["System.Void"]; + + var consoleWriteLineReference = ResolveAndImportMethod( + typeName: "System.Console", + methodName: "WriteLine", + parameterTypeNames: new[] { "System.Object" }, + assemblies: assemblies, + assemblyDefinition: assemblyDefinition); + + // Create type. + var typeDefinition = new TypeDefinition( + @namespace: "", + name: "Program", + attributes: TypeAttributes.Abstract | TypeAttributes.Sealed, + baseType: objectType); + assemblyDefinition.MainModule.Types.Add(typeDefinition); + + // Create method. + var methodDefinition = new MethodDefinition( + name: "Main", + attributes: MethodAttributes.Static | MethodAttributes.Private, + returnType: voidType); + var ilProcessor = methodDefinition.Body.GetILProcessor(); + ilProcessor.Emit(OpCodes.Ldstr, "Hello world!"); + ilProcessor.Emit(OpCodes.Call, consoleWriteLineReference); + ilProcessor.Emit(OpCodes.Ret); + + typeDefinition.Methods.Add(methodDefinition); + assemblyDefinition.EntryPoint = methodDefinition; + + assemblyDefinition.Write(outputFileName); + } + } +} diff --git a/Parser/Parser.csproj b/Parser/Parser.csproj index acfb9dc..d9ed05a 100644 --- a/Parser/Parser.csproj +++ b/Parser/Parser.csproj @@ -7,5 +7,6 @@ + \ No newline at end of file diff --git a/Solution.sln b/Solution.sln index 56fc479..69dedfa 100644 --- a/Solution.sln +++ b/Solution.sln @@ -23,7 +23,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Repl", "Repl\Repl.csproj", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MApplication", "MApplication\MApplication.csproj", "{A7EE271C-8822-43EA-BA13-5D6D5DC5B581}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "cmi", "cmi\cmi.csproj", "{C2447F0B-733D-4755-A104-5B82E24D3F47}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "cmi", "cmi\cmi.csproj", "{C2447F0B-733D-4755-A104-5B82E24D3F47}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "cmc", "cmc\cmc.csproj", "{4200B289-ED2B-4C6F-AFDF-EC91FB3837B3}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -63,6 +65,10 @@ Global {C2447F0B-733D-4755-A104-5B82E24D3F47}.Debug|Any CPU.Build.0 = Debug|Any CPU {C2447F0B-733D-4755-A104-5B82E24D3F47}.Release|Any CPU.ActiveCfg = Release|Any CPU {C2447F0B-733D-4755-A104-5B82E24D3F47}.Release|Any CPU.Build.0 = Release|Any CPU + {4200B289-ED2B-4C6F-AFDF-EC91FB3837B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4200B289-ED2B-4C6F-AFDF-EC91FB3837B3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4200B289-ED2B-4C6F-AFDF-EC91FB3837B3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4200B289-ED2B-4C6F-AFDF-EC91FB3837B3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/cmc/Program.cs b/cmc/Program.cs new file mode 100644 index 0000000..d1e1762 --- /dev/null +++ b/cmc/Program.cs @@ -0,0 +1,60 @@ +using Parser; +using System; +using System.IO; +using Mono.Options; +using System.Collections.Generic; + +namespace cmc +{ + class Program + { + static int Main(string[] args) + { + var referencePaths = new List(); + var outputPath = (string?)null; + var moduleName = (string?)null; + var helpRequested = false; + var sourcePaths = new List(); + var options = new OptionSet + { + "usage: cmc [options]", + { "r=", "The {path} of an assembly to reference", v => referencePaths.Add(v) }, + { "o=", "The output {path} of the assembly to create", v => outputPath = v }, + { "m=", "The {name} of the module", v => moduleName = v }, + { "?|h|help", "Prints help", v => helpRequested = true }, + { "<>", v => sourcePaths.Add(v) } + }; + + options.Parse(args); + if (helpRequested) + { + options.WriteOptionDescriptions(Console.Out); + return 0; + } + + if (sourcePaths.Count > 1) + { + Console.Error.WriteLine("Cannot compile more than one file."); + return -1; + } + + if (outputPath == null) + { + outputPath = Path.ChangeExtension(sourcePaths[0], ".exe"); + } + + if (moduleName == null) + { + moduleName = Path.GetFileNameWithoutExtension(outputPath); + } + + + var sourcePath = sourcePaths[0]; + var text = File.ReadAllText(sourcePath); + var tree = SyntaxTree.Parse(text); + var compilation = Compilation.Create(tree); + compilation.Emit(referencePaths.ToArray(), outputPath); + return 0; + } + } +} diff --git a/cmc/cmc.csproj b/cmc/cmc.csproj new file mode 100644 index 0000000..a6b36b8 --- /dev/null +++ b/cmc/cmc.csproj @@ -0,0 +1,18 @@ + + + + Exe + net5.0 + enable + preview + + + + + + + + + + + diff --git a/cmi/Program.cs b/cmi/Program.cs index f1f0042..c0b3932 100644 --- a/cmi/Program.cs +++ b/cmi/Program.cs @@ -4,7 +4,6 @@ using System.IO; namespace cmi { - class Program { static void Main(string[] args) diff --git a/examples/Directory.Build.props b/examples/Directory.Build.props new file mode 100644 index 0000000..2876fae --- /dev/null +++ b/examples/Directory.Build.props @@ -0,0 +1,7 @@ + + + + .m + + + \ No newline at end of file diff --git a/examples/Directory.Build.targets b/examples/Directory.Build.targets new file mode 100644 index 0000000..e75f061 --- /dev/null +++ b/examples/Directory.Build.targets @@ -0,0 +1,16 @@ + + + + + + + + + + + + + diff --git a/examples/helloworld/helloworld.cmproj b/examples/helloworld/helloworld.cmproj new file mode 100644 index 0000000..2082704 --- /dev/null +++ b/examples/helloworld/helloworld.cmproj @@ -0,0 +1,8 @@ + + + + Exe + net5.0 + + + From 62713311cc56f00c30762b55e7fb39a2225e25e8 Mon Sep 17 00:00:00 2001 From: Alexander Luzgarev Date: Fri, 17 Jul 2020 09:53:19 +0200 Subject: [PATCH 02/14] Fix treatment of trailing semicolons --- Parser.Tests/MParserShould.cs | 31 ++++++++++-- Parser/Binding/Binder.cs | 3 +- Parser/Binding/BoundNodeFactory.cs | 7 ++- Parser/Binding/BoundRoot.cs | 13 +++-- Parser/Binding/BoundTreeRewriter.cs | 2 +- Parser/Evaluator.cs | 3 +- Parser/Internal/MParserGreen.cs | 28 ++++++++--- Parser/Internal/SyntaxFactory.Generated.cs | 9 +++- Parser/Internal/SyntaxNode.Generated.cs | 55 +++++++++++++++++++--- Parser/SyntaxDefinition.xml | 4 ++ Parser/SyntaxNode.Generated.cs | 42 ++++++++++++++++- Parser/SyntaxVisitor.Generated.cs | 5 ++ Parser/TokenKind.cs | 3 ++ 13 files changed, 175 insertions(+), 30 deletions(-) diff --git a/Parser.Tests/MParserShould.cs b/Parser.Tests/MParserShould.cs index 9a5a63e..249a23b 100644 --- a/Parser.Tests/MParserShould.cs +++ b/Parser.Tests/MParserShould.cs @@ -37,12 +37,33 @@ namespace Parser.Tests var actual = sut.Parse(); var statement = actual.Root.Body.Statements[0].AsNode(); Assert.IsType(statement); - if (statement is null) - { - throw new System.Exception(); - } + Assert.IsType(((ExpressionStatementSyntaxNode)statement!).Expression); + } - Assert.IsType(((ExpressionStatementSyntaxNode)statement).Expression); + [Fact] + public void ParseExpressionStatementWithSemicolon() + { + var text = "2 + 3;"; + var sut = GetSut(text); + var actual = sut.Parse(); + Assert.Single(actual.Root.Body.Statements); + var statement = actual.Root.Body.Statements[0].AsNode(); + Assert.IsType(statement); + Assert.IsType(((ExpressionStatementSyntaxNode)statement!).Expression); + } + + [Fact] + public void ParseExpressionStatementWithSemicolonAfterNewLine() + { + var text = "2 + 3\n;"; + var sut = GetSut(text); + var actual = sut.Parse(); + Assert.Equal(2, actual.Root.Body.Statements.Count); + var statement1 = actual.Root.Body.Statements[0].AsNode(); + Assert.IsType(statement1); + Assert.IsType(((ExpressionStatementSyntaxNode)statement1!).Expression); + var statement2 = actual.Root.Body.Statements[1].AsToken(); + Assert.Equal(TokenKind.SemicolonToken, statement2.Kind); } [Fact] diff --git a/Parser/Binding/Binder.cs b/Parser/Binding/Binder.cs index 9b8fccd..6848a28 100644 --- a/Parser/Binding/Binder.cs +++ b/Parser/Binding/Binder.cs @@ -258,7 +258,8 @@ namespace Parser.Binding private BoundExpressionStatement BindExpressionStatement(ExpressionStatementSyntaxNode node) { var expression = BindExpression(node.Expression); - return ExpressionStatement(node, expression); + var discardResult = node.Semicolon is not null; + return ExpressionStatement(node, expression, discardResult); } private BoundExpression BindExpression(ExpressionSyntaxNode node) diff --git a/Parser/Binding/BoundNodeFactory.cs b/Parser/Binding/BoundNodeFactory.cs index f8b60ab..2513301 100644 --- a/Parser/Binding/BoundNodeFactory.cs +++ b/Parser/Binding/BoundNodeFactory.cs @@ -25,9 +25,12 @@ namespace Parser.Binding return new BoundBlockStatement(syntax, statements); } - public static BoundExpressionStatement ExpressionStatement(SyntaxNode syntax, BoundExpression expression) + public static BoundExpressionStatement ExpressionStatement( + SyntaxNode syntax, + BoundExpression expression, + bool discardResult) { - return new BoundExpressionStatement(syntax, expression); + return new BoundExpressionStatement(syntax, expression, discardResult); } public static BoundIfStatement IfStatement( diff --git a/Parser/Binding/BoundRoot.cs b/Parser/Binding/BoundRoot.cs index 5f9d0ca..e59f7dc 100644 --- a/Parser/Binding/BoundRoot.cs +++ b/Parser/Binding/BoundRoot.cs @@ -118,15 +118,18 @@ namespace Parser.Binding public class BoundExpressionStatement : BoundStatement { - public BoundExpression Expression { get; } - - public override BoundNodeKind Kind => BoundNodeKind.ExpressionStatement; - - public BoundExpressionStatement(SyntaxNode syntax, BoundExpression expression) + public BoundExpressionStatement(SyntaxNode syntax, BoundExpression expression, bool discardResult) : base(syntax) { Expression = expression; + DiscardResult = discardResult; } + + public BoundExpression Expression { get; } + + public bool DiscardResult { get; } + + public override BoundNodeKind Kind => BoundNodeKind.ExpressionStatement; } public class BoundForStatement : BoundStatement diff --git a/Parser/Binding/BoundTreeRewriter.cs b/Parser/Binding/BoundTreeRewriter.cs index 83dd602..24e997d 100644 --- a/Parser/Binding/BoundTreeRewriter.cs +++ b/Parser/Binding/BoundTreeRewriter.cs @@ -147,7 +147,7 @@ namespace Parser.Binding return node; } - return ExpressionStatement(node.Syntax, expression); + return ExpressionStatement(node.Syntax, expression, node.DiscardResult); } public virtual BoundStatement RewriteEmptyStatement(BoundEmptyStatement node) diff --git a/Parser/Evaluator.cs b/Parser/Evaluator.cs index b03aa7d..e4b56f6 100644 --- a/Parser/Evaluator.cs +++ b/Parser/Evaluator.cs @@ -210,7 +210,8 @@ namespace Parser private MObject? EvaluateExpressionStatement(BoundExpressionStatement node) { - return EvaluateExpression(node.Expression); + var result = EvaluateExpression(node.Expression); + return node.DiscardResult ? null : result; } private MObject? EvaluateExpression(BoundExpression node) diff --git a/Parser/Internal/MParserGreen.cs b/Parser/Internal/MParserGreen.cs index dfce71e..a58011a 100644 --- a/Parser/Internal/MParserGreen.cs +++ b/Parser/Internal/MParserGreen.cs @@ -845,7 +845,28 @@ namespace Parser.Internal throw new Exception("Expression statement cannot be empty."); } - return Factory.ExpressionStatementSyntax(expression); + if (CurrentToken.Kind == TokenKind.SemicolonToken && !TriviaContainsNewLine(expression.TrailingTrivia)) + { + var semicolon = EatToken(); + return Factory.ExpressionStatementSyntax( + expression, + Factory.TrailingSemicolonSyntax(semicolon)); + } + + return Factory.ExpressionStatementSyntax(expression, semicolon: null); + } + + private bool TriviaContainsNewLine(IReadOnlyList trivia) + { + foreach(var t in trivia) + { + if (t.Text.Contains('\n')) + { + return true; + } + } + + return false; } private AttributeAssignmentSyntaxNode? ParseAttributeAssignment() @@ -1179,11 +1200,6 @@ namespace Parser.Internal } } - if (CurrentToken.Kind == TokenKind.OpenSquareBracketToken) - { - return ParseExpressionStatement(); - } - if (CurrentToken.Kind == TokenKind.SemicolonToken) { return Factory.EmptyStatementSyntax(EatToken()); diff --git a/Parser/Internal/SyntaxFactory.Generated.cs b/Parser/Internal/SyntaxFactory.Generated.cs index 826f4d1..6280330 100644 --- a/Parser/Internal/SyntaxFactory.Generated.cs +++ b/Parser/Internal/SyntaxFactory.Generated.cs @@ -78,9 +78,14 @@ namespace Parser.Internal return new TryCatchStatementSyntaxNode(tryKeyword, tryBody, catchClause, endKeyword); } - public ExpressionStatementSyntaxNode ExpressionStatementSyntax(ExpressionSyntaxNode expression) + public ExpressionStatementSyntaxNode ExpressionStatementSyntax(ExpressionSyntaxNode expression, TrailingSemicolonSyntaxNode? semicolon) { - return new ExpressionStatementSyntaxNode(expression); + return new ExpressionStatementSyntaxNode(expression, semicolon); + } + + public TrailingSemicolonSyntaxNode TrailingSemicolonSyntax(SyntaxToken semicolon) + { + return new TrailingSemicolonSyntaxNode(semicolon); } public EmptyStatementSyntaxNode EmptyStatementSyntax(SyntaxToken semicolon) diff --git a/Parser/Internal/SyntaxNode.Generated.cs b/Parser/Internal/SyntaxNode.Generated.cs index 149e4bc..b020d0e 100644 --- a/Parser/Internal/SyntaxNode.Generated.cs +++ b/Parser/Internal/SyntaxNode.Generated.cs @@ -779,18 +779,23 @@ namespace Parser.Internal internal class ExpressionStatementSyntaxNode : StatementSyntaxNode { internal readonly ExpressionSyntaxNode _expression; - internal ExpressionStatementSyntaxNode(ExpressionSyntaxNode expression): base(TokenKind.ExpressionStatement) + internal readonly TrailingSemicolonSyntaxNode? _semicolon; + internal ExpressionStatementSyntaxNode(ExpressionSyntaxNode expression, TrailingSemicolonSyntaxNode? semicolon): base(TokenKind.ExpressionStatement) { - Slots = 1; + Slots = 2; this.AdjustWidth(expression); _expression = expression; + this.AdjustWidth(semicolon); + _semicolon = semicolon; } - internal ExpressionStatementSyntaxNode(ExpressionSyntaxNode expression, TokenDiagnostic[] diagnostics): base(TokenKind.ExpressionStatement, diagnostics) + internal ExpressionStatementSyntaxNode(ExpressionSyntaxNode expression, TrailingSemicolonSyntaxNode? semicolon, TokenDiagnostic[] diagnostics): base(TokenKind.ExpressionStatement, diagnostics) { - Slots = 1; + Slots = 2; this.AdjustWidth(expression); _expression = expression; + this.AdjustWidth(semicolon); + _semicolon = semicolon; } internal override Parser.SyntaxNode CreateRed(Parser.SyntaxNode parent, int position) @@ -800,14 +805,52 @@ namespace Parser.Internal public override GreenNode SetDiagnostics(TokenDiagnostic[] diagnostics) { - return new ExpressionStatementSyntaxNode(_expression, diagnostics); + return new ExpressionStatementSyntaxNode(_expression, _semicolon, diagnostics); } public override GreenNode? GetSlot(int i) { return i switch { - 0 => _expression, _ => null + 0 => _expression, 1 => _semicolon, _ => null + } + + ; + } + } + + internal class TrailingSemicolonSyntaxNode : SyntaxNode + { + internal readonly SyntaxToken _semicolon; + internal TrailingSemicolonSyntaxNode(SyntaxToken semicolon): base(TokenKind.TrailingSemicolon) + { + Slots = 1; + this.AdjustWidth(semicolon); + _semicolon = semicolon; + } + + internal TrailingSemicolonSyntaxNode(SyntaxToken semicolon, TokenDiagnostic[] diagnostics): base(TokenKind.TrailingSemicolon, diagnostics) + { + Slots = 1; + this.AdjustWidth(semicolon); + _semicolon = semicolon; + } + + internal override Parser.SyntaxNode CreateRed(Parser.SyntaxNode parent, int position) + { + return new Parser.TrailingSemicolonSyntaxNode(parent, this, position); + } + + public override GreenNode SetDiagnostics(TokenDiagnostic[] diagnostics) + { + return new TrailingSemicolonSyntaxNode(_semicolon, diagnostics); + } + + public override GreenNode? GetSlot(int i) + { + return i switch + { + 0 => _semicolon, _ => null } ; diff --git a/Parser/SyntaxDefinition.xml b/Parser/SyntaxDefinition.xml index fcbc09a..be2d64f 100644 --- a/Parser/SyntaxDefinition.xml +++ b/Parser/SyntaxDefinition.xml @@ -88,6 +88,10 @@ + + + + diff --git a/Parser/SyntaxNode.Generated.cs b/Parser/SyntaxNode.Generated.cs index 22a137a..9bc5b65 100644 --- a/Parser/SyntaxNode.Generated.cs +++ b/Parser/SyntaxNode.Generated.cs @@ -852,6 +852,7 @@ namespace Parser public class ExpressionStatementSyntaxNode : StatementSyntaxNode { private SyntaxNode? _expression; + private SyntaxNode? _semicolon; internal ExpressionStatementSyntaxNode(SyntaxNode parent, Internal.GreenNode green, int position): base(parent, green, position) { } @@ -865,11 +866,20 @@ namespace Parser } } + public TrailingSemicolonSyntaxNode? Semicolon + { + get + { + var red = this.GetRed(ref this._semicolon, 1); + return red is null ? default : (TrailingSemicolonSyntaxNode)red; + } + } + internal override SyntaxNode? GetNode(int i) { return i switch { - 0 => GetRed(ref _expression!, 0), _ => null + 0 => GetRed(ref _expression!, 0), 1 => GetRed(ref _semicolon, 1), _ => null } ; @@ -881,6 +891,36 @@ namespace Parser } } + public class TrailingSemicolonSyntaxNode : SyntaxNode + { + internal TrailingSemicolonSyntaxNode(SyntaxNode parent, Internal.GreenNode green, int position): base(parent, green, position) + { + } + + public SyntaxToken Semicolon + { + get + { + return new SyntaxToken(this, ((Parser.Internal.TrailingSemicolonSyntaxNode)_green)._semicolon, this.GetChildPosition(0)); + } + } + + internal override SyntaxNode? GetNode(int i) + { + return i switch + { + _ => null + } + + ; + } + + public override void Accept(SyntaxVisitor visitor) + { + visitor.VisitTrailingSemicolon(this); + } + } + public class EmptyStatementSyntaxNode : StatementSyntaxNode { internal EmptyStatementSyntaxNode(SyntaxNode parent, Internal.GreenNode green, int position): base(parent, green, position) diff --git a/Parser/SyntaxVisitor.Generated.cs b/Parser/SyntaxVisitor.Generated.cs index 7249d3b..9e2035e 100644 --- a/Parser/SyntaxVisitor.Generated.cs +++ b/Parser/SyntaxVisitor.Generated.cs @@ -83,6 +83,11 @@ namespace Parser DefaultVisit(node); } + public virtual void VisitTrailingSemicolon(TrailingSemicolonSyntaxNode node) + { + DefaultVisit(node); + } + public virtual void VisitEmptyStatement(EmptyStatementSyntaxNode node) { DefaultVisit(node); diff --git a/Parser/TokenKind.cs b/Parser/TokenKind.cs index 4645763..7b52cfc 100644 --- a/Parser/TokenKind.cs +++ b/Parser/TokenKind.cs @@ -135,6 +135,9 @@ // a list of syntax nodes and/or tokens. List, + // a semicolon that marks expression statements with discarded results. + TrailingSemicolon, + // STATEMENTS // The name ends with "Declaration" or "Statement". From c8025ff723edfeeab9d801732ad6a7688c46eb8b Mon Sep 17 00:00:00 2001 From: Alexander Luzgarev Date: Sat, 18 Jul 2020 14:01:43 +0200 Subject: [PATCH 03/14] Fix compiling --- ConsoleDemo/ConsoleDemo.csproj | 2 +- Parser.Tests/Parser.Tests.csproj | 2 +- Parser/Binding/Binder.cs | 31 +- Parser/Emitting/Emitter.cs | 416 ++++++++++++++++++++++++- Parser/Internal/DiagnosticsBag.cs | 5 + Parser/MFunctions/MHelpers.cs | 16 + Parser/Objects/MObject.cs | 5 + Parser/Parser.csproj | 2 +- Repl/Repl.csproj | 2 +- Semantics/Semantics.csproj | 2 +- SyntaxGenerator/SyntaxGenerator.csproj | 2 +- examples/Directory.Build.targets | 4 +- examples/helloworld/hello.m | 2 +- examples/helloworld/helloworld.cmproj | 4 + 14 files changed, 466 insertions(+), 29 deletions(-) create mode 100644 Parser/MFunctions/MHelpers.cs diff --git a/ConsoleDemo/ConsoleDemo.csproj b/ConsoleDemo/ConsoleDemo.csproj index a40909a..13d0592 100644 --- a/ConsoleDemo/ConsoleDemo.csproj +++ b/ConsoleDemo/ConsoleDemo.csproj @@ -1,7 +1,7 @@  Exe - netcoreapp3.0 + net5.0 diff --git a/Parser.Tests/Parser.Tests.csproj b/Parser.Tests/Parser.Tests.csproj index b0bbeec..2e800c8 100644 --- a/Parser.Tests/Parser.Tests.csproj +++ b/Parser.Tests/Parser.Tests.csproj @@ -1,6 +1,6 @@  - netcoreapp3.0 + net5.0 false enable 8.0 diff --git a/Parser/Binding/Binder.cs b/Parser/Binding/Binder.cs index 6848a28..d868fde 100644 --- a/Parser/Binding/Binder.cs +++ b/Parser/Binding/Binder.cs @@ -13,24 +13,33 @@ namespace Parser.Binding { private readonly DiagnosticsBag _diagnostics = new DiagnosticsBag(); - public static BoundProgram BindProgram(SyntaxTree syntaxTree) + private BoundProgram? BindProgramInternal(SyntaxTree syntaxTree) { - var binder = new Binder(); - var boundRoot = binder.BindRoot(syntaxTree.NullRoot); + var boundRoot = BindRoot(syntaxTree.NullRoot); var statements = ((BoundBlockStatement)boundRoot.File.Body).Statements; var functionsBuilder = ImmutableDictionary.CreateBuilder(); var globalStatements = statements.Where(s => s.Kind != BoundNodeKind.FunctionDeclaration).ToArray(); var mainFunction = (FunctionSymbol?)null; var scriptFunction = (FunctionSymbol?)null; + var functions = statements.OfType().ToArray(); if (globalStatements.Length > 0) { - // we have to gather all bound expression statements into a "script" function. - scriptFunction = new FunctionSymbol("%script"); + // we have to gather all bound expression statements into a "Main" function. + scriptFunction = new FunctionSymbol("Main"); + foreach (var f in functions) { + if (f.Name == "Main") + { + _diagnostics.ReportMainIsNotAllowed( + new TextSpan(f.Syntax.Position, f.Syntax.FullWidth)); + return null; + } + } + var body = Block(globalStatements[0].Syntax, globalStatements); var loweredBody = Lowerer.Lower(body); var declaration = new BoundFunctionDeclaration( syntax: globalStatements[0].Syntax, - name: "%script", + name: "Main", inputDescription: ImmutableArray.Empty, outputDescription: ImmutableArray.Empty, body: body); @@ -38,7 +47,7 @@ namespace Parser.Binding functionsBuilder.Add(scriptFunction, loweredFunction); } - var functions = statements.OfType().ToArray(); + var first = true; foreach (var function in functions) { @@ -55,12 +64,18 @@ namespace Parser.Binding } return new BoundProgram( - binder._diagnostics.ToImmutableArray(), + _diagnostics.ToImmutableArray(), mainFunction, scriptFunction, functionsBuilder.ToImmutable()); } + public static BoundProgram? BindProgram(SyntaxTree syntaxTree) + { + var binder = new Binder(); + return binder.BindProgramInternal(syntaxTree); + } + private static LoweredFunction LowerFunction(BoundFunctionDeclaration declaration) { var loweredBody = Lowerer.Lower(declaration.Body); diff --git a/Parser/Emitting/Emitter.cs b/Parser/Emitting/Emitter.cs index 01b68e0..85c1313 100644 --- a/Parser/Emitting/Emitter.cs +++ b/Parser/Emitting/Emitter.cs @@ -1,16 +1,49 @@ using Mono.Cecil; using Mono.Cecil.Cil; +using Mono.Cecil.Rocks; using Parser.Binding; using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.IO; using System.Linq; namespace Parser.Emitting { + public class MethodInterfaceDescription + { + public MethodInterfaceDescription(ImmutableArray inputDescription, ImmutableArray outputDescription, MethodDefinition method) + { + InputDescription = inputDescription; + OutputDescription = outputDescription; + Method = method; + } + + public ImmutableArray InputDescription { get; } + + public ImmutableArray OutputDescription { get; } + + public MethodDefinition Method { get; } + } + public class Emitter { private Dictionary _knownTypes = new Dictionary(); + private Dictionary _functions = new Dictionary(); + private MethodReference? _consoleWriteLineReference; + private MethodReference? _dispReference; + private MethodReference? _stringToMObject; + private MethodReference? _doubleToMObject; + private MethodReference? _getItemFromDictionary; + private MethodReference? _putItemIntoDictionary; + private TypeReference? _mObjectType; + private ArrayType? _mObjectArrayType; + private Dictionary _currentOutputVariables = new Dictionary(); + private VariableDefinition? _currentLocals = null; + private TypeSpecification? _stringMObjectDictionary = null; + private MethodReference? _dictionaryCtorReference = null; + private Dictionary _binaryOperations = new Dictionary(); + private Dictionary _unaryOperations = new Dictionary(); private static TypeReference ResolveAndImportType( string typeName, @@ -114,7 +147,10 @@ namespace Parser.Emitting var builtInTypes = new[] { "System.Object", - "System.Void" + "System.Void", + "System.String", + "System.Collections.Generic.Dictionary`2", + "Parser.Objects.MObject" }; // Resolve built-in types and methods. @@ -126,14 +162,101 @@ namespace Parser.Emitting var objectType = _knownTypes["System.Object"]; var voidType = _knownTypes["System.Void"]; + var stringType = _knownTypes["System.String"]; + var dictionaryType = _knownTypes["System.Collections.Generic.Dictionary`2"]; + _mObjectType = _knownTypes["Parser.Objects.MObject"]; + _mObjectArrayType = _mObjectType.MakeArrayType(); + var stringMObjectDictionary = new GenericInstanceType(dictionaryType); + stringMObjectDictionary.GenericArguments.Add(stringType); + stringMObjectDictionary.GenericArguments.Add(_mObjectType); + _stringMObjectDictionary = stringMObjectDictionary; + _dictionaryCtorReference = ResolveAndImportMethod( + typeName: "System.Collections.Generic.Dictionary`2", + methodName: ".ctor", + parameterTypeNames: Array.Empty(), + assemblies: assemblies, + assemblyDefinition: assemblyDefinition); + _dictionaryCtorReference.DeclaringType = _stringMObjectDictionary; + _getItemFromDictionary = ResolveAndImportMethod( + typeName: "System.Collections.Generic.Dictionary`2", + methodName: "get_Item", + parameterTypeNames: new[] { "TKey" }, + assemblies: assemblies, + assemblyDefinition: assemblyDefinition); + _getItemFromDictionary.DeclaringType = _stringMObjectDictionary; + _putItemIntoDictionary = ResolveAndImportMethod( + typeName: "System.Collections.Generic.Dictionary`2", + methodName: "set_Item", + parameterTypeNames: new[] { "TKey", "TValue" }, + assemblies: assemblies, + assemblyDefinition: assemblyDefinition); + _putItemIntoDictionary.DeclaringType = _stringMObjectDictionary; - var consoleWriteLineReference = ResolveAndImportMethod( + _consoleWriteLineReference = ResolveAndImportMethod( typeName: "System.Console", methodName: "WriteLine", parameterTypeNames: new[] { "System.Object" }, assemblies: assemblies, assemblyDefinition: assemblyDefinition); + _dispReference = ResolveAndImportMethod( + typeName: "Parser.MFunctions.MHelpers", + methodName: "Disp", + parameterTypeNames: new[] { "Parser.Objects.MObject" }, + assemblies: assemblies, + assemblyDefinition: assemblyDefinition); + + _stringToMObject = ResolveAndImportMethod( + typeName: "Parser.Objects.MObject", + methodName: "CreateCharArray", + parameterTypeNames: new[] { "System.String" }, + assemblies: assemblies, + assemblyDefinition: assemblyDefinition); + + _doubleToMObject = ResolveAndImportMethod( + typeName: "Parser.Objects.MObject", + methodName: "CreateDoubleNumber", + parameterTypeNames: new[] { "System.Double" }, + assemblies: assemblies, + assemblyDefinition: assemblyDefinition); + + var binaryOperationNames = new Dictionary + { + [BoundBinaryOperatorKind.Plus] = "Plus", + [BoundBinaryOperatorKind.Minus] = "Minus", + [BoundBinaryOperatorKind.Star] = "Star", + [BoundBinaryOperatorKind.Slash] = "Slash", + [BoundBinaryOperatorKind.Greater] = "Greater", + [BoundBinaryOperatorKind.GreaterOrEquals] = "GreaterOrEquals", + [BoundBinaryOperatorKind.Less] = "Less", + [BoundBinaryOperatorKind.LessOrEquals] = "LessOrEquals", + }; + + var unaryOperationNames = new Dictionary + { + [BoundUnaryOperatorKind.Minus] = "Minus", + }; + + foreach (var (op, opName) in binaryOperationNames) + { + _binaryOperations[op] = ResolveAndImportMethod( + typeName: "Parser.MFunctions.MOperations", + methodName: opName, + parameterTypeNames: new[] { "Parser.Objects.MObject", "Parser.Objects.MObject" }, + assemblies: assemblies, + assemblyDefinition: assemblyDefinition); + } + + foreach (var (op, opName) in unaryOperationNames) + { + _unaryOperations[op] = ResolveAndImportMethod( + typeName: "Parser.MFunctions.MOperations", + methodName: opName, + parameterTypeNames: new[] { "Parser.Objects.MObject" }, + assemblies: assemblies, + assemblyDefinition: assemblyDefinition); + } + // Create type. var typeDefinition = new TypeDefinition( @namespace: "", @@ -142,20 +265,287 @@ namespace Parser.Emitting baseType: objectType); assemblyDefinition.MainModule.Types.Add(typeDefinition); - // Create method. - var methodDefinition = new MethodDefinition( - name: "Main", - attributes: MethodAttributes.Static | MethodAttributes.Private, - returnType: voidType); - var ilProcessor = methodDefinition.Body.GetILProcessor(); - ilProcessor.Emit(OpCodes.Ldstr, "Hello world!"); - ilProcessor.Emit(OpCodes.Call, consoleWriteLineReference); - ilProcessor.Emit(OpCodes.Ret); + // Generate method definitions for all functions first. + foreach (var (name, body) in program.Functions) + { + var methodDefinition = new MethodDefinition( + name: name.Name, + attributes: MethodAttributes.Static | MethodAttributes.Private, + returnType: body.OutputDescription.Length == 0 ? voidType : _mObjectArrayType); + if (body.InputDescription.Length > 0) + { + foreach (var inputDescription in body.InputDescription) + { + var parameter = new ParameterDefinition( + name: inputDescription.Name, + attributes: ParameterAttributes.None, + parameterType: _mObjectType); + methodDefinition.Parameters.Add(parameter); + } + } - typeDefinition.Methods.Add(methodDefinition); - assemblyDefinition.EntryPoint = methodDefinition; + _functions[name.Name] = new MethodInterfaceDescription( + inputDescription: body.InputDescription, + outputDescription: body.OutputDescription, + method: methodDefinition); + } + + // Emit functions. + foreach (var (name, body) in program.Functions) + { + var methodDefinition = _functions[name.Name]; + EmitFunction(body, methodDefinition.Method); + typeDefinition.Methods.Add(methodDefinition.Method); + } + + // Set entry point. + if (program.ScriptFunction is { } scriptFunction) + { + assemblyDefinition.EntryPoint = _functions[scriptFunction.Name].Method; + } + else if (program.MainFunction is { } mainFunction) + { + assemblyDefinition.EntryPoint = _functions[mainFunction.Name].Method; + } assemblyDefinition.Write(outputFileName); } + + private void EmitFunction(LoweredFunction function, MethodDefinition methodDefinition) + { + var ilProcessor = methodDefinition.Body.GetILProcessor(); + + // Local #0 is the dictionary with actual local variables. + _currentLocals = new VariableDefinition(_stringMObjectDictionary); + ilProcessor.Body.Variables.Add(_currentLocals); + ilProcessor.Emit(OpCodes.Newobj, _dictionaryCtorReference); + ilProcessor.Emit(OpCodes.Stloc_0); + var counter = 0; + foreach (var inputDescription in function.InputDescription) + { + var name = inputDescription.Name; + ilProcessor.Emit(OpCodes.Ldloc_0); + ilProcessor.Emit(OpCodes.Ldstr, name); + ilProcessor.Emit(OpCodes.Ldarg, methodDefinition.Parameters[counter]); + ilProcessor.Emit(OpCodes.Callvirt, _putItemIntoDictionary); + counter++; + } + + // The following locals are "output variables". + _currentOutputVariables.Clear(); + if (function.OutputDescription.Length > 0) + { + foreach (var outputDescription in function.OutputDescription) + { + var outputVariable = new VariableDefinition(_mObjectArrayType); + ilProcessor.Body.Variables.Add(outputVariable); + _currentOutputVariables.Add(outputDescription.Name, outputVariable); + } + } + + EmitBlockStatement(function.Body, methodDefinition); + + // Copy output variables to the output array. + if (function.OutputDescription.Length > 0) + { + ilProcessor.Emit(OpCodes.Ldc_I4, function.OutputDescription.Length); + ilProcessor.Emit(OpCodes.Newarr, _mObjectType); + for (var i = 0; i < function.OutputDescription.Length; i++) + { + ilProcessor.Emit(OpCodes.Dup); + ilProcessor.Emit(OpCodes.Ldc_I4, i); + ilProcessor.Emit(OpCodes.Ldloc, i + 1); + ilProcessor.Emit(OpCodes.Stelem_Ref); + } + } + + ilProcessor.Emit(OpCodes.Ret); + } + + private void EmitBlockStatement(BoundBlockStatement block, MethodDefinition methodDefinition) + { + var index = 0; + while (index < block.Statements.Length) + { + var statement = block.Statements[index]; + switch (statement.Kind) + { + case BoundNodeKind.GotoStatement: + throw new NotImplementedException("Gotos are not supported."); + case BoundNodeKind.ConditionalGotoStatement: + throw new NotImplementedException("Conditional gotos are not supported."); + case BoundNodeKind.LabelStatement: + throw new NotImplementedException("Labels are not supported."); + default: + EmitStatement(statement, methodDefinition); + index++; + break; + } + } + + } + + private void EmitStatement(BoundStatement node, MethodDefinition methodDefinition) + { + switch (node.Kind) + { + case BoundNodeKind.EmptyStatement: + break; + case BoundNodeKind.ExpressionStatement: + EmitExpressionStatement((BoundExpressionStatement)node, methodDefinition); + break; + default: + throw new NotImplementedException($"Invalid statement kind '{node.Kind}'."); + }; + } + + private void EmitExpressionStatement(BoundExpressionStatement node, MethodDefinition methodDefinition) + { + var ilProcessor = methodDefinition.Body.GetILProcessor(); + EmitExpression(node.Expression, methodDefinition); + if (node.DiscardResult) + { + ilProcessor.Emit(OpCodes.Pop); + } + else + { + ilProcessor.Emit(OpCodes.Call, _dispReference); + } + } + + private void EmitExpression(BoundExpression node, MethodDefinition methodDefinition) + { + switch (node.Kind) { + case BoundNodeKind.AssignmentExpression: + EmitAssignmentExpression((BoundAssignmentExpression)node, methodDefinition); + break; + case BoundNodeKind.BinaryOperationExpression: + EmitBinaryOperationExpression((BoundBinaryOperationExpression)node, methodDefinition); + break; + case BoundNodeKind.FunctionCallExpression: + EmitFunctionCallExpression((BoundFunctionCallExpression)node, methodDefinition); + break; + case BoundNodeKind.IdentifierNameExpression: + EmitIdentifierNameExpression((BoundIdentifierNameExpression)node, methodDefinition); + break; + case BoundNodeKind.NumberLiteralExpression: + EmitNumberLiteralExpression((BoundNumberLiteralExpression)node, methodDefinition); + break; + case BoundNodeKind.StringLiteralExpression: + EmitStringLiteralExpression((BoundStringLiteralExpression)node, methodDefinition); + break; + default: + throw new NotImplementedException($"Invalid node kind '{node.Kind}'."); + } + } + + private void EmitBinaryOperationExpression(BoundBinaryOperationExpression node, MethodDefinition methodDefinition) + { + var ilProcessor = methodDefinition.Body.GetILProcessor(); + var method = _binaryOperations[node.Op.Kind]; + EmitExpression(node.Left, methodDefinition); + EmitExpression(node.Right, methodDefinition); + ilProcessor.Emit(OpCodes.Call, method); + } + + private void EmitAssignmentExpression(BoundAssignmentExpression node, MethodDefinition methodDefinition) + { + var ilProcessor = methodDefinition.Body.GetILProcessor(); + if (node.Left.Kind != BoundNodeKind.IdentifierNameExpression) + { + throw new Exception("Assignment to complex lvalues is not supported."); + } + var left = ((BoundIdentifierNameExpression)node.Left); + ilProcessor.Emit(OpCodes.Ldloc_0); + ilProcessor.Emit(OpCodes.Ldstr, left.Name); + EmitExpression(node.Right, methodDefinition); + ilProcessor.Emit(OpCodes.Callvirt, _putItemIntoDictionary); + ilProcessor.Emit(OpCodes.Ldloc_0); + ilProcessor.Emit(OpCodes.Ldstr, left.Name); + ilProcessor.Emit(OpCodes.Callvirt, _getItemFromDictionary); + } + + private void EmitIdentifierNameExpression(BoundIdentifierNameExpression node, MethodDefinition methodDefinition) + { + var ilProcessor = methodDefinition.Body.GetILProcessor(); + ilProcessor.Emit(OpCodes.Ldloc_0); + ilProcessor.Emit(OpCodes.Ldstr, node.Name); + ilProcessor.Emit(OpCodes.Callvirt, _getItemFromDictionary); + } + + private void EmitNumberLiteralExpression(BoundNumberLiteralExpression node, MethodDefinition methodDefinition) + { + var ilProcessor = methodDefinition.Body.GetILProcessor(); + ilProcessor.Emit(OpCodes.Ldc_R8, node.Value); + ilProcessor.Emit(OpCodes.Call, _doubleToMObject); + } + + private void EmitStringLiteralExpression(BoundStringLiteralExpression node, MethodDefinition methodDefinition) + { + var ilProcessor = methodDefinition.Body.GetILProcessor(); + ilProcessor.Emit(OpCodes.Ldstr, node.Value); + ilProcessor.Emit(OpCodes.Call, _stringToMObject); + } + + private void EmitFunctionCallExpression(BoundFunctionCallExpression node, MethodDefinition methodDefinition) + { + var ilProcessor = methodDefinition.Body.GetILProcessor(); + if (node.Name.Kind == BoundNodeKind.IdentifierNameExpression + && ((BoundIdentifierNameExpression)node.Name).Name == "disp") + { + EmitExpression(node.Arguments[0], methodDefinition); + ilProcessor.Emit(OpCodes.Call, _dispReference); + ilProcessor.Emit(OpCodes.Ldnull); + } + else + { + var function = ResolveFunction(node.Name); + for (var i = 0; i < function.InputDescription.Length; i++) + { + if (i < node.Arguments.Length) + { + EmitExpression(node.Arguments[i], methodDefinition); + } + else + { + ilProcessor.Emit(OpCodes.Ldnull); + } + } + + ilProcessor.Emit(OpCodes.Call, function.Method); + if (function.OutputDescription.Length == 0) + { + ilProcessor.Emit(OpCodes.Ldnull); + } + else if (function.OutputDescription.Length == 1) + { + ilProcessor.Emit(OpCodes.Ldc_I4_0); + ilProcessor.Emit(OpCodes.Ldelem_Ref); + } + else + { + throw new NotImplementedException("Functions with multiple output are not supported."); + } + } + } + + private MethodInterfaceDescription ResolveFunction(BoundExpression expression) + { + if (expression.Kind == BoundNodeKind.IdentifierNameExpression) + { + var name = ((BoundIdentifierNameExpression)expression).Name; + if (_functions.TryGetValue(name, out var result)) + { + return result; + } else + { + throw new Exception($"Function '{name}' not found."); + } + } + else + { + throw new NotImplementedException($"Dynamic functions calling not supported. Failed to resolve function call expression with kind '{expression.Kind}'."); + } + } } } diff --git a/Parser/Internal/DiagnosticsBag.cs b/Parser/Internal/DiagnosticsBag.cs index a906357..e81e7d0 100644 --- a/Parser/Internal/DiagnosticsBag.cs +++ b/Parser/Internal/DiagnosticsBag.cs @@ -48,6 +48,11 @@ namespace Parser.Internal Report(span, $"Unexpected character '{c}' while parsing a number."); } + internal void ReportMainIsNotAllowed(TextSpan span) + { + Report(span, $"Function name 'Main' is not allowed in scripts."); + } + internal void ReportUnexpectedEOLWhileParsingString(TextSpan span) { Report(span, "Unexpected end of line while parsing a string literal."); diff --git a/Parser/MFunctions/MHelpers.cs b/Parser/MFunctions/MHelpers.cs new file mode 100644 index 0000000..6cf25f8 --- /dev/null +++ b/Parser/MFunctions/MHelpers.cs @@ -0,0 +1,16 @@ +using Parser.Objects; +using System; + +namespace Parser.MFunctions +{ + public static class MHelpers + { + public static void Disp(MObject? obj) + { + if (obj is not null) + { + Console.WriteLine(obj); + } + } + } +} diff --git a/Parser/Objects/MObject.cs b/Parser/Objects/MObject.cs index 702e985..bebc8dc 100644 --- a/Parser/Objects/MObject.cs +++ b/Parser/Objects/MObject.cs @@ -12,6 +12,11 @@ return MCharArray.Create(chars); } + public static MCharArray CreateCharArray(string s) + { + return MCharArray.Create(s.ToCharArray()); + } + public static MLogical CreateLogical(bool value) { return MLogical.Create(value); diff --git a/Parser/Parser.csproj b/Parser/Parser.csproj index d9ed05a..5966aed 100644 --- a/Parser/Parser.csproj +++ b/Parser/Parser.csproj @@ -1,6 +1,6 @@  - netcoreapp3.0 + net5.0 8.0 enable preview diff --git a/Repl/Repl.csproj b/Repl/Repl.csproj index d84edae..1171eae 100644 --- a/Repl/Repl.csproj +++ b/Repl/Repl.csproj @@ -1,7 +1,7 @@  - netcoreapp3.0 + net5.0 diff --git a/Semantics/Semantics.csproj b/Semantics/Semantics.csproj index 6999070..bfb1fbf 100644 --- a/Semantics/Semantics.csproj +++ b/Semantics/Semantics.csproj @@ -1,6 +1,6 @@  - netcoreapp3.0 + net5.0 diff --git a/SyntaxGenerator/SyntaxGenerator.csproj b/SyntaxGenerator/SyntaxGenerator.csproj index ab61595..5e8f81d 100644 --- a/SyntaxGenerator/SyntaxGenerator.csproj +++ b/SyntaxGenerator/SyntaxGenerator.csproj @@ -1,7 +1,7 @@  Exe - netcoreapp3.0 + net5.0 diff --git a/examples/Directory.Build.targets b/examples/Directory.Build.targets index e75f061..f19f1bc 100644 --- a/examples/Directory.Build.targets +++ b/examples/Directory.Build.targets @@ -3,10 +3,12 @@ - + + net5.0 + + + + From 3f494cef6dfdd4d08622cf49bfd97b9d666d4cbd Mon Sep 17 00:00:00 2001 From: Alexander Luzgarev Date: Sun, 19 Jul 2020 09:02:45 +0200 Subject: [PATCH 04/14] Implement branching --- Parser/Emitting/Emitter.cs | 140 +++++++++++------- Parser/Emitting/MethodInterfaceDescription.cs | 22 +++ Parser/MFunctions/MHelpers.cs | 11 ++ examples/helloworld/hello.m | 12 +- 4 files changed, 129 insertions(+), 56 deletions(-) create mode 100644 Parser/Emitting/MethodInterfaceDescription.cs diff --git a/Parser/Emitting/Emitter.cs b/Parser/Emitting/Emitter.cs index 85c1313..27be020 100644 --- a/Parser/Emitting/Emitter.cs +++ b/Parser/Emitting/Emitter.cs @@ -4,34 +4,18 @@ using Mono.Cecil.Rocks; using Parser.Binding; using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.IO; using System.Linq; namespace Parser.Emitting { - public class MethodInterfaceDescription - { - public MethodInterfaceDescription(ImmutableArray inputDescription, ImmutableArray outputDescription, MethodDefinition method) - { - InputDescription = inputDescription; - OutputDescription = outputDescription; - Method = method; - } - - public ImmutableArray InputDescription { get; } - - public ImmutableArray OutputDescription { get; } - - public MethodDefinition Method { get; } - } - public class Emitter { private Dictionary _knownTypes = new Dictionary(); private Dictionary _functions = new Dictionary(); private MethodReference? _consoleWriteLineReference; private MethodReference? _dispReference; + private MethodReference? _mObjectToBool; private MethodReference? _stringToMObject; private MethodReference? _doubleToMObject; private MethodReference? _getItemFromDictionary; @@ -44,6 +28,8 @@ namespace Parser.Emitting private MethodReference? _dictionaryCtorReference = null; private Dictionary _binaryOperations = new Dictionary(); private Dictionary _unaryOperations = new Dictionary(); + private Dictionary _labels = new Dictionary(); + private Dictionary _forwardLabelsToFix = new Dictionary(); private static TypeReference ResolveAndImportType( string typeName, @@ -206,6 +192,13 @@ namespace Parser.Emitting assemblies: assemblies, assemblyDefinition: assemblyDefinition); + _mObjectToBool = ResolveAndImportMethod( + typeName: "Parser.MFunctions.MHelpers", + methodName: "ToBool", + parameterTypeNames: new[] { "Parser.Objects.MObject" }, + assemblies: assemblies, + assemblyDefinition: assemblyDefinition); + _stringToMObject = ResolveAndImportMethod( typeName: "Parser.Objects.MObject", methodName: "CreateCharArray", @@ -315,6 +308,9 @@ namespace Parser.Emitting { var ilProcessor = methodDefinition.Body.GetILProcessor(); + _labels.Clear(); + _forwardLabelsToFix.Clear(); + // Local #0 is the dictionary with actual local variables. _currentLocals = new VariableDefinition(_stringMObjectDictionary); ilProcessor.Body.Variables.Add(_currentLocals); @@ -331,6 +327,7 @@ namespace Parser.Emitting counter++; } + // The following locals are "output variables". _currentOutputVariables.Clear(); if (function.OutputDescription.Length > 0) @@ -359,50 +356,89 @@ namespace Parser.Emitting } } + foreach (var (index, target) in _forwardLabelsToFix) + { + var targetIndex = _labels[target]; + ilProcessor.Body.Instructions[index].Operand = ilProcessor.Body.Instructions[targetIndex]; + } + ilProcessor.Emit(OpCodes.Ret); } private void EmitBlockStatement(BoundBlockStatement block, MethodDefinition methodDefinition) { - var index = 0; - while (index < block.Statements.Length) + var ilProcessor = methodDefinition.Body.GetILProcessor(); + foreach (var statement in block.Statements) { - var statement = block.Statements[index]; switch (statement.Kind) { case BoundNodeKind.GotoStatement: - throw new NotImplementedException("Gotos are not supported."); + EmitGoto((BoundGotoStatement)statement, ilProcessor); + break; case BoundNodeKind.ConditionalGotoStatement: - throw new NotImplementedException("Conditional gotos are not supported."); + EmitConditionalGoto((BoundConditionalGotoStatement)statement, ilProcessor); + break; case BoundNodeKind.LabelStatement: - throw new NotImplementedException("Labels are not supported."); + EmitLabelStatement((BoundLabelStatement)statement, ilProcessor); + break; default: - EmitStatement(statement, methodDefinition); - index++; + EmitStatement(statement, ilProcessor); break; } } - } - private void EmitStatement(BoundStatement node, MethodDefinition methodDefinition) + private void EmitLabelStatement(BoundLabelStatement node, ILProcessor ilProcessor) + { + _labels[node.Label] = ilProcessor.Body.Instructions.Count; + } + + private void EmitGoto(BoundGotoStatement node, ILProcessor ilProcessor) + { + if (_labels.TryGetValue(node.Label, out var target)) + { + ilProcessor.Emit(OpCodes.Br, ilProcessor.Body.Instructions[target]); + } + else + { + _forwardLabelsToFix.Add(ilProcessor.Body.Instructions.Count, node.Label); + ilProcessor.Emit(OpCodes.Br, Instruction.Create(OpCodes.Nop)); + } + } + + private void EmitConditionalGoto(BoundConditionalGotoStatement node, ILProcessor ilProcessor) + { + EmitExpression(node.Condition, ilProcessor); + ilProcessor.Emit(OpCodes.Call, _mObjectToBool); + var instruction = node.GotoIfTrue ? OpCodes.Brtrue : OpCodes.Brfalse; + if (_labels.TryGetValue(node.Label, out var target)) + { + ilProcessor.Emit(instruction, ilProcessor.Body.Instructions[target]); + } + else + { + _forwardLabelsToFix.Add(ilProcessor.Body.Instructions.Count, node.Label); + ilProcessor.Emit(instruction, Instruction.Create(OpCodes.Nop)); + } + } + + private void EmitStatement(BoundStatement node, ILProcessor ilProcessor) { switch (node.Kind) { case BoundNodeKind.EmptyStatement: break; case BoundNodeKind.ExpressionStatement: - EmitExpressionStatement((BoundExpressionStatement)node, methodDefinition); + EmitExpressionStatement((BoundExpressionStatement)node, ilProcessor); break; default: throw new NotImplementedException($"Invalid statement kind '{node.Kind}'."); }; } - private void EmitExpressionStatement(BoundExpressionStatement node, MethodDefinition methodDefinition) + private void EmitExpressionStatement(BoundExpressionStatement node, ILProcessor ilProcessor) { - var ilProcessor = methodDefinition.Body.GetILProcessor(); - EmitExpression(node.Expression, methodDefinition); + EmitExpression(node.Expression, ilProcessor); if (node.DiscardResult) { ilProcessor.Emit(OpCodes.Pop); @@ -413,44 +449,42 @@ namespace Parser.Emitting } } - private void EmitExpression(BoundExpression node, MethodDefinition methodDefinition) + private void EmitExpression(BoundExpression node, ILProcessor ilProcessor) { switch (node.Kind) { case BoundNodeKind.AssignmentExpression: - EmitAssignmentExpression((BoundAssignmentExpression)node, methodDefinition); + EmitAssignmentExpression((BoundAssignmentExpression)node, ilProcessor); break; case BoundNodeKind.BinaryOperationExpression: - EmitBinaryOperationExpression((BoundBinaryOperationExpression)node, methodDefinition); + EmitBinaryOperationExpression((BoundBinaryOperationExpression)node, ilProcessor); break; case BoundNodeKind.FunctionCallExpression: - EmitFunctionCallExpression((BoundFunctionCallExpression)node, methodDefinition); + EmitFunctionCallExpression((BoundFunctionCallExpression)node, ilProcessor); break; case BoundNodeKind.IdentifierNameExpression: - EmitIdentifierNameExpression((BoundIdentifierNameExpression)node, methodDefinition); + EmitIdentifierNameExpression((BoundIdentifierNameExpression)node, ilProcessor); break; case BoundNodeKind.NumberLiteralExpression: - EmitNumberLiteralExpression((BoundNumberLiteralExpression)node, methodDefinition); + EmitNumberLiteralExpression((BoundNumberLiteralExpression)node, ilProcessor); break; case BoundNodeKind.StringLiteralExpression: - EmitStringLiteralExpression((BoundStringLiteralExpression)node, methodDefinition); + EmitStringLiteralExpression((BoundStringLiteralExpression)node, ilProcessor); break; default: throw new NotImplementedException($"Invalid node kind '{node.Kind}'."); } } - private void EmitBinaryOperationExpression(BoundBinaryOperationExpression node, MethodDefinition methodDefinition) + private void EmitBinaryOperationExpression(BoundBinaryOperationExpression node, ILProcessor ilProcessor) { - var ilProcessor = methodDefinition.Body.GetILProcessor(); var method = _binaryOperations[node.Op.Kind]; - EmitExpression(node.Left, methodDefinition); - EmitExpression(node.Right, methodDefinition); + EmitExpression(node.Left, ilProcessor); + EmitExpression(node.Right, ilProcessor); ilProcessor.Emit(OpCodes.Call, method); } - private void EmitAssignmentExpression(BoundAssignmentExpression node, MethodDefinition methodDefinition) + private void EmitAssignmentExpression(BoundAssignmentExpression node, ILProcessor ilProcessor) { - var ilProcessor = methodDefinition.Body.GetILProcessor(); if (node.Left.Kind != BoundNodeKind.IdentifierNameExpression) { throw new Exception("Assignment to complex lvalues is not supported."); @@ -458,42 +492,38 @@ namespace Parser.Emitting var left = ((BoundIdentifierNameExpression)node.Left); ilProcessor.Emit(OpCodes.Ldloc_0); ilProcessor.Emit(OpCodes.Ldstr, left.Name); - EmitExpression(node.Right, methodDefinition); + EmitExpression(node.Right, ilProcessor); ilProcessor.Emit(OpCodes.Callvirt, _putItemIntoDictionary); ilProcessor.Emit(OpCodes.Ldloc_0); ilProcessor.Emit(OpCodes.Ldstr, left.Name); ilProcessor.Emit(OpCodes.Callvirt, _getItemFromDictionary); } - private void EmitIdentifierNameExpression(BoundIdentifierNameExpression node, MethodDefinition methodDefinition) + private void EmitIdentifierNameExpression(BoundIdentifierNameExpression node, ILProcessor ilProcessor) { - var ilProcessor = methodDefinition.Body.GetILProcessor(); ilProcessor.Emit(OpCodes.Ldloc_0); ilProcessor.Emit(OpCodes.Ldstr, node.Name); ilProcessor.Emit(OpCodes.Callvirt, _getItemFromDictionary); } - private void EmitNumberLiteralExpression(BoundNumberLiteralExpression node, MethodDefinition methodDefinition) + private void EmitNumberLiteralExpression(BoundNumberLiteralExpression node, ILProcessor ilProcessor) { - var ilProcessor = methodDefinition.Body.GetILProcessor(); ilProcessor.Emit(OpCodes.Ldc_R8, node.Value); ilProcessor.Emit(OpCodes.Call, _doubleToMObject); } - private void EmitStringLiteralExpression(BoundStringLiteralExpression node, MethodDefinition methodDefinition) + private void EmitStringLiteralExpression(BoundStringLiteralExpression node, ILProcessor ilProcessor) { - var ilProcessor = methodDefinition.Body.GetILProcessor(); ilProcessor.Emit(OpCodes.Ldstr, node.Value); ilProcessor.Emit(OpCodes.Call, _stringToMObject); } - private void EmitFunctionCallExpression(BoundFunctionCallExpression node, MethodDefinition methodDefinition) + private void EmitFunctionCallExpression(BoundFunctionCallExpression node, ILProcessor ilProcessor) { - var ilProcessor = methodDefinition.Body.GetILProcessor(); if (node.Name.Kind == BoundNodeKind.IdentifierNameExpression && ((BoundIdentifierNameExpression)node.Name).Name == "disp") { - EmitExpression(node.Arguments[0], methodDefinition); + EmitExpression(node.Arguments[0], ilProcessor); ilProcessor.Emit(OpCodes.Call, _dispReference); ilProcessor.Emit(OpCodes.Ldnull); } @@ -504,7 +534,7 @@ namespace Parser.Emitting { if (i < node.Arguments.Length) { - EmitExpression(node.Arguments[i], methodDefinition); + EmitExpression(node.Arguments[i], ilProcessor); } else { diff --git a/Parser/Emitting/MethodInterfaceDescription.cs b/Parser/Emitting/MethodInterfaceDescription.cs new file mode 100644 index 0000000..2f34279 --- /dev/null +++ b/Parser/Emitting/MethodInterfaceDescription.cs @@ -0,0 +1,22 @@ +using Mono.Cecil; +using Parser.Binding; +using System.Collections.Immutable; + +namespace Parser.Emitting +{ + public class MethodInterfaceDescription + { + public MethodInterfaceDescription(ImmutableArray inputDescription, ImmutableArray outputDescription, MethodDefinition method) + { + InputDescription = inputDescription; + OutputDescription = outputDescription; + Method = method; + } + + public ImmutableArray InputDescription { get; } + + public ImmutableArray OutputDescription { get; } + + public MethodDefinition Method { get; } + } +} diff --git a/Parser/MFunctions/MHelpers.cs b/Parser/MFunctions/MHelpers.cs index 6cf25f8..fc975f8 100644 --- a/Parser/MFunctions/MHelpers.cs +++ b/Parser/MFunctions/MHelpers.cs @@ -12,5 +12,16 @@ namespace Parser.MFunctions Console.WriteLine(obj); } } + + public static bool ToBool(MObject operand) + { + return operand switch + { + MDoubleNumber { Value: var value } => value != 0.0, + MLogical { Value: var value } => value, + MCharArray { Chars: var value } => value.Length > 0, + _ => throw new System.Exception($"Unknown MObject type {operand.GetType()}"), + }; + } } } diff --git a/examples/helloworld/hello.m b/examples/helloworld/hello.m index c0e46b0..d13622c 100644 --- a/examples/helloworld/hello.m +++ b/examples/helloworld/hello.m @@ -1,11 +1,21 @@ x = 2; f(x); +x = 5; +f(x); +x = 3; f(x); function f(x) disp('X was'); disp(x); + if x > 3 + disp('greater than 3!'); + elseif x < 3 + disp('less than 3!'); + else + disp('exactly 3!'); + end x = x + 1; - disp('X is'); + disp('X + 1 is'); disp(x); end \ No newline at end of file From 3bc8ef0d7d2c44d12e89e62ea857c4a1d809b03b Mon Sep 17 00:00:00 2001 From: Alexander Luzgarev Date: Mon, 20 Jul 2020 10:38:32 +0200 Subject: [PATCH 05/14] More types --- Parser/Binding/Binder.cs | 69 ++++++- Parser/Binding/BoundBinaryOperator.cs | 173 ++++++++++++++--- Parser/Binding/BoundNodeFactory.cs | 63 ++++-- Parser/Binding/BoundNodeKind.cs | 7 +- Parser/Binding/BoundRoot.cs | 155 ++++++++++++++- Parser/Binding/BoundTreeRewriter.cs | 28 ++- Parser/Binding/BoundUnaryOperator.cs | 28 ++- Parser/Binding/Conversion.cs | 34 ++++ Parser/Binding/TypeSymbol.cs | 26 +++ Parser/Binding/TypedFunctionSymbol.cs | 23 +++ Parser/Binding/TypedParameterSymbol.cs | 14 ++ Parser/Binding/TypedVariableSymbol.cs | 15 ++ Parser/Emitting/Emitter.cs | 254 ++++++++++++++++++++++--- Parser/Evaluator.cs | 4 +- Parser/Internal/DiagnosticsBag.cs | 5 + Parser/Lowering/Lowerer.cs | 86 ++++++++- Parser/MFunctions/MHelpers.cs | 13 ++ Parser/MFunctions/MOperations.cs | 37 ++++ Parser/Objects/MDoubleNumber.cs | 43 +++++ Parser/Objects/MObject.cs | 14 +- examples/helloworld/hello.m | 5 + 21 files changed, 1008 insertions(+), 88 deletions(-) create mode 100644 Parser/Binding/Conversion.cs create mode 100644 Parser/Binding/TypeSymbol.cs create mode 100644 Parser/Binding/TypedFunctionSymbol.cs create mode 100644 Parser/Binding/TypedParameterSymbol.cs create mode 100644 Parser/Binding/TypedVariableSymbol.cs diff --git a/Parser/Binding/Binder.cs b/Parser/Binding/Binder.cs index d868fde..c84550b 100644 --- a/Parser/Binding/Binder.cs +++ b/Parser/Binding/Binder.cs @@ -116,7 +116,7 @@ namespace Parser.Binding TokenKind.ExpressionStatement => BindExpressionStatement((ExpressionStatementSyntaxNode)node), TokenKind.ForStatement => - BindForStatement((ForStatementSyntaxNode)node), + BindForStatement((ForStatementSyntaxNode)node)!, TokenKind.FunctionDeclaration => BindFunctionDeclaration((FunctionDeclarationSyntaxNode)node), TokenKind.IfStatement => @@ -265,9 +265,29 @@ namespace Parser.Binding return new ParameterSymbol(parameter.Text); } - private BoundForStatement BindForStatement(ForStatementSyntaxNode node) + private BoundForStatement? BindForStatement(ForStatementSyntaxNode node) { - throw new NotImplementedException(); + var loopVariable = BindLoopVariable(node.Assignment.Lhs); + if (loopVariable is null) + { + return null; + } + + var loopedExpression = BindExpression(node.Assignment.Rhs); + var body = BindStatement(node.Body); + return ForStatement(node, loopVariable, loopedExpression, body); + } + + private BoundIdentifierNameExpression? BindLoopVariable(ExpressionSyntaxNode node) + { + if (node.Kind != TokenKind.IdentifierNameExpression) + { + _diagnostics.ReportForLoopWithoutVariable( + new TextSpan(node.Position, node.FullWidth)); + return null; + } + + return Identifier(node, ((IdentifierNameExpressionSyntaxNode)node).Name.Text); } private BoundExpressionStatement BindExpressionStatement(ExpressionStatementSyntaxNode node) @@ -342,11 +362,37 @@ namespace Parser.Binding return Assignment(node, left, right); } + private BoundExpression BindConversion(BoundExpression expression, TypeSymbol targetType) + { + var conversion = Conversion.Classify(expression.Type, targetType); + if (!conversion.Exists) + { + return new BoundErrorExpression(expression.Syntax); + } + + if (conversion.IsIdentity) + { + return expression; + } + + return Conversion(expression.Syntax, targetType, expression); + } + private BoundBinaryOperationExpression BindBinaryOperationExpression(BinaryOperationExpressionSyntaxNode node) { var left = BindExpression(node.Lhs); var right = BindExpression(node.Rhs); - return BinaryOperation(node, left, node.Operation.Kind, right); + var op = BoundBinaryOperator.GetOperator(node.Operation.Kind, left.Type, right.Type); + if (op is null) + { + throw new Exception($"Unknown binary operator '{node.Operation.Kind}'."); + } + + return BinaryOperation( + node, + BindConversion(left, op.Left), + op, + BindConversion(right, op.Right)); } private BoundCellArrayElementAccessExpression BindCellArrayElementAccessExpression(CellArrayElementAccessExpressionSyntaxNode node) @@ -416,10 +462,10 @@ namespace Parser.Binding throw new NotImplementedException(); } - private BoundNumberLiteralExpression BindNumberLiteralExpression(NumberLiteralExpressionSyntaxNode node) + private BoundNumberDoubleLiteralExpression BindNumberLiteralExpression(NumberLiteralExpressionSyntaxNode node) { var value = (double)node.Number.Value!; - return NumberLiteral(node, value); + return NumberDoubleLiteral(node, value); } private BoundExpression BindParenthesizedExpression(ParenthesizedExpressionSyntaxNode node) @@ -436,7 +482,16 @@ namespace Parser.Binding private BoundUnaryOperationExpression BindUnaryPrefixOperationExpression(UnaryPrefixOperationExpressionSyntaxNode node) { var operand = BindExpression(node.Operand); - return UnaryOperation(node, node.Operation.Kind, operand); + var op = BoundUnaryOperator.GetOperator(node.Operation.Kind, operand.Type); + if (op is null) + { + throw new Exception($"Unknown binary operator '{node.Operation.Kind}'."); + } + + return UnaryOperation( + node, + op, + BindConversion(operand, op.Result)); } private BoundUnaryOperationExpression BindUnaryPostfixOperationExpression(UnaryPostfixOperationExpressionSyntaxNode node) diff --git a/Parser/Binding/BoundBinaryOperator.cs b/Parser/Binding/BoundBinaryOperator.cs index 73dc880..fc614b3 100644 --- a/Parser/Binding/BoundBinaryOperator.cs +++ b/Parser/Binding/BoundBinaryOperator.cs @@ -4,45 +4,164 @@ namespace Parser.Binding { public class BoundBinaryOperator { - private static BoundBinaryOperator[] _operators = + private static BoundBinaryOperator[] _specificOperators = { - new BoundBinaryOperator(TokenKind.EqualsToken, BoundBinaryOperatorKind.Equals), - new BoundBinaryOperator(TokenKind.PipePipeToken, BoundBinaryOperatorKind.PipePipe), - new BoundBinaryOperator(TokenKind.AmpersandAmpersandToken, BoundBinaryOperatorKind.AmpersandAmpersand), - new BoundBinaryOperator(TokenKind.PipeToken, BoundBinaryOperatorKind.Pipe), - new BoundBinaryOperator(TokenKind.AmpersandToken, BoundBinaryOperatorKind.Ampersand), - new BoundBinaryOperator(TokenKind.LessToken, BoundBinaryOperatorKind.Less), - new BoundBinaryOperator(TokenKind.LessOrEqualsToken, BoundBinaryOperatorKind.LessOrEquals), - new BoundBinaryOperator(TokenKind.GreaterToken, BoundBinaryOperatorKind.Greater), - new BoundBinaryOperator(TokenKind.GreaterOrEqualsToken, BoundBinaryOperatorKind.GreaterOrEquals), - new BoundBinaryOperator(TokenKind.EqualsEqualsToken, BoundBinaryOperatorKind.EqualsEquals), - new BoundBinaryOperator(TokenKind.TildeEqualsToken, BoundBinaryOperatorKind.TildeEquals), - new BoundBinaryOperator(TokenKind.ColonToken, BoundBinaryOperatorKind.Colon), - new BoundBinaryOperator(TokenKind.PlusToken, BoundBinaryOperatorKind.Plus), - new BoundBinaryOperator(TokenKind.MinusToken, BoundBinaryOperatorKind.Minus), - new BoundBinaryOperator(TokenKind.StarToken, BoundBinaryOperatorKind.Star), - new BoundBinaryOperator(TokenKind.DotStarToken, BoundBinaryOperatorKind.DotStar), - new BoundBinaryOperator(TokenKind.SlashToken, BoundBinaryOperatorKind.Slash), - new BoundBinaryOperator(TokenKind.DotSlashToken, BoundBinaryOperatorKind.DotSlash), - new BoundBinaryOperator(TokenKind.BackslashToken, BoundBinaryOperatorKind.Backslash), - new BoundBinaryOperator(TokenKind.DotBackslashToken, BoundBinaryOperatorKind.DotBackslash), - new BoundBinaryOperator(TokenKind.TildeToken, BoundBinaryOperatorKind.Tilde), - new BoundBinaryOperator(TokenKind.CaretToken, BoundBinaryOperatorKind.Caret), - new BoundBinaryOperator(TokenKind.DotCaretToken, BoundBinaryOperatorKind.DotCaret), + new BoundBinaryOperator( + TokenKind.LessToken, + BoundBinaryOperatorKind.Less, + TypeSymbol.Int, + TypeSymbol.Boolean), + new BoundBinaryOperator( + TokenKind.PlusToken, + BoundBinaryOperatorKind.Plus, + TypeSymbol.Int), }; - public BoundBinaryOperator(TokenKind syntaxKind, BoundBinaryOperatorKind kind) + private static BoundBinaryOperator[] _defaultOperators = + { + new BoundBinaryOperator( + TokenKind.EqualsToken, + BoundBinaryOperatorKind.Equals, + TypeSymbol.MObject), + new BoundBinaryOperator( + TokenKind.PipePipeToken, + BoundBinaryOperatorKind.PipePipe, + TypeSymbol.MObject), + new BoundBinaryOperator( + TokenKind.AmpersandAmpersandToken, + BoundBinaryOperatorKind.AmpersandAmpersand, + TypeSymbol.MObject), + new BoundBinaryOperator( + TokenKind.PipeToken, + BoundBinaryOperatorKind.Pipe, + TypeSymbol.MObject), + new BoundBinaryOperator( + TokenKind.AmpersandToken, + BoundBinaryOperatorKind.Ampersand, + TypeSymbol.MObject), + new BoundBinaryOperator( + TokenKind.LessToken, + BoundBinaryOperatorKind.Less, + TypeSymbol.MObject), + new BoundBinaryOperator( + TokenKind.LessOrEqualsToken, + BoundBinaryOperatorKind.LessOrEquals, + TypeSymbol.MObject), + new BoundBinaryOperator( + TokenKind.GreaterToken, + BoundBinaryOperatorKind.Greater, + TypeSymbol.MObject), + new BoundBinaryOperator( + TokenKind.GreaterOrEqualsToken, + BoundBinaryOperatorKind.GreaterOrEquals, + TypeSymbol.MObject), + new BoundBinaryOperator( + TokenKind.EqualsEqualsToken, + BoundBinaryOperatorKind.EqualsEquals, + TypeSymbol.MObject), + new BoundBinaryOperator( + TokenKind.TildeEqualsToken, + BoundBinaryOperatorKind.TildeEquals, + TypeSymbol.MObject), + new BoundBinaryOperator( + TokenKind.ColonToken, + BoundBinaryOperatorKind.Colon, + TypeSymbol.MObject), + new BoundBinaryOperator( + TokenKind.PlusToken, + BoundBinaryOperatorKind.Plus, + TypeSymbol.MObject), + new BoundBinaryOperator( + TokenKind.MinusToken, + BoundBinaryOperatorKind.Minus, + TypeSymbol.MObject), + new BoundBinaryOperator( + TokenKind.StarToken, + BoundBinaryOperatorKind.Star, + TypeSymbol.MObject), + new BoundBinaryOperator( + TokenKind.DotStarToken, + BoundBinaryOperatorKind.DotStar, + TypeSymbol.MObject), + new BoundBinaryOperator( + TokenKind.SlashToken, + BoundBinaryOperatorKind.Slash, + TypeSymbol.MObject), + new BoundBinaryOperator( + TokenKind.DotSlashToken, + BoundBinaryOperatorKind.DotSlash, + TypeSymbol.MObject), + new BoundBinaryOperator( + TokenKind.BackslashToken, + BoundBinaryOperatorKind.Backslash, + TypeSymbol.MObject), + new BoundBinaryOperator( + TokenKind.DotBackslashToken, + BoundBinaryOperatorKind.DotBackslash, + TypeSymbol.MObject), + new BoundBinaryOperator( + TokenKind.TildeToken, + BoundBinaryOperatorKind.Tilde, + TypeSymbol.MObject), + new BoundBinaryOperator( + TokenKind.CaretToken, + BoundBinaryOperatorKind.Caret, + TypeSymbol.MObject), + new BoundBinaryOperator( + TokenKind.DotCaretToken, + BoundBinaryOperatorKind.DotCaret, + TypeSymbol.MObject), + }; + + public BoundBinaryOperator( + TokenKind syntaxKind, + BoundBinaryOperatorKind kind, + TypeSymbol type) { SyntaxKind = syntaxKind; Kind = kind; + Left = type; + Right = type; + Result = type; + } + + public BoundBinaryOperator( + TokenKind syntaxKind, + BoundBinaryOperatorKind kind, + TypeSymbol operand, + TypeSymbol result) + { + SyntaxKind = syntaxKind; + Kind = kind; + Left = operand; + Right = operand; + Result = result; + } + + public BoundBinaryOperator( + TokenKind syntaxKind, + BoundBinaryOperatorKind kind, + TypeSymbol left, + TypeSymbol right, + TypeSymbol result) + { + SyntaxKind = syntaxKind; + Kind = kind; + Left = left; + Right = right; + Result = result; } public TokenKind SyntaxKind { get; } public BoundBinaryOperatorKind Kind { get; } + public TypeSymbol Left { get; } + public TypeSymbol Right { get; } + public TypeSymbol Result { get; } - internal static BoundBinaryOperator? GetOperator(TokenKind kind) + internal static BoundBinaryOperator? GetOperator(TokenKind kind, TypeSymbol left, TypeSymbol right) { - return _operators.FirstOrDefault(op => op.SyntaxKind == kind); + return _specificOperators.FirstOrDefault(op => op.SyntaxKind == kind && op.Left == left && op.Right == right) + ?? _defaultOperators.FirstOrDefault(op => op.SyntaxKind == kind); } } } diff --git a/Parser/Binding/BoundNodeFactory.cs b/Parser/Binding/BoundNodeFactory.cs index 2513301..26bf3ff 100644 --- a/Parser/Binding/BoundNodeFactory.cs +++ b/Parser/Binding/BoundNodeFactory.cs @@ -25,6 +25,11 @@ namespace Parser.Binding return new BoundBlockStatement(syntax, statements); } + public static BoundConversionExpression Conversion(SyntaxNode syntax, TypeSymbol targetType, BoundExpression expression) + { + return new BoundConversionExpression(syntax, targetType, expression); + } + public static BoundExpressionStatement ExpressionStatement( SyntaxNode syntax, BoundExpression expression, @@ -43,6 +48,15 @@ namespace Parser.Binding return new BoundIfStatement(syntax, condition, body, elseifClauses, elseClause); } + public static BoundForStatement ForStatement( + SyntaxNode syntax, + BoundIdentifierNameExpression loopVariable, + BoundExpression loopExpression, + BoundStatement body) + { + return new BoundForStatement(syntax, loopVariable, loopExpression, body); + } + public static BoundLabelStatement LabelStatement( SyntaxNode syntax, BoundLabel label) @@ -61,10 +75,9 @@ namespace Parser.Binding public static BoundBinaryOperationExpression BinaryOperation( SyntaxNode syntax, BoundExpression left, - TokenKind kind, + BoundBinaryOperator op, BoundExpression right) { - var op = BindBinaryOperator(kind); return new BoundBinaryOperationExpression(syntax, left, op, right); } @@ -127,11 +140,18 @@ namespace Parser.Binding return new BoundIdentifierNameExpression(syntax, name); } - public static BoundNumberLiteralExpression NumberLiteral( + public static BoundNumberDoubleLiteralExpression NumberDoubleLiteral( SyntaxNode syntax, double value) { - return new BoundNumberLiteralExpression(syntax, value); + return new BoundNumberDoubleLiteralExpression(syntax, value); + } + + public static BoundNumberIntLiteralExpression NumberIntLiteral( + SyntaxNode syntax, + int value) + { + return new BoundNumberIntLiteralExpression(syntax, value); } public static BoundStringLiteralExpression StringLiteral( @@ -149,25 +169,38 @@ namespace Parser.Binding return new BoundElseifClause(syntax, condition, body); } + public static BoundTypedVariableDeclaration TypedVariableDeclaration( + SyntaxNode syntax, + TypedVariableSymbol variable, + BoundExpression initializer) + { + return new BoundTypedVariableDeclaration(syntax, variable, initializer); + } + + public static BoundTypedVariableExpression TypedVariableExpression( + SyntaxNode syntax, + TypedVariableSymbol variable) + { + return new BoundTypedVariableExpression(syntax, variable); + } + public static BoundUnaryOperationExpression UnaryOperation( SyntaxNode syntax, - TokenKind kind, + BoundUnaryOperator op, BoundExpression operand) { - var op = BindUnaryOperator(kind); return new BoundUnaryOperationExpression(syntax, op, operand); } - private static BoundUnaryOperator BindUnaryOperator(TokenKind kind) + public static BoundExpression TypedFunctionCall( + SyntaxNode syntax, + TypedFunctionSymbol function, + ImmutableArray arguments) { - return BoundUnaryOperator.GetOperator(kind) - ?? throw new Exception($"Unexpected unary operator kind {kind}."); - } - - private static BoundBinaryOperator BindBinaryOperator(TokenKind kind) - { - return BoundBinaryOperator.GetOperator(kind) - ?? throw new Exception($"Unexpected binary operator kind {kind}."); + return new BoundTypedFunctionCallExpression( + syntax, + function, + arguments); } } } diff --git a/Parser/Binding/BoundNodeKind.cs b/Parser/Binding/BoundNodeKind.cs index 4158dcd..19a0963 100644 --- a/Parser/Binding/BoundNodeKind.cs +++ b/Parser/Binding/BoundNodeKind.cs @@ -21,6 +21,7 @@ LabelStatement, SwitchStatement, TryCatchStatement, + TypedVariableDeclaration, WhileStatement, // Expressions @@ -33,8 +34,10 @@ ClassInvokationExpression, CommandExpression, CompoundNameExpression, + ConversionExpression, DoubleQuotedStringLiteralExpression, EmptyExpression, + ErrorExpression, FunctionCallExpression, IdentifierNameExpression, IndirectMemberAccessExpression, @@ -44,11 +47,13 @@ NumberLiteralExpression, ParenthesizedExpression, StringLiteralExpression, + TypedFunctionCallExpression, + TypedVariableExpression, UnaryOperationExpression, UnquotedStringLiteralExpression, // Parts ElseIfClause, - ElseClause + ElseClause, } } diff --git a/Parser/Binding/BoundRoot.cs b/Parser/Binding/BoundRoot.cs index e59f7dc..e4a2a17 100644 --- a/Parser/Binding/BoundRoot.cs +++ b/Parser/Binding/BoundRoot.cs @@ -134,11 +134,24 @@ namespace Parser.Binding public class BoundForStatement : BoundStatement { - public BoundForStatement(SyntaxNode syntax) + public BoundForStatement( + SyntaxNode syntax, + BoundIdentifierNameExpression loopVariable, + BoundExpression loopedExpression, + BoundStatement body) : base(syntax) { + LoopVariable = loopVariable; + LoopedExpression = loopedExpression; + Body = body; } + public BoundIdentifierNameExpression LoopVariable { get; } + + public BoundExpression LoopedExpression { get; } + + public BoundStatement Body { get; } + public override BoundNodeKind Kind => BoundNodeKind.ForStatement; } @@ -257,6 +270,8 @@ namespace Parser.Binding : base(syntax) { } + + public abstract TypeSymbol Type { get; } } public class BoundArrayLiteralExpression : BoundExpression @@ -267,6 +282,8 @@ namespace Parser.Binding } public override BoundNodeKind Kind => BoundNodeKind.ArrayLiteralExpression; + + public override TypeSymbol Type => throw new System.NotImplementedException(); } public class BoundAssignmentExpression : BoundExpression @@ -282,6 +299,8 @@ namespace Parser.Binding public BoundExpression Right { get; } public override BoundNodeKind Kind => BoundNodeKind.AssignmentExpression; + + public override TypeSymbol Type => Right.Type; } public class BoundBinaryOperationExpression : BoundExpression @@ -299,6 +318,8 @@ namespace Parser.Binding public BoundExpression Right { get; } public override BoundNodeKind Kind => BoundNodeKind.BinaryOperationExpression; + + public override TypeSymbol Type => Op.Result; } public class BoundCellArrayElementAccessExpression : BoundExpression @@ -309,6 +330,8 @@ namespace Parser.Binding } public override BoundNodeKind Kind => BoundNodeKind.CellArrayElementAccessExpression; + + public override TypeSymbol Type => throw new System.NotImplementedException(); } public class BoundCellArrayLiteralExpression : BoundExpression @@ -319,6 +342,8 @@ namespace Parser.Binding } public override BoundNodeKind Kind => BoundNodeKind.CellArrayLiteralExpression; + + public override TypeSymbol Type => throw new System.NotImplementedException(); } public class BoundClassInvokationExpression : BoundExpression @@ -329,6 +354,8 @@ namespace Parser.Binding } public override BoundNodeKind Kind => BoundNodeKind.ClassInvokationExpression; + + public override TypeSymbol Type => throw new System.NotImplementedException(); } public class BoundCommandExpression : BoundExpression @@ -339,6 +366,8 @@ namespace Parser.Binding } public override BoundNodeKind Kind => BoundNodeKind.CommandExpression; + + public override TypeSymbol Type => throw new System.NotImplementedException(); } public class BoundCompoundNameExpression : BoundExpression @@ -349,6 +378,8 @@ namespace Parser.Binding } public override BoundNodeKind Kind => BoundNodeKind.CompoundNameExpression; + + public override TypeSymbol Type => throw new System.NotImplementedException(); } public class BoundDoubleQuotedStringLiteralExpression : BoundExpression @@ -359,6 +390,8 @@ namespace Parser.Binding } public override BoundNodeKind Kind => BoundNodeKind.DoubleQuotedStringLiteralExpression; + + public override TypeSymbol Type => throw new System.NotImplementedException(); } public class BoundEmptyExpression : BoundExpression @@ -369,6 +402,20 @@ namespace Parser.Binding } public override BoundNodeKind Kind => BoundNodeKind.EmptyExpression; + + public override TypeSymbol Type => TypeSymbol.Null; + } + + public class BoundErrorExpression : BoundExpression + { + public BoundErrorExpression(SyntaxNode syntax) + : base(syntax) + { + } + + public override BoundNodeKind Kind => BoundNodeKind.ErrorExpression; + + public override TypeSymbol Type => TypeSymbol.Error; } public class BoundFunctionCallExpression : BoundExpression @@ -383,6 +430,27 @@ namespace Parser.Binding public BoundExpression Name { get; } public ImmutableArray Arguments { get; } public override BoundNodeKind Kind => BoundNodeKind.FunctionCallExpression; + + public override TypeSymbol Type => throw new System.NotImplementedException(); + } + + public class BoundTypedFunctionCallExpression : BoundExpression + { + public BoundTypedFunctionCallExpression( + SyntaxNode syntax, + TypedFunctionSymbol function, + ImmutableArray arguments) + : base(syntax) + { + Function = function; + Arguments = arguments; + } + + public TypedFunctionSymbol Function { get; } + public ImmutableArray Arguments { get; } + public override BoundNodeKind Kind => BoundNodeKind.TypedFunctionCallExpression; + + public override TypeSymbol Type => Function.ReturnType; } public class BoundIdentifierNameExpression : BoundExpression @@ -395,6 +463,8 @@ namespace Parser.Binding public string Name { get; } public override BoundNodeKind Kind => BoundNodeKind.IdentifierNameExpression; + + public override TypeSymbol Type => TypeSymbol.MObject; } public class BoundIndirectMemberAccessExpression : BoundExpression @@ -405,6 +475,8 @@ namespace Parser.Binding } public override BoundNodeKind Kind => BoundNodeKind.IndirectMemberAccessExpression; + + public override TypeSymbol Type => throw new System.NotImplementedException(); } public class BoundLambdaExpression : BoundExpression @@ -415,6 +487,8 @@ namespace Parser.Binding } public override BoundNodeKind Kind => BoundNodeKind.LambdaExpression; + + public override TypeSymbol Type => throw new System.NotImplementedException(); } public class BoundMemberAccessExpression : BoundExpression @@ -425,6 +499,8 @@ namespace Parser.Binding } public override BoundNodeKind Kind => BoundNodeKind.MemberAccessExpression; + + public override TypeSymbol Type => throw new System.NotImplementedException(); } public class BoundNamedFunctionHandleExpression : BoundExpression @@ -435,11 +511,13 @@ namespace Parser.Binding } public override BoundNodeKind Kind => BoundNodeKind.NamedFunctionHandleExpression; + + public override TypeSymbol Type => throw new System.NotImplementedException(); } - public class BoundNumberLiteralExpression : BoundExpression + public class BoundNumberDoubleLiteralExpression : BoundExpression { - public BoundNumberLiteralExpression(SyntaxNode syntax, double value) + public BoundNumberDoubleLiteralExpression(SyntaxNode syntax, double value) : base(syntax) { Value = value; @@ -447,6 +525,22 @@ namespace Parser.Binding public double Value { get; } public override BoundNodeKind Kind => BoundNodeKind.NumberLiteralExpression; + + public override TypeSymbol Type => TypeSymbol.Double; + } + + public class BoundNumberIntLiteralExpression : BoundExpression + { + public BoundNumberIntLiteralExpression(SyntaxNode syntax, int value) + : base(syntax) + { + Value = value; + } + + public double Value { get; } + public override BoundNodeKind Kind => BoundNodeKind.NumberLiteralExpression; + + public override TypeSymbol Type => TypeSymbol.Int; } public class BoundStringLiteralExpression : BoundExpression @@ -459,6 +553,39 @@ namespace Parser.Binding public string Value { get; } public override BoundNodeKind Kind => BoundNodeKind.StringLiteralExpression; + + public override TypeSymbol Type => TypeSymbol.String; + } + + public class BoundTypedVariableDeclaration : BoundStatement + { + public BoundTypedVariableDeclaration(SyntaxNode syntax, TypedVariableSymbol variable, BoundExpression initializer) + : base(syntax) + { + Variable = variable; + Initializer = initializer; + } + + public TypedVariableSymbol Variable { get; } + + public BoundExpression Initializer { get; } + + public override BoundNodeKind Kind => BoundNodeKind.TypedVariableDeclaration; + } + + public class BoundTypedVariableExpression : BoundExpression + { + public BoundTypedVariableExpression(SyntaxNode syntax, TypedVariableSymbol variable) + : base(syntax) + { + Variable = variable; + } + + public TypedVariableSymbol Variable { get; } + + public override BoundNodeKind Kind => BoundNodeKind.TypedVariableExpression; + + public override TypeSymbol Type => Variable.Type; } public class BoundUnaryOperationExpression : BoundExpression @@ -474,6 +601,8 @@ namespace Parser.Binding public BoundUnaryOperator Op { get; } public BoundExpression Operand { get; } + + public override TypeSymbol Type => Op.Result; } public class BoundUnquotedStringLiteralExpression : BoundExpression @@ -484,6 +613,26 @@ namespace Parser.Binding } public override BoundNodeKind Kind => BoundNodeKind.UnquotedStringLiteralExpression; + + public override TypeSymbol Type => throw new System.NotImplementedException(); + } + + public class BoundConversionExpression : BoundExpression + { + public BoundConversionExpression(SyntaxNode syntax, TypeSymbol targetType, BoundExpression expression) + : base(syntax) + { + TargetType = targetType; + Expression = expression; + } + + public TypeSymbol TargetType { get; } + + public BoundExpression Expression { get; } + + public override BoundNodeKind Kind => BoundNodeKind.ConversionExpression; + + public override TypeSymbol Type => TargetType; } public class BoundElseifClause : BoundNode diff --git a/Parser/Binding/BoundTreeRewriter.cs b/Parser/Binding/BoundTreeRewriter.cs index 24e997d..42de105 100644 --- a/Parser/Binding/BoundTreeRewriter.cs +++ b/Parser/Binding/BoundTreeRewriter.cs @@ -38,6 +38,8 @@ namespace Parser.Binding RewriteSwitchStatement((BoundSwitchStatement)node), BoundNodeKind.TryCatchStatement => RewriteTryCatchStatement((BoundTryCatchStatement)node), + BoundNodeKind.TypedVariableDeclaration => + RewriteTypedVariableDeclaration((BoundTypedVariableDeclaration)node), BoundNodeKind.WhileStatement => RewriteWhileStatement((BoundWhileStatement)node), _ => @@ -45,6 +47,11 @@ namespace Parser.Binding }; } + public virtual BoundStatement RewriteTypedVariableDeclaration(BoundTypedVariableDeclaration node) + { + return node; + } + public virtual BoundStatement RewriteGotoStatement(BoundGotoStatement node) { return node; @@ -136,7 +143,7 @@ namespace Parser.Binding public virtual BoundStatement RewriteForStatement(BoundForStatement node) { - throw new NotImplementedException(); + return node; } public virtual BoundStatement RewriteExpressionStatement(BoundExpressionStatement node) @@ -220,6 +227,8 @@ namespace Parser.Binding RewriteCommandExpression((BoundCommandExpression)node), BoundNodeKind.CompoundNameExpression => RewriteCompoundNameExpression((BoundCompoundNameExpression)node), + BoundNodeKind.ConversionExpression => + RewriteConversionExpression((BoundConversionExpression)node), BoundNodeKind.DoubleQuotedStringLiteralExpression => RewriteDoubleQuotedStringLiteralExpression((BoundDoubleQuotedStringLiteralExpression)node), BoundNodeKind.EmptyExpression => @@ -237,9 +246,11 @@ namespace Parser.Binding BoundNodeKind.NamedFunctionHandleExpression => RewriteNamedFunctionHandleExpression((BoundNamedFunctionHandleExpression)node), BoundNodeKind.NumberLiteralExpression => - RewriteNumberLiteralExpression((BoundNumberLiteralExpression)node), + RewriteNumberLiteralExpression((BoundNumberDoubleLiteralExpression)node), BoundNodeKind.StringLiteralExpression => RewriteStringLiteralExpression((BoundStringLiteralExpression)node), + BoundNodeKind.TypedVariableExpression => + RewriteTypedVariableExpression((BoundTypedVariableExpression)node), BoundNodeKind.UnaryOperationExpression => RewriteUnaryOperationExpression((BoundUnaryOperationExpression)node), BoundNodeKind.UnquotedStringLiteralExpression => @@ -249,6 +260,17 @@ namespace Parser.Binding }; } + public virtual BoundExpression RewriteConversionExpression(BoundConversionExpression node) + { + var operand = RewriteExpression(node.Expression); + return Conversion(node.Syntax, node.TargetType, operand); + } + + public virtual BoundExpression RewriteTypedVariableExpression(BoundTypedVariableExpression node) + { + return node; + } + public virtual BoundExpression RewriteUnquotedStringLiteralExpression(BoundUnquotedStringLiteralExpression node) { throw new NotImplementedException(); @@ -265,7 +287,7 @@ namespace Parser.Binding return node; } - public virtual BoundExpression RewriteNumberLiteralExpression(BoundNumberLiteralExpression node) + public virtual BoundExpression RewriteNumberLiteralExpression(BoundNumberDoubleLiteralExpression node) { return node; } diff --git a/Parser/Binding/BoundUnaryOperator.cs b/Parser/Binding/BoundUnaryOperator.cs index 7006e1a..b4053c1 100644 --- a/Parser/Binding/BoundUnaryOperator.cs +++ b/Parser/Binding/BoundUnaryOperator.cs @@ -1,26 +1,42 @@ -using System.Linq; +using System; +using System.Linq; namespace Parser.Binding { public class BoundUnaryOperator { - private static BoundUnaryOperator[] _operators = + private static BoundUnaryOperator[] _specificOperators = Array.Empty(); + + private static BoundUnaryOperator[] _defaultOperators = { - new BoundUnaryOperator(TokenKind.MinusToken, BoundUnaryOperatorKind.Minus), + new BoundUnaryOperator(TokenKind.MinusToken, BoundUnaryOperatorKind.Minus, TypeSymbol.MObject), }; - public BoundUnaryOperator(TokenKind syntaxKind, BoundUnaryOperatorKind kind) + public BoundUnaryOperator(TokenKind syntaxKind, BoundUnaryOperatorKind kind, TypeSymbol type) { SyntaxKind = syntaxKind; Kind = kind; + Operand = type; + Result = type; + } + + public BoundUnaryOperator(TokenKind syntaxKind, BoundUnaryOperatorKind kind, TypeSymbol operand, TypeSymbol result) + { + SyntaxKind = syntaxKind; + Kind = kind; + Operand = operand; + Result = result; } public TokenKind SyntaxKind { get; } public BoundUnaryOperatorKind Kind { get; } + public TypeSymbol Operand { get; } + public TypeSymbol Result { get; } - internal static BoundUnaryOperator? GetOperator(TokenKind kind) + internal static BoundUnaryOperator? GetOperator(TokenKind kind, TypeSymbol operand) { - return _operators.FirstOrDefault(op => op.SyntaxKind == kind); + return _specificOperators.FirstOrDefault(op => op.SyntaxKind == kind && op.Operand == operand) + ?? _defaultOperators.FirstOrDefault(op => op.SyntaxKind == kind); } } } diff --git a/Parser/Binding/Conversion.cs b/Parser/Binding/Conversion.cs new file mode 100644 index 0000000..9db7af3 --- /dev/null +++ b/Parser/Binding/Conversion.cs @@ -0,0 +1,34 @@ +namespace Parser.Binding +{ + internal class Conversion + { + public static Conversion None = new Conversion(exists: false, isIdentity: false); + public static Conversion Identity = new Conversion(exists: true, isIdentity: true); + public static Conversion Implicit = new Conversion(exists: true, isIdentity: false); + + private Conversion(bool exists, bool isIdentity) + { + Exists = exists; + IsIdentity = isIdentity; + } + + public bool Exists { get; } + + public bool IsIdentity { get; } + + public static Conversion Classify(TypeSymbol from, TypeSymbol to) + { + if (from == to) + { + return Identity; + } + + if (to == TypeSymbol.MObject) + { + return Implicit; + } + + return None; + } + } +} diff --git a/Parser/Binding/TypeSymbol.cs b/Parser/Binding/TypeSymbol.cs new file mode 100644 index 0000000..420d1e0 --- /dev/null +++ b/Parser/Binding/TypeSymbol.cs @@ -0,0 +1,26 @@ +namespace Parser.Binding +{ + public class TypeSymbol + { + public static readonly TypeSymbol Error = new TypeSymbol("error"); + public static readonly TypeSymbol Null = new TypeSymbol("null"); + public static readonly TypeSymbol Boolean = new TypeSymbol("bool"); + public static readonly TypeSymbol Double = new TypeSymbol("double"); + public static readonly TypeSymbol Int = new TypeSymbol("int"); + public static readonly TypeSymbol String = new TypeSymbol("string"); + public static readonly TypeSymbol MObject = new TypeSymbol("mobject"); + public static readonly TypeSymbol Void = new TypeSymbol("void"); + + private TypeSymbol(string name) + { + Name = name; + } + + public string Name { get; } + + public override string ToString() + { + return Name; + } + } +} diff --git a/Parser/Binding/TypedFunctionSymbol.cs b/Parser/Binding/TypedFunctionSymbol.cs new file mode 100644 index 0000000..e610c86 --- /dev/null +++ b/Parser/Binding/TypedFunctionSymbol.cs @@ -0,0 +1,23 @@ +using System.Collections.Immutable; + +namespace Parser.Binding +{ + public class TypedFunctionSymbol + { + public TypedFunctionSymbol( + string name, + ImmutableArray parameters, + TypeSymbol returnType) + { + Name = name; + Parameters = parameters; + ReturnType = returnType; + } + + public string Name { get; } + + public ImmutableArray Parameters { get; } + + public TypeSymbol ReturnType { get; } + } +} diff --git a/Parser/Binding/TypedParameterSymbol.cs b/Parser/Binding/TypedParameterSymbol.cs new file mode 100644 index 0000000..0d61f9d --- /dev/null +++ b/Parser/Binding/TypedParameterSymbol.cs @@ -0,0 +1,14 @@ +namespace Parser.Binding +{ + public class TypedParameterSymbol + { + public TypedParameterSymbol(string name, TypeSymbol type) + { + Name = name; + Type = type; + } + + public string Name { get; } + public TypeSymbol Type { get; } + } +} \ No newline at end of file diff --git a/Parser/Binding/TypedVariableSymbol.cs b/Parser/Binding/TypedVariableSymbol.cs new file mode 100644 index 0000000..f939032 --- /dev/null +++ b/Parser/Binding/TypedVariableSymbol.cs @@ -0,0 +1,15 @@ +namespace Parser.Binding +{ + public class TypedVariableSymbol + { + public TypedVariableSymbol(string name, TypeSymbol type) + { + Name = name; + Type = type; + } + + public string Name { get; } + + public TypeSymbol Type { get; } + } +} diff --git a/Parser/Emitting/Emitter.cs b/Parser/Emitting/Emitter.cs index 27be020..51941b7 100644 --- a/Parser/Emitting/Emitter.cs +++ b/Parser/Emitting/Emitter.cs @@ -4,20 +4,34 @@ using Mono.Cecil.Rocks; using Parser.Binding; using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.IO; using System.Linq; namespace Parser.Emitting { + public static class BuiltInFunctions + { + public static readonly TypedFunctionSymbol Disp = new TypedFunctionSymbol( + "disp", + ImmutableArray.Create( + new TypedParameterSymbol("text", TypeSymbol.MObject)), + TypeSymbol.Void); + } + public class Emitter { private Dictionary _knownTypes = new Dictionary(); + private Dictionary _resolvedTypes = new Dictionary(); private Dictionary _functions = new Dictionary(); private MethodReference? _consoleWriteLineReference; private MethodReference? _dispReference; + private MethodReference? _lenReference; + private MethodReference? _arraySliceReference; private MethodReference? _mObjectToBool; private MethodReference? _stringToMObject; private MethodReference? _doubleToMObject; + private MethodReference? _intToMObject; private MethodReference? _getItemFromDictionary; private MethodReference? _putItemIntoDictionary; private TypeReference? _mObjectType; @@ -30,6 +44,8 @@ namespace Parser.Emitting private Dictionary _unaryOperations = new Dictionary(); private Dictionary _labels = new Dictionary(); private Dictionary _forwardLabelsToFix = new Dictionary(); + private Dictionary _typedLocals = new Dictionary(); + private Dictionary _builtInFunctions = new Dictionary(); private static TypeReference ResolveAndImportType( string typeName, @@ -132,6 +148,7 @@ namespace Parser.Emitting kind: ModuleKind.Console); var builtInTypes = new[] { + "System.Int32", "System.Object", "System.Void", "System.String", @@ -139,6 +156,12 @@ namespace Parser.Emitting "Parser.Objects.MObject" }; + var typeSymbolToKnownType = new Dictionary + { + [TypeSymbol.Int] = "System.Int32", + [TypeSymbol.MObject] = "Parser.Objects.MObject", + }; + // Resolve built-in types and methods. foreach (var typeName in builtInTypes) { @@ -146,6 +169,11 @@ namespace Parser.Emitting _knownTypes.Add(typeName, typeReference); } + foreach (var (typeSymbol, knownTypeName) in typeSymbolToKnownType) + { + _resolvedTypes.Add(typeSymbol, _knownTypes[knownTypeName]); + } + var objectType = _knownTypes["System.Object"]; var voidType = _knownTypes["System.Void"]; var stringType = _knownTypes["System.String"]; @@ -192,6 +220,20 @@ namespace Parser.Emitting assemblies: assemblies, assemblyDefinition: assemblyDefinition); + _lenReference = ResolveAndImportMethod( + typeName: "Parser.MFunctions.MHelpers", + methodName: "Len", + parameterTypeNames: new[] { "Parser.Objects.MObject" }, + assemblies: assemblies, + assemblyDefinition: assemblyDefinition); + + _arraySliceReference = ResolveAndImportMethod( + typeName: "Parser.MFunctions.MOperations", + methodName: "ArraySlice", + parameterTypeNames: new[] { "Parser.Objects.MObject", "Parser.Objects.MObject" }, + assemblies: assemblies, + assemblyDefinition: assemblyDefinition); + _mObjectToBool = ResolveAndImportMethod( typeName: "Parser.MFunctions.MHelpers", methodName: "ToBool", @@ -213,8 +255,16 @@ namespace Parser.Emitting assemblies: assemblies, assemblyDefinition: assemblyDefinition); + _intToMObject = ResolveAndImportMethod( + typeName: "Parser.Objects.MObject", + methodName: "CreateIntNumber", + parameterTypeNames: new[] { "System.Int32" }, + assemblies: assemblies, + assemblyDefinition: assemblyDefinition); + var binaryOperationNames = new Dictionary { + [BoundBinaryOperatorKind.Colon] = "Colon", [BoundBinaryOperatorKind.Plus] = "Plus", [BoundBinaryOperatorKind.Minus] = "Minus", [BoundBinaryOperatorKind.Star] = "Star", @@ -230,6 +280,10 @@ namespace Parser.Emitting [BoundUnaryOperatorKind.Minus] = "Minus", }; + _builtInFunctions.Add( + BuiltInFunctions.Disp, + _dispReference); + foreach (var (op, opName) in binaryOperationNames) { _binaryOperations[op] = ResolveAndImportMethod( @@ -306,10 +360,12 @@ namespace Parser.Emitting private void EmitFunction(LoweredFunction function, MethodDefinition methodDefinition) { + Console.WriteLine($"Emitting function '{function.Name}'."); var ilProcessor = methodDefinition.Body.GetILProcessor(); _labels.Clear(); _forwardLabelsToFix.Clear(); + _typedLocals.Clear(); // Local #0 is the dictionary with actual local variables. _currentLocals = new VariableDefinition(_stringMObjectDictionary); @@ -356,13 +412,16 @@ namespace Parser.Emitting } } + ilProcessor.Emit(OpCodes.Ret); + foreach (var (index, target) in _forwardLabelsToFix) { var targetIndex = _labels[target]; - ilProcessor.Body.Instructions[index].Operand = ilProcessor.Body.Instructions[targetIndex]; + var left = ilProcessor.Body.Instructions[index]; + var right = ilProcessor.Body.Instructions[targetIndex]; + left.Operand = right; } - ilProcessor.Emit(OpCodes.Ret); } private void EmitBlockStatement(BoundBlockStatement block, MethodDefinition methodDefinition) @@ -431,11 +490,25 @@ namespace Parser.Emitting case BoundNodeKind.ExpressionStatement: EmitExpressionStatement((BoundExpressionStatement)node, ilProcessor); break; + case BoundNodeKind.TypedVariableDeclaration: + EmitTypedVariableDeclaration((BoundTypedVariableDeclaration)node, ilProcessor); + break; default: throw new NotImplementedException($"Invalid statement kind '{node.Kind}'."); }; } + private void EmitTypedVariableDeclaration(BoundTypedVariableDeclaration node, ILProcessor ilProcessor) + { + var typeReference = _resolvedTypes[node.Variable.Type]; + var variableDefinition = new VariableDefinition(typeReference); + _typedLocals.Add(node.Variable, variableDefinition); + ilProcessor.Body.Variables.Add(variableDefinition); + + EmitExpression(node.Initializer, ilProcessor); + ilProcessor.Emit(OpCodes.Stloc, variableDefinition); + } + private void EmitExpressionStatement(BoundExpressionStatement node, ILProcessor ilProcessor) { EmitExpression(node.Expression, ilProcessor); @@ -458,6 +531,9 @@ namespace Parser.Emitting case BoundNodeKind.BinaryOperationExpression: EmitBinaryOperationExpression((BoundBinaryOperationExpression)node, ilProcessor); break; + case BoundNodeKind.ConversionExpression: + EmitConversionExpression((BoundConversionExpression)node, ilProcessor); + break; case BoundNodeKind.FunctionCallExpression: EmitFunctionCallExpression((BoundFunctionCallExpression)node, ilProcessor); break; @@ -465,16 +541,47 @@ namespace Parser.Emitting EmitIdentifierNameExpression((BoundIdentifierNameExpression)node, ilProcessor); break; case BoundNodeKind.NumberLiteralExpression: - EmitNumberLiteralExpression((BoundNumberLiteralExpression)node, ilProcessor); + EmitNumberLiteralExpression((BoundNumberDoubleLiteralExpression)node, ilProcessor); break; case BoundNodeKind.StringLiteralExpression: EmitStringLiteralExpression((BoundStringLiteralExpression)node, ilProcessor); break; + case BoundNodeKind.TypedFunctionCallExpression: + EmitTypedFunctionCallExpression((BoundTypedFunctionCallExpression)node, ilProcessor); + break; + case BoundNodeKind.TypedVariableExpression: + EmitTypedVariableExpression((BoundTypedVariableExpression)node, ilProcessor); + break; default: throw new NotImplementedException($"Invalid node kind '{node.Kind}'."); } } + private void EmitConversionExpression(BoundConversionExpression node, ILProcessor ilProcessor) + { + var fromType = node.Expression.Type; + var toType = node.TargetType; + EmitExpression(node.Expression, ilProcessor); + if ((fromType, toType) == (TypeSymbol.Double, TypeSymbol.MObject)) + { + ilProcessor.Emit(OpCodes.Call, _doubleToMObject); + } + else if ((fromType, toType) == (TypeSymbol.String, TypeSymbol.MObject)) + { + ilProcessor.Emit(OpCodes.Call, _stringToMObject); + } + else + { + throw new NotImplementedException($"Conversion of '{fromType}' to '{toType}' is not implemented."); + } + } + + private void EmitTypedVariableExpression(BoundTypedVariableExpression node, ILProcessor ilProcessor) + { + var variableDefinition = _typedLocals[node.Variable]; + ilProcessor.Emit(OpCodes.Ldloc, variableDefinition); + } + private void EmitBinaryOperationExpression(BoundBinaryOperationExpression node, ILProcessor ilProcessor) { var method = _binaryOperations[node.Op.Kind]; @@ -485,18 +592,38 @@ namespace Parser.Emitting private void EmitAssignmentExpression(BoundAssignmentExpression node, ILProcessor ilProcessor) { - if (node.Left.Kind != BoundNodeKind.IdentifierNameExpression) + var leftType = node.Left.Kind switch { - throw new Exception("Assignment to complex lvalues is not supported."); + BoundNodeKind.IdentifierNameExpression => TypeSymbol.MObject, + BoundNodeKind.TypedVariableExpression => ((BoundTypedVariableExpression)node.Left).Type, + _ => throw new Exception($"Assignment to lvalue of kind {node.Left.Kind} is not supported."), + }; + + var rightType = node.Right.Type; + + var rewrittenRight = ConvertExpression(node.Right, leftType); + + if (node.Left.Kind == BoundNodeKind.IdentifierNameExpression) + { + var left = ((BoundIdentifierNameExpression)node.Left); + ilProcessor.Emit(OpCodes.Ldloc_0); + ilProcessor.Emit(OpCodes.Ldstr, left.Name); + EmitExpression(rewrittenRight, ilProcessor); + ilProcessor.Emit(OpCodes.Callvirt, _putItemIntoDictionary); + ilProcessor.Emit(OpCodes.Ldloc_0); + ilProcessor.Emit(OpCodes.Ldstr, left.Name); + ilProcessor.Emit(OpCodes.Callvirt, _getItemFromDictionary); + } + else if (node.Left.Kind == BoundNodeKind.TypedVariableExpression) + { + var typedVariableExpression = (BoundTypedVariableExpression)node.Left; + EmitExpression(rewrittenRight, ilProcessor); + ilProcessor.Emit(OpCodes.Stloc, _typedLocals[typedVariableExpression.Variable]); + } + else + { + throw new Exception($"Assignment to lvalue of kind {node.Left.Kind} is not supported."); } - var left = ((BoundIdentifierNameExpression)node.Left); - ilProcessor.Emit(OpCodes.Ldloc_0); - ilProcessor.Emit(OpCodes.Ldstr, left.Name); - EmitExpression(node.Right, ilProcessor); - ilProcessor.Emit(OpCodes.Callvirt, _putItemIntoDictionary); - ilProcessor.Emit(OpCodes.Ldloc_0); - ilProcessor.Emit(OpCodes.Ldstr, left.Name); - ilProcessor.Emit(OpCodes.Callvirt, _getItemFromDictionary); } private void EmitIdentifierNameExpression(BoundIdentifierNameExpression node, ILProcessor ilProcessor) @@ -506,26 +633,109 @@ namespace Parser.Emitting ilProcessor.Emit(OpCodes.Callvirt, _getItemFromDictionary); } - private void EmitNumberLiteralExpression(BoundNumberLiteralExpression node, ILProcessor ilProcessor) + private void EmitNumberLiteralExpression(BoundNumberDoubleLiteralExpression node, ILProcessor ilProcessor) { ilProcessor.Emit(OpCodes.Ldc_R8, node.Value); - ilProcessor.Emit(OpCodes.Call, _doubleToMObject); } private void EmitStringLiteralExpression(BoundStringLiteralExpression node, ILProcessor ilProcessor) { ilProcessor.Emit(OpCodes.Ldstr, node.Value); - ilProcessor.Emit(OpCodes.Call, _stringToMObject); + } + + private BoundExpression? ConvertExpression(BoundExpression expression, TypeSymbol targetType) + { + var conversion = Conversion.Classify(expression.Type, targetType); + if (!conversion.Exists) + { + return null; + } + + if (conversion.IsIdentity) + { + return expression; + } + else + { + return BoundNodeFactory.Conversion(expression.Syntax, targetType, expression); + } + + } + + private BoundExpression RewriteFunctionCall(BoundFunctionCallExpression node, TypedFunctionSymbol function) + { + var numberOfArguments = node.Arguments.Length; + if (numberOfArguments != function.Parameters.Length) + { + throw new NotImplementedException($"Function '{function.Name}' expected {function.Parameters.Length} arguments, but was called with {numberOfArguments}."); + } + + var rewrittenArguments = ImmutableArray.CreateBuilder(numberOfArguments); + for (var i = 0; i < numberOfArguments; i++) + { + var argument = node.Arguments[i]; + var parameter = function.Parameters[i]; + var rewrittenArgument = ConvertExpression(argument, parameter.Type); + if (rewrittenArgument is null) + { + throw new NotImplementedException($"Argument number {i + 1} of function '{function.Name}' expects {parameter.Type}, but got {argument.Type}, and no conversion exists."); + + } + rewrittenArguments.Add(rewrittenArgument); + } + + return BoundNodeFactory.TypedFunctionCall(node.Syntax, function, rewrittenArguments.MoveToImmutable()); + } + + private void EmitTypedFunctionCallExpression(BoundTypedFunctionCallExpression node, ILProcessor ilProcessor) + { + var function = _builtInFunctions[node.Function]; + foreach (var argument in node.Arguments) + { + EmitExpression(argument, ilProcessor); + } + + ilProcessor.Emit(OpCodes.Call, function); + if (node.Function.ReturnType == TypeSymbol.Void) + { + ilProcessor.Emit(OpCodes.Ldnull); + } + else if (node.Function.ReturnType == TypeSymbol.MObject) + { + } + else + { + throw new Exception("Don't know how to cast function output."); + } } private void EmitFunctionCallExpression(BoundFunctionCallExpression node, ILProcessor ilProcessor) { if (node.Name.Kind == BoundNodeKind.IdentifierNameExpression && ((BoundIdentifierNameExpression)node.Name).Name == "disp") + { + var dispFunctionSymbol = BuiltInFunctions.Disp; + var rewrittenCall = RewriteFunctionCall(node, dispFunctionSymbol); + EmitExpression(rewrittenCall, ilProcessor); + //ilProcessor.Emit(OpCodes.Call, _dispReference); + //ilProcessor.Emit(OpCodes.Ldnull); + } else if (node.Name.Kind == BoundNodeKind.IdentifierNameExpression + && ((BoundIdentifierNameExpression)node.Name).Name == "len") { EmitExpression(node.Arguments[0], ilProcessor); - ilProcessor.Emit(OpCodes.Call, _dispReference); - ilProcessor.Emit(OpCodes.Ldnull); + ilProcessor.Emit(OpCodes.Call, _lenReference); + } + else if (node.Name.Kind == BoundNodeKind.TypedVariableExpression) + { + if (node.Arguments.Length > 1) + { + throw new Exception("Multi-dimensional array slicing is not supported."); + } + + var typedVariableExpression = (BoundTypedVariableExpression)node.Name; + EmitTypedVariableExpression(typedVariableExpression, ilProcessor); + EmitExpression(node.Arguments[0], ilProcessor); + ilProcessor.Emit(OpCodes.Call, _arraySliceReference); } else { @@ -559,11 +769,11 @@ namespace Parser.Emitting } } - private MethodInterfaceDescription ResolveFunction(BoundExpression expression) + private MethodInterfaceDescription ResolveFunction(BoundExpression node) { - if (expression.Kind == BoundNodeKind.IdentifierNameExpression) + if (node.Kind == BoundNodeKind.IdentifierNameExpression) { - var name = ((BoundIdentifierNameExpression)expression).Name; + var name = ((BoundIdentifierNameExpression)node).Name; if (_functions.TryGetValue(name, out var result)) { return result; @@ -574,7 +784,7 @@ namespace Parser.Emitting } else { - throw new NotImplementedException($"Dynamic functions calling not supported. Failed to resolve function call expression with kind '{expression.Kind}'."); + throw new NotImplementedException($"Dynamic functions calling not supported. Failed to resolve function call expression with kind '{node.Kind}'."); } } } diff --git a/Parser/Evaluator.cs b/Parser/Evaluator.cs index e4b56f6..160d175 100644 --- a/Parser/Evaluator.cs +++ b/Parser/Evaluator.cs @@ -251,7 +251,7 @@ namespace Parser BoundNodeKind.NamedFunctionHandleExpression => EvaluateNamedFunctionHandleExpression((BoundNamedFunctionHandleExpression)node), BoundNodeKind.NumberLiteralExpression => - EvaluateNumberLiteralExpression((BoundNumberLiteralExpression)node), + EvaluateNumberLiteralExpression((BoundNumberDoubleLiteralExpression)node), BoundNodeKind.StringLiteralExpression => EvaluateStringLiteralExpression((BoundStringLiteralExpression)node), BoundNodeKind.UnaryOperationExpression => @@ -420,7 +420,7 @@ namespace Parser }; } - private MObject? EvaluateNumberLiteralExpression(BoundNumberLiteralExpression node) + private MObject? EvaluateNumberLiteralExpression(BoundNumberDoubleLiteralExpression node) { return MObject.CreateDoubleNumber(node.Value); } diff --git a/Parser/Internal/DiagnosticsBag.cs b/Parser/Internal/DiagnosticsBag.cs index e81e7d0..574261f 100644 --- a/Parser/Internal/DiagnosticsBag.cs +++ b/Parser/Internal/DiagnosticsBag.cs @@ -107,5 +107,10 @@ namespace Parser.Internal { Report(span, $"Too many inputs in the call to '{functionName}'."); } + + internal void ReportForLoopWithoutVariable(TextSpan span) + { + Report(span, $"A 'for' loop must have a loop variable."); + } } } \ No newline at end of file diff --git a/Parser/Lowering/Lowerer.cs b/Parser/Lowering/Lowerer.cs index fbe3d05..95f8971 100644 --- a/Parser/Lowering/Lowerer.cs +++ b/Parser/Lowering/Lowerer.cs @@ -1,5 +1,4 @@ using Parser.Binding; -using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -10,6 +9,7 @@ namespace Parser.Lowering internal class Lowerer : BoundTreeRewriter { private int _labelNumber = 0; + private int _localVariableNumber = 0; private Lowerer() { @@ -21,6 +21,12 @@ namespace Parser.Lowering return new BoundLabel(name); } + private TypedVariableSymbol GenerateTypedLocalVariable(TypeSymbol typeSymbol) + { + var name = $"#{++_localVariableNumber}"; + return new TypedVariableSymbol(name, typeSymbol); + } + public override BoundStatement RewriteIfStatement(BoundIfStatement node) { // if cond @@ -80,6 +86,84 @@ namespace Parser.Lowering return RewriteBlockStatement(Block(node.Syntax, builder.ToArray())); } + public override BoundStatement RewriteForStatement(BoundForStatement node) + { + // for i = expr + // body + // end + // + // #array = expr + // #length = len(#array) + // #index = 0 + // i = #array(#index); + // LabelLoop: + // gotoFalse (#index < #length) LabelEnd + // body + // #index = #index + 1 + // goto LabelLoop + // LabelEnd: + var builder = ImmutableArray.CreateBuilder(); + var labelLoop = GenerateLabel(); + var labelEnd = GenerateLabel(); + var localArray = GenerateTypedLocalVariable(TypeSymbol.MObject); + var localLength = GenerateTypedLocalVariable(TypeSymbol.Int); + var localIndex = GenerateTypedLocalVariable(TypeSymbol.Int); + return RewriteBlockStatement(Block( + node.Syntax, + // #array = expr + TypedVariableDeclaration(node.Syntax, localArray, node.LoopedExpression), + // #length = len(#array) + TypedVariableDeclaration( + node.Syntax, + localLength, + FunctionCall( + node.Syntax, + Identifier(node.Syntax, "len"), + new[] { (BoundExpression)TypedVariableExpression(node.Syntax, localArray) }.ToImmutableArray())), + // #index = 0 + TypedVariableDeclaration(node.Syntax, localIndex, NumberDoubleLiteral(node.Syntax, 0.0)), + // i = #array(#index); + ExpressionStatement( + node.Syntax, + Assignment( + node.Syntax, + node.LoopVariable, + FunctionCall( + node.Syntax, + TypedVariableExpression(node.Syntax, localArray), + new[] { (BoundExpression)TypedVariableExpression(node.Syntax, localIndex) }.ToImmutableArray())), + discardResult: true), + // LabelLoop: + LabelStatement(node.Syntax, labelLoop), + // gotoFalse (#index < #length) LabelEnd + GotoIfFalse( + node.Syntax, + BinaryOperation( + node.Syntax, + TypedVariableExpression(node.Syntax, localIndex), + BoundBinaryOperator.GetOperator(TokenKind.LessToken, TypeSymbol.Int, TypeSymbol.Int)!, + TypedVariableExpression(node.Syntax, localLength)), + labelEnd), + // body + node.Body, + // #index = #index + 1; + ExpressionStatement( + node.Syntax, + Assignment( + node.Syntax, + TypedVariableExpression(node.Syntax, localIndex), + BinaryOperation( + node.Syntax, + TypedVariableExpression(node.Syntax, localIndex), + BoundBinaryOperator.GetOperator(TokenKind.PlusToken, TypeSymbol.Int, TypeSymbol.Int)!, + NumberIntLiteral(node.Syntax, 1))), + discardResult: true), + // goto LabelLoop + Goto(node.Syntax, labelLoop), + // LabelEnd: + LabelStatement(node.Syntax, labelEnd))); + } + public static BoundBlockStatement Lower(BoundStatement statement) { var lowerer = new Lowerer(); diff --git a/Parser/MFunctions/MHelpers.cs b/Parser/MFunctions/MHelpers.cs index fc975f8..d4fa446 100644 --- a/Parser/MFunctions/MHelpers.cs +++ b/Parser/MFunctions/MHelpers.cs @@ -13,6 +13,18 @@ namespace Parser.MFunctions } } + public static int Len(MObject? obj) + { + return obj switch + { + MDoubleMatrix m => m.RowCount * m.ColumnCount, + MDoubleNumber _ => 1, + MLogical _ => 1, + MCharArray c => c.Chars.Length, + _ => throw new System.Exception($"Unknown MObject type {obj.GetType()}"), + }; + } + public static bool ToBool(MObject operand) { return operand switch @@ -20,6 +32,7 @@ namespace Parser.MFunctions MDoubleNumber { Value: var value } => value != 0.0, MLogical { Value: var value } => value, MCharArray { Chars: var value } => value.Length > 0, + MDoubleMatrix m => m.RowCount > 0 && m.ColumnCount > 0, _ => throw new System.Exception($"Unknown MObject type {operand.GetType()}"), }; } diff --git a/Parser/MFunctions/MOperations.cs b/Parser/MFunctions/MOperations.cs index f454b3f..2fccee3 100644 --- a/Parser/MFunctions/MOperations.cs +++ b/Parser/MFunctions/MOperations.cs @@ -1,9 +1,34 @@ using Parser.Objects; +using System; namespace Parser.MFunctions { public static class MOperations { + public static MObject? Colon(MObject left, MObject right) + { + if (left is MDoubleNumber { Value: var lValue } + && right is MDoubleNumber { Value: var rValue }) + { + var array = CreateRangeMatrix(lValue, rValue); + return MObject.CreateDoubleMatrix(array); + } + + return null; + } + + private static double[,] CreateRangeMatrix(double lValue, double rValue) + { + var length = (int)Math.Round(rValue - lValue) + 1; + var result = new double[1, length]; + for (var i = 0; i < length; i++) + { + result[0, i] = lValue + i; + } + + return result; + } + public static MObject? Plus(MObject left, MObject right) { if (left is MDoubleNumber { Value: var lValue } @@ -101,5 +126,17 @@ namespace Parser.MFunctions return null; } + + public static MObject? ArraySlice(MObject array, MObject range) + { + if (array is MDoubleMatrix m + && range is MDoubleNumber { Value: var doubleIndex }) + { + var index = (int)doubleIndex; + return MObject.CreateDoubleNumber(m[index]); + } + + return null; + } } } diff --git a/Parser/Objects/MDoubleNumber.cs b/Parser/Objects/MDoubleNumber.cs index bb7f76f..b7bd105 100644 --- a/Parser/Objects/MDoubleNumber.cs +++ b/Parser/Objects/MDoubleNumber.cs @@ -1,7 +1,50 @@ using System.Globalization; +using System.Text; namespace Parser.Objects { + public class MDoubleMatrix : MObject + { + private MDoubleMatrix(double[,] matrix) + { + Matrix = matrix; + } + + public double[,] Matrix { get; } + + public int RowCount { get; } + + public int ColumnCount { get; } + + public ref double this[int i, int j] => ref Matrix[i, j]; + + public ref double this[int i] => ref Matrix[i % RowCount, i / RowCount]; + + public override string ToString() + { + var sb = new StringBuilder(); + for (var i = 0; i < RowCount; i++) + { + for (var j = 0; j < ColumnCount; j++) + { + if (j > 0) + { + sb.Append(' '); + } + sb.Append(Matrix[i, j]); + } + + sb.AppendLine(); + } + return sb.ToString(); + } + + public static MDoubleMatrix Create(double[,] matrix) + { + return new MDoubleMatrix(matrix); + } + } + public class MDoubleNumber : MObject { private MDoubleNumber(double value) diff --git a/Parser/Objects/MObject.cs b/Parser/Objects/MObject.cs index bebc8dc..0df472f 100644 --- a/Parser/Objects/MObject.cs +++ b/Parser/Objects/MObject.cs @@ -1,4 +1,6 @@ -namespace Parser.Objects +using System; + +namespace Parser.Objects { public abstract class MObject { @@ -7,6 +9,16 @@ return MDoubleNumber.Create(value); } + public static MDoubleNumber CreateIntNumber(int value) + { + return MDoubleNumber.Create(value); + } + + public static MDoubleMatrix CreateDoubleMatrix(double[,] matrix) + { + return MDoubleMatrix.Create(matrix); + } + public static MCharArray CreateCharArray(char[] chars) { return MCharArray.Create(chars); diff --git a/examples/helloworld/hello.m b/examples/helloworld/hello.m index d13622c..e5c74d8 100644 --- a/examples/helloworld/hello.m +++ b/examples/helloworld/hello.m @@ -5,6 +5,11 @@ f(x); x = 3; f(x); +% for i = 1:10 +% disp(i); +% end + + function f(x) disp('X was'); disp(x); From 52198147e2616bb86ea912af87d48d238356a260 Mon Sep 17 00:00:00 2001 From: Alexander Luzgarev Date: Mon, 20 Jul 2020 13:44:52 +0200 Subject: [PATCH 06/14] Emit integers --- Parser/Binding/Binder.cs | 12 ++++++++++-- Parser/Binding/BoundNodeKind.cs | 3 ++- Parser/Binding/BoundRoot.cs | 17 ++++++++++++----- Parser/Binding/BoundTreeRewriter.cs | 13 ++++++++++--- Parser/Emitting/Emitter.cs | 18 +++++++++++++++--- Parser/Evaluator.cs | 13 ++++++++++--- 6 files changed, 59 insertions(+), 17 deletions(-) diff --git a/Parser/Binding/Binder.cs b/Parser/Binding/Binder.cs index c84550b..354562c 100644 --- a/Parser/Binding/Binder.cs +++ b/Parser/Binding/Binder.cs @@ -462,10 +462,18 @@ namespace Parser.Binding throw new NotImplementedException(); } - private BoundNumberDoubleLiteralExpression BindNumberLiteralExpression(NumberLiteralExpressionSyntaxNode node) + private BoundNumberLiteralExpression BindNumberLiteralExpression(NumberLiteralExpressionSyntaxNode node) { var value = (double)node.Number.Value!; - return NumberDoubleLiteral(node, value); + var intValue = (int)Math.Round(value); + if (intValue == value) + { + return NumberIntLiteral(node, intValue); + } + else + { + return NumberDoubleLiteral(node, value); + } } private BoundExpression BindParenthesizedExpression(ParenthesizedExpressionSyntaxNode node) diff --git a/Parser/Binding/BoundNodeKind.cs b/Parser/Binding/BoundNodeKind.cs index 19a0963..dec18e0 100644 --- a/Parser/Binding/BoundNodeKind.cs +++ b/Parser/Binding/BoundNodeKind.cs @@ -44,7 +44,8 @@ LambdaExpression, MemberAccessExpression, NamedFunctionHandleExpression, - NumberLiteralExpression, + NumberDoubleLiteralExpression, + NumberIntLiteralExpression, ParenthesizedExpression, StringLiteralExpression, TypedFunctionCallExpression, diff --git a/Parser/Binding/BoundRoot.cs b/Parser/Binding/BoundRoot.cs index e4a2a17..041abb9 100644 --- a/Parser/Binding/BoundRoot.cs +++ b/Parser/Binding/BoundRoot.cs @@ -515,7 +515,14 @@ namespace Parser.Binding public override TypeSymbol Type => throw new System.NotImplementedException(); } - public class BoundNumberDoubleLiteralExpression : BoundExpression + public abstract class BoundNumberLiteralExpression : BoundExpression + { + protected BoundNumberLiteralExpression(SyntaxNode syntax) : base(syntax) + { + } + } + + public class BoundNumberDoubleLiteralExpression : BoundNumberLiteralExpression { public BoundNumberDoubleLiteralExpression(SyntaxNode syntax, double value) : base(syntax) @@ -524,12 +531,12 @@ namespace Parser.Binding } public double Value { get; } - public override BoundNodeKind Kind => BoundNodeKind.NumberLiteralExpression; + public override BoundNodeKind Kind => BoundNodeKind.NumberDoubleLiteralExpression; public override TypeSymbol Type => TypeSymbol.Double; } - public class BoundNumberIntLiteralExpression : BoundExpression + public class BoundNumberIntLiteralExpression : BoundNumberLiteralExpression { public BoundNumberIntLiteralExpression(SyntaxNode syntax, int value) : base(syntax) @@ -537,8 +544,8 @@ namespace Parser.Binding Value = value; } - public double Value { get; } - public override BoundNodeKind Kind => BoundNodeKind.NumberLiteralExpression; + public int Value { get; } + public override BoundNodeKind Kind => BoundNodeKind.NumberIntLiteralExpression; public override TypeSymbol Type => TypeSymbol.Int; } diff --git a/Parser/Binding/BoundTreeRewriter.cs b/Parser/Binding/BoundTreeRewriter.cs index 42de105..2074331 100644 --- a/Parser/Binding/BoundTreeRewriter.cs +++ b/Parser/Binding/BoundTreeRewriter.cs @@ -245,8 +245,10 @@ namespace Parser.Binding RewriteMemberAccessExpression((BoundMemberAccessExpression)node), BoundNodeKind.NamedFunctionHandleExpression => RewriteNamedFunctionHandleExpression((BoundNamedFunctionHandleExpression)node), - BoundNodeKind.NumberLiteralExpression => - RewriteNumberLiteralExpression((BoundNumberDoubleLiteralExpression)node), + BoundNodeKind.NumberDoubleLiteralExpression => + RewriteNumberDoubleLiteralExpression((BoundNumberDoubleLiteralExpression)node), + BoundNodeKind.NumberIntLiteralExpression => + RewriteNumberIntLiteralExpression((BoundNumberIntLiteralExpression)node), BoundNodeKind.StringLiteralExpression => RewriteStringLiteralExpression((BoundStringLiteralExpression)node), BoundNodeKind.TypedVariableExpression => @@ -287,7 +289,12 @@ namespace Parser.Binding return node; } - public virtual BoundExpression RewriteNumberLiteralExpression(BoundNumberDoubleLiteralExpression node) + public virtual BoundExpression RewriteNumberDoubleLiteralExpression(BoundNumberDoubleLiteralExpression node) + { + return node; + } + + public virtual BoundExpression RewriteNumberIntLiteralExpression(BoundNumberIntLiteralExpression node) { return node; } diff --git a/Parser/Emitting/Emitter.cs b/Parser/Emitting/Emitter.cs index 51941b7..57b2c59 100644 --- a/Parser/Emitting/Emitter.cs +++ b/Parser/Emitting/Emitter.cs @@ -540,8 +540,11 @@ namespace Parser.Emitting case BoundNodeKind.IdentifierNameExpression: EmitIdentifierNameExpression((BoundIdentifierNameExpression)node, ilProcessor); break; - case BoundNodeKind.NumberLiteralExpression: - EmitNumberLiteralExpression((BoundNumberDoubleLiteralExpression)node, ilProcessor); + case BoundNodeKind.NumberDoubleLiteralExpression: + EmitNumberDoubleLiteralExpression((BoundNumberDoubleLiteralExpression)node, ilProcessor); + break; + case BoundNodeKind.NumberIntLiteralExpression: + EmitNumberIntLiteralExpression((BoundNumberIntLiteralExpression)node, ilProcessor); break; case BoundNodeKind.StringLiteralExpression: EmitStringLiteralExpression((BoundStringLiteralExpression)node, ilProcessor); @@ -566,6 +569,10 @@ namespace Parser.Emitting { ilProcessor.Emit(OpCodes.Call, _doubleToMObject); } + if ((fromType, toType) == (TypeSymbol.Int, TypeSymbol.MObject)) + { + ilProcessor.Emit(OpCodes.Call, _intToMObject); + } else if ((fromType, toType) == (TypeSymbol.String, TypeSymbol.MObject)) { ilProcessor.Emit(OpCodes.Call, _stringToMObject); @@ -633,11 +640,16 @@ namespace Parser.Emitting ilProcessor.Emit(OpCodes.Callvirt, _getItemFromDictionary); } - private void EmitNumberLiteralExpression(BoundNumberDoubleLiteralExpression node, ILProcessor ilProcessor) + private void EmitNumberDoubleLiteralExpression(BoundNumberDoubleLiteralExpression node, ILProcessor ilProcessor) { ilProcessor.Emit(OpCodes.Ldc_R8, node.Value); } + private void EmitNumberIntLiteralExpression(BoundNumberIntLiteralExpression node, ILProcessor ilProcessor) + { + ilProcessor.Emit(OpCodes.Ldc_I4, node.Value); + } + private void EmitStringLiteralExpression(BoundStringLiteralExpression node, ILProcessor ilProcessor) { ilProcessor.Emit(OpCodes.Ldstr, node.Value); diff --git a/Parser/Evaluator.cs b/Parser/Evaluator.cs index 160d175..a563051 100644 --- a/Parser/Evaluator.cs +++ b/Parser/Evaluator.cs @@ -250,8 +250,10 @@ namespace Parser EvaluateMemberAccess((BoundMemberAccessExpression)node), BoundNodeKind.NamedFunctionHandleExpression => EvaluateNamedFunctionHandleExpression((BoundNamedFunctionHandleExpression)node), - BoundNodeKind.NumberLiteralExpression => - EvaluateNumberLiteralExpression((BoundNumberDoubleLiteralExpression)node), + BoundNodeKind.NumberDoubleLiteralExpression => + EvaluateNumberDoubleLiteralExpression((BoundNumberDoubleLiteralExpression)node), + BoundNodeKind.NumberIntLiteralExpression => + EvaluateNumberIntLiteralExpression((BoundNumberIntLiteralExpression)node), BoundNodeKind.StringLiteralExpression => EvaluateStringLiteralExpression((BoundStringLiteralExpression)node), BoundNodeKind.UnaryOperationExpression => @@ -420,7 +422,12 @@ namespace Parser }; } - private MObject? EvaluateNumberLiteralExpression(BoundNumberDoubleLiteralExpression node) + private MObject? EvaluateNumberDoubleLiteralExpression(BoundNumberDoubleLiteralExpression node) + { + return MObject.CreateDoubleNumber(node.Value); + } + + private MObject? EvaluateNumberIntLiteralExpression(BoundNumberIntLiteralExpression node) { return MObject.CreateDoubleNumber(node.Value); } From f66013cf12d86173693460d28d13a6d21baf76e7 Mon Sep 17 00:00:00 2001 From: Alexander Luzgarev Date: Mon, 20 Jul 2020 14:02:39 +0200 Subject: [PATCH 07/14] Implement While --- Parser/Binding/Binder.cs | 12 ++++++---- Parser/Binding/BoundNodeFactory.cs | 8 +++++++ Parser/Binding/BoundRoot.cs | 8 ++++++- Parser/Binding/BoundTreeRewriter.cs | 4 +++- Parser/Binding/Conversion.cs | 5 ++++ Parser/Emitting/Emitter.cs | 15 +++++++++--- Parser/Lowering/Lowerer.cs | 36 ++++++++++++++++++++++++++++- examples/helloworld/hello.m | 6 +++++ 8 files changed, 83 insertions(+), 11 deletions(-) diff --git a/Parser/Binding/Binder.cs b/Parser/Binding/Binder.cs index 354562c..ba0b8b1 100644 --- a/Parser/Binding/Binder.cs +++ b/Parser/Binding/Binder.cs @@ -132,11 +132,6 @@ namespace Parser.Binding }; } - private BoundWhileStatement BindWhileStatement(WhileStatementSyntaxNode node) - { - throw new NotImplementedException(); - } - private BoundTryCatchStatement BindTryCatchStatement(TryCatchStatementSyntaxNode node) { throw new NotImplementedException(); @@ -290,6 +285,13 @@ namespace Parser.Binding return Identifier(node, ((IdentifierNameExpressionSyntaxNode)node).Name.Text); } + private BoundWhileStatement BindWhileStatement(WhileStatementSyntaxNode node) + { + var condition = BindConversion(BindExpression(node.Condition), TypeSymbol.Boolean); + var body = BindStatement(node.Body); + return WhileStatement(node, condition, body); + } + private BoundExpressionStatement BindExpressionStatement(ExpressionStatementSyntaxNode node) { var expression = BindExpression(node.Expression); diff --git a/Parser/Binding/BoundNodeFactory.cs b/Parser/Binding/BoundNodeFactory.cs index 26bf3ff..845453e 100644 --- a/Parser/Binding/BoundNodeFactory.cs +++ b/Parser/Binding/BoundNodeFactory.cs @@ -133,6 +133,14 @@ namespace Parser.Binding return new BoundGotoStatement(syntax, label); } + public static BoundWhileStatement WhileStatement( + SyntaxNode syntax, + BoundExpression condition, + BoundStatement body) + { + return new BoundWhileStatement(syntax, condition, body); + } + public static BoundIdentifierNameExpression Identifier( SyntaxNode syntax, string name) diff --git a/Parser/Binding/BoundRoot.cs b/Parser/Binding/BoundRoot.cs index 041abb9..5810d05 100644 --- a/Parser/Binding/BoundRoot.cs +++ b/Parser/Binding/BoundRoot.cs @@ -256,12 +256,18 @@ namespace Parser.Binding public class BoundWhileStatement : BoundStatement { - public BoundWhileStatement(SyntaxNode syntax) + public BoundWhileStatement(SyntaxNode syntax, BoundExpression condition, BoundStatement body) : base(syntax) { + Condition = condition; + Body = body; } public override BoundNodeKind Kind => BoundNodeKind.WhileStatement; + + public BoundExpression Condition { get; } + + public BoundStatement Body { get; } } public abstract class BoundExpression : BoundNode diff --git a/Parser/Binding/BoundTreeRewriter.cs b/Parser/Binding/BoundTreeRewriter.cs index 2074331..db3273d 100644 --- a/Parser/Binding/BoundTreeRewriter.cs +++ b/Parser/Binding/BoundTreeRewriter.cs @@ -70,7 +70,9 @@ namespace Parser.Binding public virtual BoundStatement RewriteWhileStatement(BoundWhileStatement node) { - throw new NotImplementedException(); + var condition = RewriteExpression(node.Condition); + var body = RewriteStatement(node.Body); + return WhileStatement(node.Syntax, condition, body); } public virtual BoundStatement RewriteTryCatchStatement(BoundTryCatchStatement node) diff --git a/Parser/Binding/Conversion.cs b/Parser/Binding/Conversion.cs index 9db7af3..d4c4ae0 100644 --- a/Parser/Binding/Conversion.cs +++ b/Parser/Binding/Conversion.cs @@ -28,6 +28,11 @@ return Implicit; } + if (to == TypeSymbol.Boolean) + { + return Implicit; + } + return None; } } diff --git a/Parser/Emitting/Emitter.cs b/Parser/Emitting/Emitter.cs index 57b2c59..16a5015 100644 --- a/Parser/Emitting/Emitter.cs +++ b/Parser/Emitting/Emitter.cs @@ -467,8 +467,13 @@ namespace Parser.Emitting private void EmitConditionalGoto(BoundConditionalGotoStatement node, ILProcessor ilProcessor) { - EmitExpression(node.Condition, ilProcessor); - ilProcessor.Emit(OpCodes.Call, _mObjectToBool); + var condition = ConvertExpression(node.Condition, TypeSymbol.Boolean); + if (condition is null) + { + throw new Exception("Cannot cast a condition in GOTO to boolean."); + } + + EmitExpression(condition, ilProcessor); var instruction = node.GotoIfTrue ? OpCodes.Brtrue : OpCodes.Brfalse; if (_labels.TryGetValue(node.Label, out var target)) { @@ -569,7 +574,7 @@ namespace Parser.Emitting { ilProcessor.Emit(OpCodes.Call, _doubleToMObject); } - if ((fromType, toType) == (TypeSymbol.Int, TypeSymbol.MObject)) + else if ((fromType, toType) == (TypeSymbol.Int, TypeSymbol.MObject)) { ilProcessor.Emit(OpCodes.Call, _intToMObject); } @@ -577,6 +582,10 @@ namespace Parser.Emitting { ilProcessor.Emit(OpCodes.Call, _stringToMObject); } + else if ((fromType, toType) == (TypeSymbol.MObject, TypeSymbol.Boolean)) + { + ilProcessor.Emit(OpCodes.Call, _mObjectToBool); + } else { throw new NotImplementedException($"Conversion of '{fromType}' to '{toType}' is not implemented."); diff --git a/Parser/Lowering/Lowerer.cs b/Parser/Lowering/Lowerer.cs index 95f8971..7950fb9 100644 --- a/Parser/Lowering/Lowerer.cs +++ b/Parser/Lowering/Lowerer.cs @@ -86,6 +86,41 @@ namespace Parser.Lowering return RewriteBlockStatement(Block(node.Syntax, builder.ToArray())); } + public override BoundStatement RewriteWhileStatement(BoundWhileStatement node) + { + // while cond + // body + // end + // + // | + // | + // V + // + // LabelLoop: + // gotoFalse cond LabelEnd + // body + // goto LabelLoop + // LabelEnd: + var labelLoop = GenerateLabel(); + var labelEnd = GenerateLabel(); + var result = Block( + node.Syntax, + // LabelLoop: + LabelStatement(node.Syntax, labelLoop), + // gotoFalse cond LabelEnd + GotoIfFalse( + node.Syntax, + node.Condition, + labelEnd), + // body + node.Body, + // goto LabelLoop + Goto(node.Syntax, labelLoop), + // LabelEnd: + LabelStatement(node.Syntax, labelEnd)); + return RewriteBlockStatement(result); + } + public override BoundStatement RewriteForStatement(BoundForStatement node) { // for i = expr @@ -102,7 +137,6 @@ namespace Parser.Lowering // #index = #index + 1 // goto LabelLoop // LabelEnd: - var builder = ImmutableArray.CreateBuilder(); var labelLoop = GenerateLabel(); var labelEnd = GenerateLabel(); var localArray = GenerateTypedLocalVariable(TypeSymbol.MObject); diff --git a/examples/helloworld/hello.m b/examples/helloworld/hello.m index e5c74d8..22a2fa2 100644 --- a/examples/helloworld/hello.m +++ b/examples/helloworld/hello.m @@ -5,6 +5,12 @@ f(x); x = 3; f(x); +i = 1; +while i <= 10 + disp(i); + i = i + 1; +end + % for i = 1:10 % disp(i); % end From 6f6bbfaa1c4199c3c371893df02bb94020d40449 Mon Sep 17 00:00:00 2001 From: Alexander Luzgarev Date: Mon, 20 Jul 2020 14:52:14 +0200 Subject: [PATCH 08/14] Reimplement For in terms of While --- Parser/Binding/BoundBinaryOperator.cs | 4 +- Parser/Binding/BoundBinaryOperatorKind.cs | 2 + Parser/Binding/BoundRoot.cs | 2 +- Parser/Emitting/Emitter.cs | 56 ++++++++++++---- Parser/Lowering/Lowerer.cs | 79 +++++++++++------------ Parser/Objects/MDoubleMatrix.cs | 48 ++++++++++++++ Parser/Objects/MDoubleNumber.cs | 43 ------------ examples/helloworld/hello.m | 13 ++-- 8 files changed, 140 insertions(+), 107 deletions(-) create mode 100644 Parser/Objects/MDoubleMatrix.cs diff --git a/Parser/Binding/BoundBinaryOperator.cs b/Parser/Binding/BoundBinaryOperator.cs index fc614b3..772a085 100644 --- a/Parser/Binding/BoundBinaryOperator.cs +++ b/Parser/Binding/BoundBinaryOperator.cs @@ -8,12 +8,12 @@ namespace Parser.Binding { new BoundBinaryOperator( TokenKind.LessToken, - BoundBinaryOperatorKind.Less, + BoundBinaryOperatorKind.LessInt, TypeSymbol.Int, TypeSymbol.Boolean), new BoundBinaryOperator( TokenKind.PlusToken, - BoundBinaryOperatorKind.Plus, + BoundBinaryOperatorKind.PlusInt, TypeSymbol.Int), }; diff --git a/Parser/Binding/BoundBinaryOperatorKind.cs b/Parser/Binding/BoundBinaryOperatorKind.cs index cf43bf7..e4bd95d 100644 --- a/Parser/Binding/BoundBinaryOperatorKind.cs +++ b/Parser/Binding/BoundBinaryOperatorKind.cs @@ -25,5 +25,7 @@ Tilde, Caret, DotCaret, + LessInt, + PlusInt, } } diff --git a/Parser/Binding/BoundRoot.cs b/Parser/Binding/BoundRoot.cs index 5810d05..9c62a87 100644 --- a/Parser/Binding/BoundRoot.cs +++ b/Parser/Binding/BoundRoot.cs @@ -437,7 +437,7 @@ namespace Parser.Binding public ImmutableArray Arguments { get; } public override BoundNodeKind Kind => BoundNodeKind.FunctionCallExpression; - public override TypeSymbol Type => throw new System.NotImplementedException(); + public override TypeSymbol Type => TypeSymbol.MObject; } public class BoundTypedFunctionCallExpression : BoundExpression diff --git a/Parser/Emitting/Emitter.cs b/Parser/Emitting/Emitter.cs index 16a5015..4d1d8dd 100644 --- a/Parser/Emitting/Emitter.cs +++ b/Parser/Emitting/Emitter.cs @@ -360,7 +360,6 @@ namespace Parser.Emitting private void EmitFunction(LoweredFunction function, MethodDefinition methodDefinition) { - Console.WriteLine($"Emitting function '{function.Name}'."); var ilProcessor = methodDefinition.Body.GetILProcessor(); _labels.Clear(); @@ -467,7 +466,7 @@ namespace Parser.Emitting private void EmitConditionalGoto(BoundConditionalGotoStatement node, ILProcessor ilProcessor) { - var condition = ConvertExpression(node.Condition, TypeSymbol.Boolean); + var condition = TryConvertExpression(node.Condition, TypeSymbol.Boolean); if (condition is null) { throw new Exception("Cannot cast a condition in GOTO to boolean."); @@ -600,10 +599,30 @@ namespace Parser.Emitting private void EmitBinaryOperationExpression(BoundBinaryOperationExpression node, ILProcessor ilProcessor) { - var method = _binaryOperations[node.Op.Kind]; - EmitExpression(node.Left, ilProcessor); - EmitExpression(node.Right, ilProcessor); - ilProcessor.Emit(OpCodes.Call, method); + if (_binaryOperations.TryGetValue(node.Op.Kind, out var method)) + { + EmitExpression(node.Left, ilProcessor); + EmitExpression(node.Right, ilProcessor); + ilProcessor.Emit(OpCodes.Call, method); + } else + { + if (node.Op.Kind == BoundBinaryOperatorKind.LessInt) + { + EmitExpression(node.Left, ilProcessor); + EmitExpression(node.Right, ilProcessor); + ilProcessor.Emit(OpCodes.Clt); + } + else if (node.Op.Kind == BoundBinaryOperatorKind.PlusInt) + { + EmitExpression(node.Left, ilProcessor); + EmitExpression(node.Right, ilProcessor); + ilProcessor.Emit(OpCodes.Add); + } + else + { + throw new Exception($"Binary operation '{node.Op.Kind}' not implemented."); + } + } } private void EmitAssignmentExpression(BoundAssignmentExpression node, ILProcessor ilProcessor) @@ -615,9 +634,11 @@ namespace Parser.Emitting _ => throw new Exception($"Assignment to lvalue of kind {node.Left.Kind} is not supported."), }; - var rightType = node.Right.Type; - - var rewrittenRight = ConvertExpression(node.Right, leftType); + var rewrittenRight = TryConvertExpression(node.Right, leftType); + if (rewrittenRight is null) + { + throw new Exception($"Cannot convert an expression of type '{node.Right.Type}' to '{leftType}'."); + } if (node.Left.Kind == BoundNodeKind.IdentifierNameExpression) { @@ -634,6 +655,7 @@ namespace Parser.Emitting { var typedVariableExpression = (BoundTypedVariableExpression)node.Left; EmitExpression(rewrittenRight, ilProcessor); + ilProcessor.Emit(OpCodes.Dup); ilProcessor.Emit(OpCodes.Stloc, _typedLocals[typedVariableExpression.Variable]); } else @@ -664,7 +686,7 @@ namespace Parser.Emitting ilProcessor.Emit(OpCodes.Ldstr, node.Value); } - private BoundExpression? ConvertExpression(BoundExpression expression, TypeSymbol targetType) + private BoundExpression? TryConvertExpression(BoundExpression expression, TypeSymbol targetType) { var conversion = Conversion.Classify(expression.Type, targetType); if (!conversion.Exists) @@ -680,7 +702,12 @@ namespace Parser.Emitting { return BoundNodeFactory.Conversion(expression.Syntax, targetType, expression); } + } + private BoundExpression ConvertExpression(BoundExpression expression, TypeSymbol targetType) + { + return TryConvertExpression(expression, targetType) + ?? throw new Exception($"Conversion from '{expression.Type}' to '{targetType}' failed."); } private BoundExpression RewriteFunctionCall(BoundFunctionCallExpression node, TypedFunctionSymbol function) @@ -696,7 +723,7 @@ namespace Parser.Emitting { var argument = node.Arguments[i]; var parameter = function.Parameters[i]; - var rewrittenArgument = ConvertExpression(argument, parameter.Type); + var rewrittenArgument = TryConvertExpression(argument, parameter.Type); if (rewrittenArgument is null) { throw new NotImplementedException($"Argument number {i + 1} of function '{function.Name}' expects {parameter.Type}, but got {argument.Type}, and no conversion exists."); @@ -753,9 +780,10 @@ namespace Parser.Emitting throw new Exception("Multi-dimensional array slicing is not supported."); } - var typedVariableExpression = (BoundTypedVariableExpression)node.Name; - EmitTypedVariableExpression(typedVariableExpression, ilProcessor); - EmitExpression(node.Arguments[0], ilProcessor); + var typedVariableExpression = ConvertExpression((BoundTypedVariableExpression)node.Name, TypeSymbol.MObject); + EmitExpression(typedVariableExpression, ilProcessor); + var indexExpression = ConvertExpression(node.Arguments[0], TypeSymbol.MObject); + EmitExpression(indexExpression, ilProcessor); ilProcessor.Emit(OpCodes.Call, _arraySliceReference); } else diff --git a/Parser/Lowering/Lowerer.cs b/Parser/Lowering/Lowerer.cs index 7950fb9..9912f8e 100644 --- a/Parser/Lowering/Lowerer.cs +++ b/Parser/Lowering/Lowerer.cs @@ -1,4 +1,5 @@ using Parser.Binding; +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -127,36 +128,23 @@ namespace Parser.Lowering // body // end // + // | + // | + // V + // // #array = expr // #length = len(#array) // #index = 0 - // i = #array(#index); - // LabelLoop: - // gotoFalse (#index < #length) LabelEnd - // body - // #index = #index + 1 - // goto LabelLoop - // LabelEnd: - var labelLoop = GenerateLabel(); - var labelEnd = GenerateLabel(); + // while #index < #length + // i = #array(#index); + // body + // #index = #index + 1 var localArray = GenerateTypedLocalVariable(TypeSymbol.MObject); var localLength = GenerateTypedLocalVariable(TypeSymbol.Int); var localIndex = GenerateTypedLocalVariable(TypeSymbol.Int); - return RewriteBlockStatement(Block( + var whileBody = Block( node.Syntax, - // #array = expr - TypedVariableDeclaration(node.Syntax, localArray, node.LoopedExpression), - // #length = len(#array) - TypedVariableDeclaration( - node.Syntax, - localLength, - FunctionCall( - node.Syntax, - Identifier(node.Syntax, "len"), - new[] { (BoundExpression)TypedVariableExpression(node.Syntax, localArray) }.ToImmutableArray())), - // #index = 0 - TypedVariableDeclaration(node.Syntax, localIndex, NumberDoubleLiteral(node.Syntax, 0.0)), - // i = #array(#index); + // i = #array(#index); ExpressionStatement( node.Syntax, Assignment( @@ -167,18 +155,7 @@ namespace Parser.Lowering TypedVariableExpression(node.Syntax, localArray), new[] { (BoundExpression)TypedVariableExpression(node.Syntax, localIndex) }.ToImmutableArray())), discardResult: true), - // LabelLoop: - LabelStatement(node.Syntax, labelLoop), - // gotoFalse (#index < #length) LabelEnd - GotoIfFalse( - node.Syntax, - BinaryOperation( - node.Syntax, - TypedVariableExpression(node.Syntax, localIndex), - BoundBinaryOperator.GetOperator(TokenKind.LessToken, TypeSymbol.Int, TypeSymbol.Int)!, - TypedVariableExpression(node.Syntax, localLength)), - labelEnd), - // body + // body node.Body, // #index = #index + 1; ExpressionStatement( @@ -191,11 +168,33 @@ namespace Parser.Lowering TypedVariableExpression(node.Syntax, localIndex), BoundBinaryOperator.GetOperator(TokenKind.PlusToken, TypeSymbol.Int, TypeSymbol.Int)!, NumberIntLiteral(node.Syntax, 1))), - discardResult: true), - // goto LabelLoop - Goto(node.Syntax, labelLoop), - // LabelEnd: - LabelStatement(node.Syntax, labelEnd))); + discardResult: true)); + + var result = Block( + node.Syntax, + // #array = expr + TypedVariableDeclaration(node.Syntax, localArray, node.LoopedExpression), + // #length = len(#array) + TypedVariableDeclaration( + node.Syntax, + localLength, + FunctionCall( + node.Syntax, + Identifier(node.Syntax, "len"), + new[] { (BoundExpression)TypedVariableExpression(node.Syntax, localArray) }.ToImmutableArray())), + // #index = 0 + TypedVariableDeclaration(node.Syntax, localIndex, NumberIntLiteral(node.Syntax, 0)), + // while #index < #length + // whileBody + WhileStatement( + node.Syntax, + BinaryOperation( + node.Syntax, + TypedVariableExpression(node.Syntax, localIndex), + BoundBinaryOperator.GetOperator(TokenKind.LessToken, TypeSymbol.Int, TypeSymbol.Int)!, + TypedVariableExpression(node.Syntax, localLength)), + whileBody)); + return RewriteBlockStatement(result); } public static BoundBlockStatement Lower(BoundStatement statement) diff --git a/Parser/Objects/MDoubleMatrix.cs b/Parser/Objects/MDoubleMatrix.cs new file mode 100644 index 0000000..6fa38e1 --- /dev/null +++ b/Parser/Objects/MDoubleMatrix.cs @@ -0,0 +1,48 @@ +using System.Text; + +namespace Parser.Objects +{ + public class MDoubleMatrix : MObject + { + private MDoubleMatrix(double[,] matrix) + { + Matrix = matrix; + RowCount = matrix.GetLength(0); + ColumnCount = matrix.GetLength(1); + } + + public double[,] Matrix { get; } + + public int RowCount { get; } + + public int ColumnCount { get; } + + public ref double this[int i, int j] => ref Matrix[i, j]; + + public ref double this[int i] => ref Matrix[i % RowCount, i / RowCount]; + + public override string ToString() + { + var sb = new StringBuilder(); + for (var i = 0; i < RowCount; i++) + { + for (var j = 0; j < ColumnCount; j++) + { + if (j > 0) + { + sb.Append(' '); + } + sb.Append(Matrix[i, j]); + } + + sb.AppendLine(); + } + return sb.ToString(); + } + + public static MDoubleMatrix Create(double[,] matrix) + { + return new MDoubleMatrix(matrix); + } + } +} \ No newline at end of file diff --git a/Parser/Objects/MDoubleNumber.cs b/Parser/Objects/MDoubleNumber.cs index b7bd105..bb7f76f 100644 --- a/Parser/Objects/MDoubleNumber.cs +++ b/Parser/Objects/MDoubleNumber.cs @@ -1,50 +1,7 @@ using System.Globalization; -using System.Text; namespace Parser.Objects { - public class MDoubleMatrix : MObject - { - private MDoubleMatrix(double[,] matrix) - { - Matrix = matrix; - } - - public double[,] Matrix { get; } - - public int RowCount { get; } - - public int ColumnCount { get; } - - public ref double this[int i, int j] => ref Matrix[i, j]; - - public ref double this[int i] => ref Matrix[i % RowCount, i / RowCount]; - - public override string ToString() - { - var sb = new StringBuilder(); - for (var i = 0; i < RowCount; i++) - { - for (var j = 0; j < ColumnCount; j++) - { - if (j > 0) - { - sb.Append(' '); - } - sb.Append(Matrix[i, j]); - } - - sb.AppendLine(); - } - return sb.ToString(); - } - - public static MDoubleMatrix Create(double[,] matrix) - { - return new MDoubleMatrix(matrix); - } - } - public class MDoubleNumber : MObject { private MDoubleNumber(double value) diff --git a/examples/helloworld/hello.m b/examples/helloworld/hello.m index 22a2fa2..1a46c7e 100644 --- a/examples/helloworld/hello.m +++ b/examples/helloworld/hello.m @@ -5,16 +5,15 @@ f(x); x = 3; f(x); -i = 1; -while i <= 10 - disp(i); - i = i + 1; -end - -% for i = 1:10 +% i = 1; +% while i <= 10 % disp(i); +% i = i + 1; % end +for i = 1:10 + disp(i); +end function f(x) disp('X was'); From 35840307680afae282dd3cb1e52067d490d45444 Mon Sep 17 00:00:00 2001 From: Alexander Luzgarev Date: Thu, 23 Jul 2020 16:12:30 +0200 Subject: [PATCH 09/14] Implement FullSpan on tokens & nodes --- Parser.Tests/MParserShould.cs | 28 +++++++++++++++++++++++++--- Parser.Tests/Parser.Tests.csproj | 1 + Parser/SyntaxNode.cs | 11 ++++++++--- Parser/SyntaxToken.cs | 6 +++++- 4 files changed, 39 insertions(+), 7 deletions(-) diff --git a/Parser.Tests/MParserShould.cs b/Parser.Tests/MParserShould.cs index 249a23b..a586436 100644 --- a/Parser.Tests/MParserShould.cs +++ b/Parser.Tests/MParserShould.cs @@ -1,4 +1,5 @@ using Xunit; +using FluentAssertions; namespace Parser.Tests { @@ -95,7 +96,7 @@ namespace Parser.Tests [Fact] public void ProvidePosition() { - var text = "2 + 3"; + var text = "% Comment\n 2 + 3"; var sut = GetSut(text); var actual = sut.Parse(); var statement = actual.Root.Body.Statements[0].AsNode() as ExpressionStatementSyntaxNode; @@ -104,8 +105,29 @@ namespace Parser.Tests var operation = expression.Operation; var rhs = expression.Rhs; Assert.Equal(0, lhs.Position); - Assert.Equal(2, operation.Position); - Assert.Equal(4, rhs.Position); + Assert.Equal(14, operation.Position); + Assert.Equal(16, rhs.Position); + } + + [Fact] + public void ProvideFullSpan() + { + var text = "% Comment\n 2 + 3"; + var sut = GetSut(text); + var actual = sut.Parse(); + var statement = actual.Root.Body.Statements[0].AsNode() as ExpressionStatementSyntaxNode; + var expression = statement!.Expression as BinaryOperationExpressionSyntaxNode; + var lhs = expression!.Lhs; + var operation = expression.Operation; + var rhs = expression.Rhs; + expression.FullSpan.Start.Should().Be(0); + expression.FullSpan.End.Should().Be(17); + lhs.FullSpan.Start.Should().Be(0); + lhs.FullSpan.End.Should().Be(14); + operation.FullSpan.Start.Should().Be(14); + operation.FullSpan.End.Should().Be(16); + rhs.FullSpan.Start.Should().Be(16); + rhs.FullSpan.End.Should().Be(17); } } } \ No newline at end of file diff --git a/Parser.Tests/Parser.Tests.csproj b/Parser.Tests/Parser.Tests.csproj index 2e800c8..c321274 100644 --- a/Parser.Tests/Parser.Tests.csproj +++ b/Parser.Tests/Parser.Tests.csproj @@ -6,6 +6,7 @@ 8.0 + diff --git a/Parser/SyntaxNode.cs b/Parser/SyntaxNode.cs index e7eb31e..009acfd 100644 --- a/Parser/SyntaxNode.cs +++ b/Parser/SyntaxNode.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using Parser.Internal; +using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -13,6 +14,7 @@ namespace Parser _parent = parent; _green = green; Position = position; + FullSpan = new TextSpan(Position, green.FullWidth); } private protected SyntaxNode(Internal.GreenNode green, int position) @@ -20,6 +22,7 @@ namespace Parser _parent = this; _green = green; Position = position; + FullSpan = new TextSpan(Position, green.FullWidth); } public TokenKind Kind => _green.Kind; @@ -35,6 +38,10 @@ namespace Parser public int Position { get; } + public TextSpan FullSpan { get; } + + public int FullWidth => _green.FullWidth; + internal int GetChildPosition(int slot) { var result = Position; @@ -75,8 +82,6 @@ namespace Parser public virtual string FullText => _green.FullText; - public int FullWidth => _green.FullWidth; - public virtual IReadOnlyList LeadingTrivia { get diff --git a/Parser/SyntaxToken.cs b/Parser/SyntaxToken.cs index 2b68968..7b1cc67 100644 --- a/Parser/SyntaxToken.cs +++ b/Parser/SyntaxToken.cs @@ -1,4 +1,5 @@ -using System; +using Parser.Internal; +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -22,6 +23,7 @@ namespace Parser _parent = parent; _token = token ?? throw new ArgumentNullException(nameof(token)); Position = position; + FullSpan = new TextSpan(Position, token.FullWidth); } public SyntaxNode Parent => _parent; @@ -29,6 +31,8 @@ namespace Parser public int Position { get; } + public TextSpan FullSpan { get; } + public object? Value => _token.GetValue(); public bool Equals(SyntaxToken other) From e60cc1bb739b98c2117fdf6978b06ae978581c3c Mon Sep 17 00:00:00 2001 From: Alexander Luzgarev Date: Thu, 23 Jul 2020 18:51:09 +0200 Subject: [PATCH 10/14] Implement Span on tokens & nodes --- Parser.Tests/MParserShould.cs | 21 ++++++ Parser/Internal/GreenNode.cs | 8 +-- Parser/Internal/MLexerGreen.cs | 4 +- Parser/Internal/MParserGreen.cs | 25 ++++--- Parser/Internal/SyntaxList.cs | 2 + Parser/Internal/SyntaxList`1.cs | 12 +++- Parser/Internal/SyntaxNode.cs | 4 +- Parser/Internal/SyntaxToken.cs | 118 ++++++++++++++++---------------- Parser/Internal/SyntaxTrivia.cs | 9 ++- Parser/Internal/TokenFactory.cs | 52 +++++++++++++- Parser/SyntaxNavigator.cs | 27 ++++++++ Parser/SyntaxNode.cs | 38 +++++++--- Parser/SyntaxToken.cs | 21 ++++-- Parser/SyntaxTriviaList.cs | 52 ++++++++++++++ Parser/SyntaxVisitor.cs | 5 ++ Semantics/GetClass.cs | 8 +-- 16 files changed, 307 insertions(+), 99 deletions(-) create mode 100644 Parser/SyntaxNavigator.cs create mode 100644 Parser/SyntaxTriviaList.cs diff --git a/Parser.Tests/MParserShould.cs b/Parser.Tests/MParserShould.cs index a586436..7aa648d 100644 --- a/Parser.Tests/MParserShould.cs +++ b/Parser.Tests/MParserShould.cs @@ -129,5 +129,26 @@ namespace Parser.Tests rhs.FullSpan.Start.Should().Be(16); rhs.FullSpan.End.Should().Be(17); } + + [Fact] + public void ProvideSpan() + { + var text = "% Comment\n 2 + 3"; + var sut = GetSut(text); + var actual = sut.Parse(); + var statement = actual.Root.Body.Statements[0].AsNode() as ExpressionStatementSyntaxNode; + var expression = statement!.Expression as BinaryOperationExpressionSyntaxNode; + var lhs = expression!.Lhs; + var operation = expression.Operation; + var rhs = expression.Rhs; + expression.Span.Start.Should().Be(12); + expression.Span.End.Should().Be(17); + lhs.Span.Start.Should().Be(12); + lhs.Span.End.Should().Be(13); + operation.Span.Start.Should().Be(14); + operation.Span.End.Should().Be(15); + rhs.Span.Start.Should().Be(16); + rhs.Span.End.Should().Be(17); + } } } \ No newline at end of file diff --git a/Parser/Internal/GreenNode.cs b/Parser/Internal/GreenNode.cs index 1ee5b19..9a07bc0 100644 --- a/Parser/Internal/GreenNode.cs +++ b/Parser/Internal/GreenNode.cs @@ -191,11 +191,11 @@ namespace Parser.Internal } } - public virtual IReadOnlyList LeadingTrivia => GetFirstTerminal()?.LeadingTriviaCore ?? new List(); - public virtual IReadOnlyList TrailingTrivia => GetLastTerminal()?.TrailingTriviaCore ?? new List(); + public virtual GreenNode? LeadingTrivia => GetFirstTerminal()?.LeadingTriviaCore; + public virtual GreenNode? TrailingTrivia => GetLastTerminal()?.TrailingTriviaCore; - public abstract IReadOnlyList LeadingTriviaCore { get; } - public abstract IReadOnlyList TrailingTriviaCore { get; } + public virtual GreenNode? LeadingTriviaCore => null; + public virtual GreenNode? TrailingTriviaCore => null; public virtual string FullText { diff --git a/Parser/Internal/MLexerGreen.cs b/Parser/Internal/MLexerGreen.cs index 5c8a170..a1f592c 100644 --- a/Parser/Internal/MLexerGreen.cs +++ b/Parser/Internal/MLexerGreen.cs @@ -493,7 +493,7 @@ namespace Parser.Internal if (TokensSinceNewLine == 1 && !TokenStack.Any() && LastToken.Kind == TokenKind.IdentifierToken - && LastToken.TrailingTrivia.Any() + && LastToken.TrailingTrivia is not null && character != '=' && character != '(' && !SyntaxFacts.Keywords.Contains(LastToken.Text)) @@ -773,7 +773,7 @@ namespace Parser.Internal || LastToken.Kind == TokenKind.CloseSquareBracketToken || LastToken.Kind == TokenKind.IdentifierToken)) { - if (LastToken.TrailingTrivia.Count == 0 && leadingTrivia.Count == 0) + if (LastToken.TrailingTrivia is null && leadingTrivia.Count == 0) { Window.ConsumeChar(); tokenInfo.Kind = TokenKind.ApostropheToken; diff --git a/Parser/Internal/MParserGreen.cs b/Parser/Internal/MParserGreen.cs index a58011a..79df93a 100644 --- a/Parser/Internal/MParserGreen.cs +++ b/Parser/Internal/MParserGreen.cs @@ -348,7 +348,7 @@ namespace Parser.Internal switch (token.Kind) { case TokenKind.OpenBraceToken: // cell array element access - if (options.ParsingArrayElements && expression.TrailingTrivia.Any()) + if (options.ParsingArrayElements && expression.TrailingTrivia is not null) { return expression; } @@ -363,7 +363,7 @@ namespace Parser.Internal ); break; case TokenKind.OpenParenthesisToken: // function call - if (options.ParsingArrayElements && expression.TrailingTrivia.Any()) + if (options.ParsingArrayElements && expression.TrailingTrivia is not null) { return expression; } @@ -401,7 +401,7 @@ namespace Parser.Internal case TokenKind.UnquotedStringLiteralToken: return ParseCommandExpression(expression); case TokenKind.AtToken: - if (expression.TrailingTrivia.Any()) + if (expression.TrailingTrivia is not null) { return expression; } @@ -435,7 +435,7 @@ namespace Parser.Internal private ClassInvokationExpressionSyntaxNode ParseBaseClassInvokation(ExpressionSyntaxNode expression) { if (expression is IdentifierNameExpressionSyntaxNode methodName - && !expression.TrailingTrivia.Any()) + && expression.TrailingTrivia is null) { var atToken = EatToken(); var baseClassNameWithArguments = ParseExpression(); @@ -446,7 +446,7 @@ namespace Parser.Internal return Factory.ClassInvokationExpressionSyntax(methodName, atToken, baseClassNameWithArguments); } if (expression is MemberAccessExpressionSyntaxNode memberAccess - && !expression.TrailingTrivia.Any()) + && expression.TrailingTrivia is null) { var atToken = EatToken(); var baseClassNameWithArguments = ParseExpression(); @@ -530,7 +530,7 @@ namespace Parser.Internal var builder = new SyntaxListBuilder(); builder.Add(firstName); while (CurrentToken.Kind == TokenKind.DotToken - && !lastToken.TrailingTrivia.Any()) + && lastToken.TrailingTrivia is null) { var dot = EatToken(); builder.Add(dot); @@ -856,11 +856,18 @@ namespace Parser.Internal return Factory.ExpressionStatementSyntax(expression, semicolon: null); } - private bool TriviaContainsNewLine(IReadOnlyList trivia) + private bool TriviaContainsNewLine(GreenNode? trivia) { - foreach(var t in trivia) + var triviaList = trivia as SyntaxList; + if (triviaList is null) { - if (t.Text.Contains('\n')) + return false; + } + + for (var i = 0; i < triviaList.Length; i++) + { + var text = triviaList[i].Text; + if (text.Contains('\n')) { return true; } diff --git a/Parser/Internal/SyntaxList.cs b/Parser/Internal/SyntaxList.cs index 5a5fbd7..3966071 100644 --- a/Parser/Internal/SyntaxList.cs +++ b/Parser/Internal/SyntaxList.cs @@ -58,5 +58,7 @@ namespace Parser.Internal { return new SyntaxList(_elements, diagnostics); } + + public int Length => _elements.Length; } } \ No newline at end of file diff --git a/Parser/Internal/SyntaxList`1.cs b/Parser/Internal/SyntaxList`1.cs index 21ce891..b3b4dad 100644 --- a/Parser/Internal/SyntaxList`1.cs +++ b/Parser/Internal/SyntaxList`1.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System.Collections.Generic; +using System.Linq; namespace Parser.Internal { @@ -31,11 +32,18 @@ namespace Parser.Internal return (T)_list.GetListSlot(i); } + public T this[int i] => (T)_list.GetListSlot(i); + public static SyntaxList List(T[] elements) { return new SyntaxList(elements); } + public static SyntaxList List(IReadOnlyList elements) + { + return new SyntaxList(elements.ToArray()); + } + public static SyntaxList Empty => new SyntaxList(new T[] { }); public override bool IsList => true; @@ -49,5 +57,7 @@ namespace Parser.Internal { return new SyntaxList(_list._elements.Select(x => (T)x).ToArray(), diagnostics); } + + public int Length => _list.Length; } } \ No newline at end of file diff --git a/Parser/Internal/SyntaxNode.cs b/Parser/Internal/SyntaxNode.cs index 2b0a450..999f06d 100644 --- a/Parser/Internal/SyntaxNode.cs +++ b/Parser/Internal/SyntaxNode.cs @@ -53,8 +53,8 @@ namespace Parser.Internal public override string FullText => CollectFullText(); - public override IReadOnlyList LeadingTriviaCore => throw new NotImplementedException(); - public override IReadOnlyList TrailingTriviaCore => throw new NotImplementedException(); + public override GreenNode? LeadingTriviaCore => throw new NotImplementedException(); + public override GreenNode? TrailingTriviaCore => throw new NotImplementedException(); } internal abstract class StatementSyntaxNode : SyntaxNode diff --git a/Parser/Internal/SyntaxToken.cs b/Parser/Internal/SyntaxToken.cs index 22ff7ac..6bc4ce2 100644 --- a/Parser/Internal/SyntaxToken.cs +++ b/Parser/Internal/SyntaxToken.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; namespace Parser.Internal { @@ -16,66 +15,66 @@ namespace Parser.Internal public SyntaxTokenWithTrivia( TokenKind kind, string text, - IReadOnlyList leadingTrivia, - IReadOnlyList trailingTrivia) : base(kind) + GreenNode? leadingTrivia, + GreenNode? trailingTrivia) : base(kind) { _text = text; LeadingTriviaCore = leadingTrivia; TrailingTriviaCore = trailingTrivia; - _fullWidth = (leadingTrivia?.Sum(t => t.FullWidth) ?? 0) + (text?.Length ?? 0) + (trailingTrivia?.Sum(t => t.FullWidth) ?? 0); + _fullWidth = (leadingTrivia?.FullWidth ?? 0) + (_text?.Length ?? 0) + (trailingTrivia?.FullWidth ?? 0); } public SyntaxTokenWithTrivia( TokenKind kind, - IReadOnlyList leadingTrivia, - IReadOnlyList trailingTrivia) : base(kind) + GreenNode? leadingTrivia, + GreenNode? trailingTrivia) : base(kind) { _text = base.Text; LeadingTriviaCore = leadingTrivia; TrailingTriviaCore = trailingTrivia; - _fullWidth = (leadingTrivia?.Sum(t => t.FullWidth) ?? 0) + (_text?.Length ?? 0) + (trailingTrivia?.Sum(t => t.FullWidth) ?? 0); + _fullWidth = (leadingTrivia?.FullWidth ?? 0) + (_text?.Length ?? 0) + (trailingTrivia?.FullWidth ?? 0); } public SyntaxTokenWithTrivia( TokenKind kind, string text, - IReadOnlyList leadingTrivia, - IReadOnlyList trailingTrivia, + GreenNode? leadingTrivia, + GreenNode? trailingTrivia, TokenDiagnostic[] diagnostics) : base(kind, diagnostics) { _text = text; LeadingTriviaCore = leadingTrivia; TrailingTriviaCore = trailingTrivia; - _fullWidth = (leadingTrivia?.Sum(t => t.FullWidth) ?? 0) + (text?.Length ?? 0) + (trailingTrivia?.Sum(t => t.FullWidth) ?? 0); + _fullWidth = (leadingTrivia?.FullWidth ?? 0) + (_text?.Length ?? 0) + (trailingTrivia?.FullWidth ?? 0); } public SyntaxTokenWithTrivia( TokenKind kind, - IReadOnlyList leadingTrivia, - IReadOnlyList trailingTrivia, + GreenNode? leadingTrivia, + GreenNode? trailingTrivia, TokenDiagnostic[] diagnostics) : base(kind, diagnostics) { _text = base.Text; LeadingTriviaCore = leadingTrivia; TrailingTriviaCore = trailingTrivia; - _fullWidth = (leadingTrivia?.Sum(t => t.FullWidth) ?? 0) + (_text?.Length ?? 0) + (trailingTrivia?.Sum(t => t.FullWidth) ?? 0); + _fullWidth = (leadingTrivia?.FullWidth ?? 0) + (_text?.Length ?? 0) + (trailingTrivia?.FullWidth ?? 0); } public override void WriteTokenTo(TextWriter writer, bool leading, bool trailing) { - if (leading) + if (leading && LeadingTrivia is SyntaxList leadingTrivia) { - foreach (var trivia in LeadingTrivia) + for (var i = 0; i < leadingTrivia.Length; i++) { - writer.Write(trivia.Text); + leadingTrivia[i].WriteTriviaTo(writer); } } base.WriteTokenTo(writer, leading, trailing); - if (trailing) + if (trailing && TrailingTrivia is SyntaxList trailingTrivia) { - foreach (var trivia in TrailingTrivia) + for (var i = 0; i < trailingTrivia.Length; i++) { - writer.Write(trivia.Text); + trailingTrivia[i].WriteTriviaTo(writer); } } } @@ -85,9 +84,9 @@ namespace Parser.Internal return new SyntaxTokenWithTrivia(Kind, _text, LeadingTrivia, TrailingTrivia, diagnostics); } - public override IReadOnlyList LeadingTriviaCore { get; } + public override GreenNode? LeadingTriviaCore { get; } - public override IReadOnlyList TrailingTriviaCore { get; } + public override GreenNode? TrailingTriviaCore { get; } } internal class SyntaxTokenWithValue : SyntaxToken @@ -138,46 +137,47 @@ namespace Parser.Internal TokenKind kind, string text, T value, - IReadOnlyList leadingTrivia, - IReadOnlyList trailingTrivia) : base(kind, text, value) + GreenNode? leadingTrivia, + GreenNode? trailingTrivia) + : base(kind, text, value) { LeadingTriviaCore = leadingTrivia; TrailingTriviaCore = trailingTrivia; - _fullWidth = (leadingTrivia?.Sum(t => t.FullWidth) ?? 0) + (text?.Length ?? 0) + (trailingTrivia?.Sum(t => t.FullWidth) ?? 0); + _fullWidth = (leadingTrivia?.FullWidth ?? 0) + (text?.Length ?? 0) + (trailingTrivia?.FullWidth ?? 0); } public SyntaxTokenWithValueAndTrivia( TokenKind kind, string text, T value, - IReadOnlyList leadingTrivia, - IReadOnlyList trailingTrivia, + GreenNode? leadingTrivia, + GreenNode? trailingTrivia, TokenDiagnostic[] diagnostics) : base(kind, text, value, diagnostics) { LeadingTriviaCore = leadingTrivia; TrailingTriviaCore = trailingTrivia; - _fullWidth = (leadingTrivia?.Sum(t => t.FullWidth) ?? 0) + (text?.Length ?? 0) + (trailingTrivia?.Sum(t => t.FullWidth) ?? 0); + _fullWidth = (leadingTrivia?.FullWidth ?? 0) + (text?.Length ?? 0) + (trailingTrivia?.FullWidth ?? 0); } - public override IReadOnlyList LeadingTriviaCore { get; } + public override GreenNode? LeadingTriviaCore { get; } - public override IReadOnlyList TrailingTriviaCore { get; } + public override GreenNode? TrailingTriviaCore { get; } public override void WriteTokenTo(TextWriter writer, bool leading, bool trailing) { - if (leading) + if (leading && LeadingTrivia is SyntaxList leadingTrivia) { - foreach (var trivia in LeadingTrivia) + for (var i = 0; i < leadingTrivia.Length; i++) { - writer.Write(trivia.Text); + leadingTrivia[i].WriteTriviaTo(writer); } } base.WriteTokenTo(writer, leading, trailing); - if (trailing) + if (trailing && TrailingTrivia is SyntaxList trailingTrivia) { - foreach (var trivia in TrailingTrivia) + for (var i = 0; i < trailingTrivia.Length; i++) { - writer.Write(trivia.Text); + trailingTrivia[i].WriteTriviaTo(writer); } } } @@ -220,49 +220,49 @@ namespace Parser.Internal internal class SyntaxIdentifierWithTrivia : SyntaxIdentifier { - private readonly IReadOnlyList _leadingTrivia; - private readonly IReadOnlyList _trailingTrivia; + private readonly GreenNode? _leadingTrivia; + private readonly GreenNode? _trailingTrivia; - public override IReadOnlyList LeadingTriviaCore => _leadingTrivia; - public override IReadOnlyList TrailingTriviaCore => _trailingTrivia; + public override GreenNode? LeadingTriviaCore => _leadingTrivia; + public override GreenNode? TrailingTriviaCore => _trailingTrivia; public SyntaxIdentifierWithTrivia( string text, - IReadOnlyList leadingTrivia, - IReadOnlyList trailingTrivia - ) : base(text) + GreenNode? leadingTrivia, + GreenNode? trailingTrivia) + : base(text) { _leadingTrivia = leadingTrivia; _trailingTrivia = trailingTrivia; - _fullWidth = (leadingTrivia?.Sum(t => t.FullWidth) ?? 0) + (text?.Length ?? 0) + (trailingTrivia?.Sum(t => t.FullWidth) ?? 0); + _fullWidth = (leadingTrivia?.FullWidth ?? 0) + (text?.Length ?? 0) + (trailingTrivia?.FullWidth ?? 0); } public SyntaxIdentifierWithTrivia( string text, - IReadOnlyList leadingTrivia, - IReadOnlyList trailingTrivia, + GreenNode? leadingTrivia, + GreenNode? trailingTrivia, TokenDiagnostic[] diagnostics) : base(text, diagnostics) { _leadingTrivia = leadingTrivia; _trailingTrivia = trailingTrivia; - _fullWidth = (leadingTrivia?.Sum(t => t.FullWidth) ?? 0) + (text?.Length ?? 0) + (trailingTrivia?.Sum(t => t.FullWidth) ?? 0); + _fullWidth = (leadingTrivia?.FullWidth ?? 0) + (text?.Length ?? 0) + (trailingTrivia?.FullWidth ?? 0); } public override void WriteTokenTo(TextWriter writer, bool leading, bool trailing) { - if (leading) + if (leading && LeadingTrivia is SyntaxList leadingTrivia) { - foreach (var trivia in LeadingTrivia) + for (var i = 0; i < leadingTrivia.Length; i++) { - writer.Write(trivia.Text); + leadingTrivia[i].WriteTriviaTo(writer); } } base.WriteTokenTo(writer, leading, trailing); - if (trailing) + if (trailing && TrailingTrivia is SyntaxList trailingTrivia) { - foreach (var trivia in TrailingTrivia) + for (var i = 0; i < trailingTrivia.Length; i++) { - writer.Write(trivia.Text); + trailingTrivia[i].WriteTriviaTo(writer); } } } @@ -280,8 +280,8 @@ namespace Parser.Internal { public MissingTokenWithTrivia( TokenKind kind, - IReadOnlyList leadingTrivia, - IReadOnlyList trailingTrivia + GreenNode? leadingTrivia, + GreenNode? trailingTrivia ) : base(kind, leadingTrivia, trailingTrivia) { _isMissing = true; @@ -289,8 +289,8 @@ namespace Parser.Internal public MissingTokenWithTrivia( TokenKind kind, - IReadOnlyList leadingTrivia, - IReadOnlyList trailingTrivia, + GreenNode? leadingTrivia, + GreenNode? trailingTrivia, TokenDiagnostic[] diagnostics) : base(kind, leadingTrivia, trailingTrivia, diagnostics) { _isMissing = true; @@ -312,7 +312,7 @@ namespace Parser.Internal { } - internal static SyntaxToken NoneToken => new MissingTokenWithTrivia(TokenKind.None, s_EmptySyntaxTriviaList, s_EmptySyntaxTriviaList); + internal static SyntaxToken NoneToken => new MissingTokenWithTrivia(TokenKind.None, null, null); public virtual object? Value => null; @@ -320,8 +320,8 @@ namespace Parser.Internal public virtual int Width => Text.Length; - public override IReadOnlyList LeadingTriviaCore => s_EmptySyntaxTriviaList; - public override IReadOnlyList TrailingTriviaCore => s_EmptySyntaxTriviaList; + public override GreenNode? LeadingTriviaCore => null; + public override GreenNode? TrailingTriviaCore => null; public override GreenNode? GetSlot(int i) { diff --git a/Parser/Internal/SyntaxTrivia.cs b/Parser/Internal/SyntaxTrivia.cs index 277bc5a..e577bb3 100644 --- a/Parser/Internal/SyntaxTrivia.cs +++ b/Parser/Internal/SyntaxTrivia.cs @@ -40,12 +40,17 @@ namespace Parser.Internal writer.Write(_text); } + public override void WriteTokenTo(TextWriter writer, bool leading, bool trailing) + { + writer.Write(_text); + } + public override GreenNode SetDiagnostics(TokenDiagnostic[] diagnostics) { return new SyntaxTrivia(Kind, _text, diagnostics); } - public override IReadOnlyList LeadingTriviaCore => new List(); - public override IReadOnlyList TrailingTriviaCore => new List(); + public override GreenNode? LeadingTriviaCore => null; + public override GreenNode? TrailingTriviaCore => null; } } \ No newline at end of file diff --git a/Parser/Internal/TokenFactory.cs b/Parser/Internal/TokenFactory.cs index 1281663..d3d28ee 100644 --- a/Parser/Internal/TokenFactory.cs +++ b/Parser/Internal/TokenFactory.cs @@ -13,6 +13,16 @@ namespace Parser.Internal TokenKind kind, IReadOnlyList leadingTrivia, IReadOnlyList trailingTrivia) + { + var leading = leadingTrivia.Count > 0 ? SyntaxList.List(leadingTrivia) : null; + var trailing = trailingTrivia.Count > 0 ? SyntaxList.List(trailingTrivia) : null; + return new SyntaxToken.SyntaxTokenWithTrivia(kind, leading, trailing); + } + + public static SyntaxToken CreateTokenWithTrivia( + TokenKind kind, + GreenNode? leadingTrivia, + GreenNode? trailingTrivia) { return new SyntaxToken.SyntaxTokenWithTrivia(kind, leadingTrivia, trailingTrivia); } @@ -21,6 +31,16 @@ namespace Parser.Internal string text, IReadOnlyList leadingTrivia, IReadOnlyList trailingTrivia) + { + var leading = leadingTrivia.Count > 0 ? SyntaxList.List(leadingTrivia) : null; + var trailing = trailingTrivia.Count > 0 ? SyntaxList.List(trailingTrivia) : null; + return new SyntaxToken.SyntaxIdentifierWithTrivia(text, leading, trailing); + } + + public static SyntaxToken CreateIdentifier( + string text, + GreenNode? leadingTrivia, + GreenNode? trailingTrivia) { return new SyntaxToken.SyntaxIdentifierWithTrivia(text, leadingTrivia, trailingTrivia); } @@ -31,6 +51,18 @@ namespace Parser.Internal T value, IReadOnlyList leadingTrivia, IReadOnlyList trailingTrivia) + { + var leading = leadingTrivia.Count > 0 ? SyntaxList.List(leadingTrivia) : null; + var trailing = trailingTrivia.Count > 0 ? SyntaxList.List(trailingTrivia) : null; + return new SyntaxToken.SyntaxTokenWithValueAndTrivia(kind, text, value, leading, trailing); + } + + public static SyntaxToken CreateTokenWithValueAndTrivia( + TokenKind kind, + string text, + T value, + GreenNode? leadingTrivia, + GreenNode? trailingTrivia) { return new SyntaxToken.SyntaxTokenWithValueAndTrivia(kind, text, value, leadingTrivia, trailingTrivia); } @@ -40,6 +72,22 @@ namespace Parser.Internal string value, IReadOnlyList leadingTrivia, IReadOnlyList trailingTrivia) + { + var leading = leadingTrivia.Count > 0 ? SyntaxList.List(leadingTrivia) : null; + var trailing = trailingTrivia.Count > 0 ? SyntaxList.List(trailingTrivia) : null; + return new SyntaxToken.SyntaxTokenWithValueAndTrivia( + TokenKind.UnquotedStringLiteralToken, + text, + value, + leading, + trailing); + } + + public static SyntaxToken CreateUnquotedStringLiteral( + string text, + string value, + GreenNode? leadingTrivia, + GreenNode? trailingTrivia) { return new SyntaxToken.SyntaxTokenWithValueAndTrivia( TokenKind.UnquotedStringLiteralToken, @@ -54,7 +102,9 @@ namespace Parser.Internal IReadOnlyList? leadingTrivia, IReadOnlyList? trailingTrivia) { - return new SyntaxToken.MissingTokenWithTrivia(kind, leadingTrivia ?? SyntaxToken.s_EmptySyntaxTriviaList, trailingTrivia ?? SyntaxToken.s_EmptySyntaxTriviaList); + var leading = (leadingTrivia is { } l && l.Count > 0) ? SyntaxList.List(leadingTrivia) : null; + var trailing = (trailingTrivia is { } c && c.Count > 0) ? SyntaxList.List(trailingTrivia) : null; + return new SyntaxToken.MissingTokenWithTrivia(kind, leading, trailing); } } } \ No newline at end of file diff --git a/Parser/SyntaxNavigator.cs b/Parser/SyntaxNavigator.cs new file mode 100644 index 0000000..5b7c7ad --- /dev/null +++ b/Parser/SyntaxNavigator.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; + +namespace Parser +{ + public class SyntaxNavigator + { + public static SyntaxNavigator Singleton = new SyntaxNavigator(); + + public IEnumerable EnumerateTokens(SyntaxNode node) + { + foreach (var child in node.GetChildNodesAndTokens()) + { + if (child.IsNode) + { + foreach (var token in EnumerateTokens(child.AsNode()!)) + { + yield return token; + } + } + if (child.IsToken) + { + yield return child.AsToken(); + } + } + } + } +} \ No newline at end of file diff --git a/Parser/SyntaxNode.cs b/Parser/SyntaxNode.cs index 009acfd..baa4f7a 100644 --- a/Parser/SyntaxNode.cs +++ b/Parser/SyntaxNode.cs @@ -1,6 +1,5 @@ using Parser.Internal; using System.Collections.Generic; -using System.Collections.Immutable; using System.Linq; namespace Parser @@ -40,6 +39,15 @@ namespace Parser public TextSpan FullSpan { get; } + public TextSpan Span => CalculateSpan(); + + private TextSpan CalculateSpan() + { + var leadingTriviaWidth = LeadingTrivia?.Width ?? 0; + var trailingTriviaWidth = TrailingTrivia?.Width ?? 0; + return new TextSpan(Position + leadingTriviaWidth, _green.FullWidth - leadingTriviaWidth - trailingTriviaWidth); + } + public int FullWidth => _green.FullWidth; internal int GetChildPosition(int slot) @@ -82,24 +90,32 @@ namespace Parser public virtual string FullText => _green.FullText; - public virtual IReadOnlyList LeadingTrivia + public virtual SyntaxTriviaList? LeadingTrivia { get { - var p = Parent; - return _green.LeadingTrivia.Select(trivia => new SyntaxTrivia(p, trivia)).ToImmutableList(); + return GetFirstToken()?.LeadingTrivia; } } - public virtual IReadOnlyList TrailingTrivia + public virtual SyntaxTriviaList? TrailingTrivia { get { - var p = Parent; - return _green.TrailingTrivia.Select(trivia => new SyntaxTrivia(p, trivia)).ToImmutableList(); + return GetLastToken()?.TrailingTrivia; } } + public SyntaxToken? GetFirstToken() + { + return SyntaxNavigator.Singleton.EnumerateTokens(this).Select(t => (SyntaxToken?)t).FirstOrDefault(); + } + + public SyntaxToken? GetLastToken() + { + return SyntaxNavigator.Singleton.EnumerateTokens(this).Select(t => (SyntaxToken?)t).LastOrDefault(); + } + public abstract void Accept(SyntaxVisitor visitor); public SyntaxDiagnostic[] GetDiagnostics() @@ -170,12 +186,16 @@ namespace Parser internal override SyntaxNode? GetNode(int index) { - throw new System.NotImplementedException(); + return index switch + { + 0 => GetRed(ref _file!, 0), + _ => null, + }; } public override void Accept(SyntaxVisitor visitor) { - throw new System.NotImplementedException(); + visitor.VisitRoot(this); } diff --git a/Parser/SyntaxToken.cs b/Parser/SyntaxToken.cs index 7b1cc67..33844d7 100644 --- a/Parser/SyntaxToken.cs +++ b/Parser/SyntaxToken.cs @@ -33,6 +33,15 @@ namespace Parser public TextSpan FullSpan { get; } + public TextSpan Span => CalculateSpan(); + + public TextSpan CalculateSpan() + { + var leadingTriviaWidth = LeadingTrivia.Width; + var trailingTriviaWidth = TrailingTrivia.Width; + return new TextSpan(Position + leadingTriviaWidth, FullWidth - leadingTriviaWidth - trailingTriviaWidth); + } + public object? Value => _token.GetValue(); public bool Equals(SyntaxToken other) @@ -69,21 +78,21 @@ namespace Parser public int FullWidth => _token.FullWidth; public bool IsMissing => _token.IsMissing; - public IReadOnlyList LeadingTrivia + public SyntaxTriviaList LeadingTrivia { get { - var p = _parent; - return _token.LeadingTrivia.Select(trivia => new SyntaxTrivia(p, trivia)).ToImmutableList(); + return new SyntaxTriviaList(this, Token.LeadingTriviaCore, this.Position); } } - public IReadOnlyList TrailingTrivia + public SyntaxTriviaList TrailingTrivia { get { - var p = _parent; - return _token.TrailingTrivia.Select(trivia => new SyntaxTrivia(p, trivia)).ToImmutableList(); + var trailingGreen = Token.TrailingTriviaCore; + var trailingTriviaWidth = trailingGreen?.FullWidth ?? 0; + return new SyntaxTriviaList(this, trailingGreen, this.Position + this.FullWidth - trailingTriviaWidth); } } } diff --git a/Parser/SyntaxTriviaList.cs b/Parser/SyntaxTriviaList.cs new file mode 100644 index 0000000..61eadd7 --- /dev/null +++ b/Parser/SyntaxTriviaList.cs @@ -0,0 +1,52 @@ +using Parser.Internal; +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Parser +{ + public struct SyntaxTriviaList : IReadOnlyList + { + internal GreenNode? Node { get; } + + internal SyntaxTriviaList(SyntaxToken token, GreenNode? node, int position) + { + Node = node; + Token = token; + Position = position; + } + + public SyntaxToken Token { get; } + + public int Position { get; } + + public int Count => (Node as SyntaxList)?.Length ?? 0; + + public int Width => Node?.FullWidth ?? 0; + + public SyntaxTrivia this[int index] + { + get + { + return Node switch + { + SyntaxList triviaList => new SyntaxTrivia(Token.Parent, triviaList[index]), + _ => throw new IndexOutOfRangeException(), + }; + } + } + + public IEnumerator GetEnumerator() + { + for (var i = 0; i < Count; i++) + { + yield return this[i]; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/Parser/SyntaxVisitor.cs b/Parser/SyntaxVisitor.cs index b03fd73..2f89563 100644 --- a/Parser/SyntaxVisitor.cs +++ b/Parser/SyntaxVisitor.cs @@ -15,5 +15,10 @@ { DefaultVisit(list); } + + public virtual void VisitRoot(RootSyntaxNode node) + { + DefaultVisit(node); + } } } \ No newline at end of file diff --git a/Semantics/GetClass.cs b/Semantics/GetClass.cs index 49a38cd..4d74ddf 100644 --- a/Semantics/GetClass.cs +++ b/Semantics/GetClass.cs @@ -11,14 +11,14 @@ namespace Semantics { var name = methodDefinition.Name.Text; var description = ""; - description += string.Join("", methodDefinition.LeadingTrivia.Select(x => x.FullText)); + description += string.Join("", methodDefinition.LeadingTrivia?.Select(x => x.FullText)); if (methodDefinition.Body == null) { - description += string.Join("", methodDefinition.EndKeyword.LeadingTrivia.Select(x => x.FullText)); + description += string.Join("", methodDefinition.EndKeyword.LeadingTrivia?.Select(x => x.FullText)); } else { - description += string.Join("", methodDefinition.Body.LeadingTrivia.Select(x => x.FullText)); + description += string.Join("", methodDefinition.Body.LeadingTrivia?.Select(x => x.FullText)); } return new MMethod(name, description); @@ -28,7 +28,7 @@ namespace Semantics { var name = methodDeclaration.Name.Text; var description = ""; - description += string.Join("", methodDeclaration.LeadingTrivia.Select(x => x.FullText)); + description += string.Join("", methodDeclaration.LeadingTrivia?.Select(x => x.FullText)); return new MMethod(name, description); } From 165cd35485b282431363bf71b68dc597171a6288 Mon Sep 17 00:00:00 2001 From: Alexander Luzgarev Date: Fri, 24 Jul 2020 09:18:36 +0200 Subject: [PATCH 11/14] Use Span in diagnostics reporting --- Parser/Binding/Binder.cs | 5 ++--- Parser/Evaluator.cs | 21 ++++++++------------- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/Parser/Binding/Binder.cs b/Parser/Binding/Binder.cs index ba0b8b1..8adb0a0 100644 --- a/Parser/Binding/Binder.cs +++ b/Parser/Binding/Binder.cs @@ -30,7 +30,7 @@ namespace Parser.Binding if (f.Name == "Main") { _diagnostics.ReportMainIsNotAllowed( - new TextSpan(f.Syntax.Position, f.Syntax.FullWidth)); + f.Syntax.Span); return null; } } @@ -277,8 +277,7 @@ namespace Parser.Binding { if (node.Kind != TokenKind.IdentifierNameExpression) { - _diagnostics.ReportForLoopWithoutVariable( - new TextSpan(node.Position, node.FullWidth)); + _diagnostics.ReportForLoopWithoutVariable(node.Span); return null; } diff --git a/Parser/Evaluator.cs b/Parser/Evaluator.cs index a563051..98ae2e1 100644 --- a/Parser/Evaluator.cs +++ b/Parser/Evaluator.cs @@ -39,7 +39,7 @@ namespace Parser if (mainFunction.InputDescription.Length > 0) { _diagnostics.ReportNotEnoughInputs( - new TextSpan(mainFunction.Body.Syntax.Position, mainFunction.Body.Syntax.Position + mainFunction.Body.Syntax.FullWidth), + mainFunction.Body.Syntax.Span, mainFunction.Name); return new EvaluationResult(null, _diagnostics.ToImmutableArray()); } @@ -291,10 +291,9 @@ namespace Parser foreach (var argument in node.Arguments) { var evaluatedArgument = EvaluateExpression(argument); - if (argument is null) + if (evaluatedArgument is null) { - _diagnostics.ReportCannotEvaluateExpression( - new TextSpan(argument.Syntax.Position, argument.Syntax.FullWidth)); + _diagnostics.ReportCannotEvaluateExpression(argument.Syntax.Span); allGood = false; } else @@ -319,9 +318,7 @@ namespace Parser if (resolvedFunction is null) { _diagnostics.ReportFunctionNotFound( - new TextSpan( - node.Name.Syntax.Position, - node.Name.Syntax.Position + node.Name.Syntax.FullWidth), + node.Name.Syntax.Span, function.Name); return null; } @@ -343,10 +340,8 @@ namespace Parser if (counter < arguments.Count) { _diagnostics.ReportTooManyInputs( - new TextSpan( - node.Arguments[counter].Syntax.Position, - node.Arguments[counter].Syntax.Position + node.Arguments[counter].Syntax.FullWidth), - function.Name); + node.Arguments[counter].Syntax.Span, + function.Name); return null; } _scopeStack.Push(newScope); @@ -439,7 +434,7 @@ namespace Parser if (maybeValue is null) { _diagnostics.ReportVariableNotFound( - new TextSpan(node.Syntax.Position, node.Syntax.FullWidth), + node.Syntax.Span, variableName); } @@ -505,7 +500,7 @@ namespace Parser if (rightValue is null) { _diagnostics.ReportCannotEvaluateExpression( - new TextSpan(node.Right.Syntax.Position, node.Right.Syntax.Position + node.Right.Syntax.FullWidth)); + node.Right.Syntax.Span); return null; } From c70e94d166dcc0f961858f918848217bc37e7304 Mon Sep 17 00:00:00 2001 From: Alexander Luzgarev Date: Fri, 24 Jul 2020 10:00:48 +0200 Subject: [PATCH 12/14] Style fixes --- Parser/Binding/Binder.cs | 10 +++-- Parser/Internal/MLexerGreen.cs | 72 +++++++++++++++------------------ Parser/Internal/MParserGreen.cs | 63 ++++++----------------------- Parser/Internal/SyntaxNode.cs | 8 ++-- Parser/MFunctions/MHelpers.cs | 2 +- Parser/SyntaxToken.cs | 2 +- Semantics/GetClass.cs | 3 +- cmi/TreeRenderer.cs | 2 +- 8 files changed, 60 insertions(+), 102 deletions(-) diff --git a/Parser/Binding/Binder.cs b/Parser/Binding/Binder.cs index 8adb0a0..1fd69a8 100644 --- a/Parser/Binding/Binder.cs +++ b/Parser/Binding/Binder.cs @@ -13,7 +13,7 @@ namespace Parser.Binding { private readonly DiagnosticsBag _diagnostics = new DiagnosticsBag(); - private BoundProgram? BindProgramInternal(SyntaxTree syntaxTree) + private BoundProgram BindProgramInternal(SyntaxTree syntaxTree) { var boundRoot = BindRoot(syntaxTree.NullRoot); var statements = ((BoundBlockStatement)boundRoot.File.Body).Statements; @@ -31,7 +31,11 @@ namespace Parser.Binding { _diagnostics.ReportMainIsNotAllowed( f.Syntax.Span); - return null; + return new BoundProgram( + _diagnostics.ToImmutableArray(), + mainFunction: null, + scriptFunction: null, + functions: functionsBuilder.ToImmutable()); } } @@ -70,7 +74,7 @@ namespace Parser.Binding functionsBuilder.ToImmutable()); } - public static BoundProgram? BindProgram(SyntaxTree syntaxTree) + public static BoundProgram BindProgram(SyntaxTree syntaxTree) { var binder = new Binder(); return binder.BindProgramInternal(syntaxTree); diff --git a/Parser/Internal/MLexerGreen.cs b/Parser/Internal/MLexerGreen.cs index a1f592c..3442f46 100644 --- a/Parser/Internal/MLexerGreen.cs +++ b/Parser/Internal/MLexerGreen.cs @@ -858,46 +858,40 @@ namespace Parser.Internal List leadingTrivia, List trailingTrivia) { - switch (tokenInfo.Kind) + return tokenInfo.Kind switch { - case TokenKind.IdentifierToken: - return TokenFactory.CreateIdentifier( - tokenInfo.Text, - leadingTrivia, - trailingTrivia); - case TokenKind.UnquotedStringLiteralToken: - return TokenFactory.CreateUnquotedStringLiteral( - tokenInfo.Text, - tokenInfo.StringValue, - leadingTrivia, - trailingTrivia); - case TokenKind.NumberLiteralToken: - return TokenFactory.CreateTokenWithValueAndTrivia( - tokenInfo.Kind, - tokenInfo.Text, - tokenInfo.DoubleValue, - leadingTrivia, - trailingTrivia); - case TokenKind.StringLiteralToken: - return TokenFactory.CreateTokenWithValueAndTrivia( - tokenInfo.Kind, - tokenInfo.Text, - tokenInfo.StringValue, - leadingTrivia, - trailingTrivia); - case TokenKind.DoubleQuotedStringLiteralToken: - return TokenFactory.CreateTokenWithValueAndTrivia( - tokenInfo.Kind, - tokenInfo.Text, - tokenInfo.StringValue, - leadingTrivia, - trailingTrivia); - default: - return TokenFactory.CreateTokenWithTrivia( - tokenInfo.Kind, - leadingTrivia, - trailingTrivia); - } + TokenKind.IdentifierToken => TokenFactory.CreateIdentifier( + tokenInfo.Text, + leadingTrivia, + trailingTrivia), + TokenKind.UnquotedStringLiteralToken => TokenFactory.CreateUnquotedStringLiteral( + tokenInfo.Text, + tokenInfo.StringValue, + leadingTrivia, + trailingTrivia), + TokenKind.NumberLiteralToken => TokenFactory.CreateTokenWithValueAndTrivia( + tokenInfo.Kind, + tokenInfo.Text, + tokenInfo.DoubleValue, + leadingTrivia, + trailingTrivia), + TokenKind.StringLiteralToken => TokenFactory.CreateTokenWithValueAndTrivia( + tokenInfo.Kind, + tokenInfo.Text, + tokenInfo.StringValue, + leadingTrivia, + trailingTrivia), + TokenKind.DoubleQuotedStringLiteralToken => TokenFactory.CreateTokenWithValueAndTrivia( + tokenInfo.Kind, + tokenInfo.Text, + tokenInfo.StringValue, + leadingTrivia, + trailingTrivia), + _ => TokenFactory.CreateTokenWithTrivia( + tokenInfo.Kind, + leadingTrivia, + trailingTrivia), + }; } public List<(SyntaxToken, Position)> ParseAll() diff --git a/Parser/Internal/MParserGreen.cs b/Parser/Internal/MParserGreen.cs index 79df93a..920575c 100644 --- a/Parser/Internal/MParserGreen.cs +++ b/Parser/Internal/MParserGreen.cs @@ -76,17 +76,6 @@ namespace Parser.Internal return token; } - private SyntaxToken? PossiblyEatIdentifier(string s) - { - var token = CurrentToken; - if (token.Kind == TokenKind.IdentifierToken && token.Text == s) - { - return EatToken(); - } - - return null; - } - private SyntaxToken EatPossiblyMissingIdentifier(string s) { var token = CurrentToken; @@ -300,43 +289,20 @@ namespace Parser.Internal return builder.ToList(); } - private ExpressionSyntaxNode? ParseTerm(ParseOptions options) + private ExpressionSyntaxNode ParseTerm(ParseOptions options) { var token = CurrentToken; - ExpressionSyntaxNode? expression = null; - switch (token.Kind) + ExpressionSyntaxNode expression = token.Kind switch { - case TokenKind.NumberLiteralToken: - expression = Factory.NumberLiteralExpressionSyntax(EatToken()); - break; - case TokenKind.StringLiteralToken: - expression = Factory.StringLiteralExpressionSyntax(EatToken()); - break; - case TokenKind.DoubleQuotedStringLiteralToken: - expression = Factory.DoubleQuotedStringLiteralExpressionSyntax(EatToken()); - break; - case TokenKind.OpenSquareBracketToken: - expression = ParseArrayLiteral(); - break; - case TokenKind.OpenBraceToken: - expression = ParseCellArrayLiteral(); - break; - case TokenKind.ColonToken: - expression = Factory.EmptyExpressionSyntax(); - break; - case TokenKind.OpenParenthesisToken: - expression = ParseParenthesizedExpression(); - break; - default: - var id = EatToken(TokenKind.IdentifierToken); - expression = Factory.IdentifierNameExpressionSyntax(id); - break; - } - - if (expression == null) - { - return null; - } + TokenKind.NumberLiteralToken => Factory.NumberLiteralExpressionSyntax(EatToken()), + TokenKind.StringLiteralToken => Factory.StringLiteralExpressionSyntax(EatToken()), + TokenKind.DoubleQuotedStringLiteralToken => Factory.DoubleQuotedStringLiteralExpressionSyntax(EatToken()), + TokenKind.OpenSquareBracketToken => ParseArrayLiteral(), + TokenKind.OpenBraceToken => ParseCellArrayLiteral(), + TokenKind.ColonToken => Factory.EmptyExpressionSyntax(), + TokenKind.OpenParenthesisToken => ParseParenthesizedExpression(), + _ => Factory.IdentifierNameExpressionSyntax(EatToken(TokenKind.IdentifierToken)), + }; return ParsePostfix(options, expression); } @@ -599,10 +565,6 @@ namespace Parser.Internal else { lhs = ParseTerm(options); - if (lhs is null) - { - throw new Exception("Left-hand side in subexpression cannot be empty."); - } } while (true) @@ -858,8 +820,7 @@ namespace Parser.Internal private bool TriviaContainsNewLine(GreenNode? trivia) { - var triviaList = trivia as SyntaxList; - if (triviaList is null) + if (trivia is not SyntaxList triviaList) { return false; } diff --git a/Parser/Internal/SyntaxNode.cs b/Parser/Internal/SyntaxNode.cs index 999f06d..9486928 100644 --- a/Parser/Internal/SyntaxNode.cs +++ b/Parser/Internal/SyntaxNode.cs @@ -121,11 +121,11 @@ namespace Parser.Internal public override GreenNode? GetSlot(int i) { - switch (i) + return i switch { - case 0: return _file; - default: return null; - } + 0 => _file, + _ => null, + }; } public override GreenNode SetDiagnostics(TokenDiagnostic[] diagnostics) diff --git a/Parser/MFunctions/MHelpers.cs b/Parser/MFunctions/MHelpers.cs index d4fa446..ee13028 100644 --- a/Parser/MFunctions/MHelpers.cs +++ b/Parser/MFunctions/MHelpers.cs @@ -13,7 +13,7 @@ namespace Parser.MFunctions } } - public static int Len(MObject? obj) + public static int Len(MObject obj) { return obj switch { diff --git a/Parser/SyntaxToken.cs b/Parser/SyntaxToken.cs index 33844d7..f3c8a9b 100644 --- a/Parser/SyntaxToken.cs +++ b/Parser/SyntaxToken.cs @@ -51,7 +51,7 @@ namespace Parser public override bool Equals(object? obj) { - if (ReferenceEquals(null, obj)) return false; + if (obj is null) return false; return obj is SyntaxToken token && Equals(token); } diff --git a/Semantics/GetClass.cs b/Semantics/GetClass.cs index 4d74ddf..73ff533 100644 --- a/Semantics/GetClass.cs +++ b/Semantics/GetClass.cs @@ -58,8 +58,7 @@ namespace Semantics public static MClass FromTree(FileSyntaxNode tree, string fileName) { - var classDeclaration = tree.Body.Statements[0].AsNode() as ClassDeclarationSyntaxNode; - if (classDeclaration == null) + if (tree.Body.Statements[0].AsNode() is not ClassDeclarationSyntaxNode classDeclaration) { return null; } diff --git a/cmi/TreeRenderer.cs b/cmi/TreeRenderer.cs index e0d6871..5219465 100644 --- a/cmi/TreeRenderer.cs +++ b/cmi/TreeRenderer.cs @@ -27,7 +27,7 @@ namespace cmi var child = children[index]; if (child.IsNode) { - RenderNode(child.AsNode(), indent, index == last); + RenderNode(child.AsNode()!, indent, index == last); } else if (child.IsToken) { From 6c204846ce9c2f5f299cc2f2e41f080ba1ef4bb1 Mon Sep 17 00:00:00 2001 From: Alexander Luzgarev Date: Sat, 25 Jul 2020 12:11:07 +0200 Subject: [PATCH 13/14] Fix trivia handling --- Parser.Tests/ThereAndBackAgain.cs | 31 +++++++++++++++++++++- Parser/Internal/GreenNode.cs | 1 - Parser/Internal/MParserGreen.cs | 7 ++++- Parser/Internal/SyntaxFactory.Generated.cs | 2 +- Parser/Internal/SyntaxNode.Generated.cs | 6 ++--- Parser/Internal/SyntaxNode.cs | 2 +- Parser/Internal/SyntaxTrivia.cs | 1 + Parser/SyntaxDefinition.xml | 2 +- Parser/SyntaxNode.Generated.cs | 8 +++--- Parser/SyntaxTriviaList.cs | 2 ++ 10 files changed, 49 insertions(+), 13 deletions(-) diff --git a/Parser.Tests/ThereAndBackAgain.cs b/Parser.Tests/ThereAndBackAgain.cs index 782f36f..7c9221c 100644 --- a/Parser.Tests/ThereAndBackAgain.cs +++ b/Parser.Tests/ThereAndBackAgain.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text; using Xunit; namespace Parser.Tests @@ -56,7 +57,35 @@ namespace Parser.Tests Assert.Equal(text, actualText); Assert.Equal(text.Length, actualWidth); } - + + [Theory] + [MemberData(nameof(FilesData))] + public void TestLeadingAndTrailingTrivia(string fileName) + { + var text = File.ReadAllText(fileName); + var window = new TextWindowWithNull(text, fileName); + var parser = CreateParser(window); + var tree = parser.Parse(); + + var sb = new StringBuilder(); + var maybeLeadingTrivia = tree.Root.LeadingTrivia; + var maybeTrailingTrivia = tree.Root.TrailingTrivia; + if (maybeLeadingTrivia is SyntaxTriviaList leadingTrivia) + { + sb.Append(leadingTrivia.FullText); + } + + sb.Append(tree.Root.Text); + + if (maybeTrailingTrivia is SyntaxTriviaList trailingTrivia) + { + sb.Append(trailingTrivia.FullText); + } + + var actualText = sb.ToString(); + Assert.Equal(text, actualText); + } + public static IEnumerable FilesData() { return Files().Select(fileName => new object[] { fileName }); diff --git a/Parser/Internal/GreenNode.cs b/Parser/Internal/GreenNode.cs index 9a07bc0..a18b838 100644 --- a/Parser/Internal/GreenNode.cs +++ b/Parser/Internal/GreenNode.cs @@ -209,7 +209,6 @@ namespace Parser.Internal } return sb.ToString(); - } } diff --git a/Parser/Internal/MParserGreen.cs b/Parser/Internal/MParserGreen.cs index 920575c..32851ba 100644 --- a/Parser/Internal/MParserGreen.cs +++ b/Parser/Internal/MParserGreen.cs @@ -1175,9 +1175,14 @@ namespace Parser.Internal return ParseExpressionStatement(); } - private BlockStatementSyntaxNode ParseBlockStatement() + private BlockStatementSyntaxNode? ParseBlockStatement() { var statements = ParseStatementList(); + if (statements.Length == 0) + { + return null; + } + return Factory.BlockStatementSyntax(statements); } diff --git a/Parser/Internal/SyntaxFactory.Generated.cs b/Parser/Internal/SyntaxFactory.Generated.cs index 6280330..77463ca 100644 --- a/Parser/Internal/SyntaxFactory.Generated.cs +++ b/Parser/Internal/SyntaxFactory.Generated.cs @@ -3,7 +3,7 @@ namespace Parser.Internal { internal partial class SyntaxFactory { - public FileSyntaxNode FileSyntax(BlockStatementSyntaxNode body, SyntaxToken endOfFile) + public FileSyntaxNode FileSyntax(BlockStatementSyntaxNode? body, SyntaxToken endOfFile) { return new FileSyntaxNode(body, endOfFile); } diff --git a/Parser/Internal/SyntaxNode.Generated.cs b/Parser/Internal/SyntaxNode.Generated.cs index b020d0e..8bcebfd 100644 --- a/Parser/Internal/SyntaxNode.Generated.cs +++ b/Parser/Internal/SyntaxNode.Generated.cs @@ -3,9 +3,9 @@ namespace Parser.Internal { internal class FileSyntaxNode : SyntaxNode { - internal readonly BlockStatementSyntaxNode _body; + internal readonly BlockStatementSyntaxNode? _body; internal readonly SyntaxToken _endOfFile; - internal FileSyntaxNode(BlockStatementSyntaxNode body, SyntaxToken endOfFile): base(TokenKind.File) + internal FileSyntaxNode(BlockStatementSyntaxNode? body, SyntaxToken endOfFile): base(TokenKind.File) { Slots = 2; this.AdjustWidth(body); @@ -14,7 +14,7 @@ namespace Parser.Internal _endOfFile = endOfFile; } - internal FileSyntaxNode(BlockStatementSyntaxNode body, SyntaxToken endOfFile, TokenDiagnostic[] diagnostics): base(TokenKind.File, diagnostics) + internal FileSyntaxNode(BlockStatementSyntaxNode? body, SyntaxToken endOfFile, TokenDiagnostic[] diagnostics): base(TokenKind.File, diagnostics) { Slots = 2; this.AdjustWidth(body); diff --git a/Parser/Internal/SyntaxNode.cs b/Parser/Internal/SyntaxNode.cs index 9486928..a94e8fb 100644 --- a/Parser/Internal/SyntaxNode.cs +++ b/Parser/Internal/SyntaxNode.cs @@ -51,7 +51,7 @@ namespace Parser.Internal return builder.ToString(); } - public override string FullText => CollectFullText(); + //public override string FullText => CollectFullText(); public override GreenNode? LeadingTriviaCore => throw new NotImplementedException(); public override GreenNode? TrailingTriviaCore => throw new NotImplementedException(); diff --git a/Parser/Internal/SyntaxTrivia.cs b/Parser/Internal/SyntaxTrivia.cs index e577bb3..9abec4c 100644 --- a/Parser/Internal/SyntaxTrivia.cs +++ b/Parser/Internal/SyntaxTrivia.cs @@ -20,6 +20,7 @@ namespace Parser.Internal } public override string Text => _text; + public override string FullText => _text; public int Width => _text.Length; public override GreenNode? GetSlot(int i) diff --git a/Parser/SyntaxDefinition.xml b/Parser/SyntaxDefinition.xml index be2d64f..68022df 100644 --- a/Parser/SyntaxDefinition.xml +++ b/Parser/SyntaxDefinition.xml @@ -1,7 +1,7 @@ - + diff --git a/Parser/SyntaxNode.Generated.cs b/Parser/SyntaxNode.Generated.cs index 9bc5b65..d1b0431 100644 --- a/Parser/SyntaxNode.Generated.cs +++ b/Parser/SyntaxNode.Generated.cs @@ -16,12 +16,12 @@ namespace Parser } } - public BlockStatementSyntaxNode Body + public BlockStatementSyntaxNode? Body { get { - var red = this.GetRed(ref this._body!, 0); - return red is null ? throw new System.Exception("body cannot be null.") : (BlockStatementSyntaxNode)red; + var red = this.GetRed(ref this._body, 0); + return red is null ? default : (BlockStatementSyntaxNode)red; } } @@ -29,7 +29,7 @@ namespace Parser { return i switch { - 0 => GetRed(ref _body!, 0), _ => null + 0 => GetRed(ref _body, 0), _ => null } ; diff --git a/Parser/SyntaxTriviaList.cs b/Parser/SyntaxTriviaList.cs index 61eadd7..c76e9b1 100644 --- a/Parser/SyntaxTriviaList.cs +++ b/Parser/SyntaxTriviaList.cs @@ -36,6 +36,8 @@ namespace Parser } } + public string FullText => Node?.FullText ?? string.Empty; + public IEnumerator GetEnumerator() { for (var i = 0; i < Count; i++) From 98ae1a1499db9801bbd73cef338550eea3d52df7 Mon Sep 17 00:00:00 2001 From: Alexander Luzgarev Date: Thu, 21 Apr 2022 18:12:26 +0200 Subject: [PATCH 14/14] Fix infinite loop when parsing parameter list This fixes a bug reported by David Garrison: on encountering the new function argument validation syntax, the parser went into an infinite loop. --- Parser.Tests/MParserShould.cs | 30 ++++++++++++++++++++++++++++++ Parser/Internal/MParserGreen.cs | 4 ++++ 2 files changed, 34 insertions(+) diff --git a/Parser.Tests/MParserShould.cs b/Parser.Tests/MParserShould.cs index 7aa648d..0a47f54 100644 --- a/Parser.Tests/MParserShould.cs +++ b/Parser.Tests/MParserShould.cs @@ -1,5 +1,6 @@ using Xunit; using FluentAssertions; +using System; namespace Parser.Tests { @@ -150,5 +151,34 @@ namespace Parser.Tests rhs.Span.Start.Should().Be(16); rhs.Span.End.Should().Be(17); } + + [Fact] + public void NotHangOnUnknownSyntax() + { + var text = @" +classdef myClass + properties + Channel; + NodeID; + Node; + end + methods + function this = sendData(this, arg1, arg2) + arguments + this (1,1) myClass + arg1 (1,1) double {mustBeNonnegative} + arg2 (1,1) double {mustBeNonnegative} + end + If (arg1 = 0) + this.NodeID = 3; + end + end + function this = getData(this, arg1, arg2) + end +end"; + var sut = GetSut(text); + Func action = sut.Parse; + action.Should().Throw(); + } } } \ No newline at end of file diff --git a/Parser/Internal/MParserGreen.cs b/Parser/Internal/MParserGreen.cs index 32851ba..f9d3480 100644 --- a/Parser/Internal/MParserGreen.cs +++ b/Parser/Internal/MParserGreen.cs @@ -170,6 +170,10 @@ namespace Parser.Internal { var identifierToken = EatToken(TokenKind.IdentifierToken); builder.Add(Factory.IdentifierNameExpressionSyntax(identifierToken)); + if (identifierToken.IsMissing) + { + break; + } } }