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 + + + +