diff --git a/Parser/Binding/Binder.cs b/Parser/Binding/Binder.cs index a042671..e0b5c87 100644 --- a/Parser/Binding/Binder.cs +++ b/Parser/Binding/Binder.cs @@ -17,11 +17,49 @@ namespace Parser.Binding { var binder = new Binder(); var boundRoot = binder.BindRoot(syntaxTree.NullRoot); - var loweredStatement = Lowerer.Lower(boundRoot.File.Body); - var newRoot = Root( - boundRoot.Syntax, - File(boundRoot.File.Syntax, loweredStatement)); - return new BoundProgram(newRoot, binder._diagnostics.ToImmutableArray()); + 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; + if (globalStatements.Length > 0) + { + // we have to gather all bound expression statements into a "script" function. + scriptFunction = new FunctionSymbol( + name: "%script", + parameters: ImmutableArray.Empty, + declaration: null); + var body = Block(globalStatements[0].Syntax, globalStatements); + var loweredBody = Lowerer.Lower(body); + functionsBuilder.Add(scriptFunction, loweredBody); + } + else + { + } + + var functions = statements.OfType().ToArray(); + var first = true; + foreach (var function in functions) + { + var functionSymbol = new FunctionSymbol( + name: function.Name, + parameters: function.InputDescription, + declaration: (FunctionDeclarationSyntaxNode)function.Syntax); + var loweredBody = Lowerer.Lower(function.Body); + functionsBuilder.Add(functionSymbol, loweredBody); + if (first && globalStatements.Length == 0) + { + // the first function in a file will become "main". + first = false; + mainFunction = functionSymbol; + } + } + + return new BoundProgram( + binder._diagnostics.ToImmutableArray(), + mainFunction, + scriptFunction, + functionsBuilder.ToImmutable()); } private BoundRoot BindRoot(RootSyntaxNode node) @@ -148,7 +186,58 @@ namespace Parser.Binding private BoundFunctionDeclaration BindFunctionDeclaration(FunctionDeclarationSyntaxNode node) { - throw new NotImplementedException(); + var inputDescription = BindInputDescription(node.InputDescription); + var outputDescription = BindOutputDescription(node.OutputDescription); + var body = BindStatement(node.Body); + return new BoundFunctionDeclaration(node, node.Name.Text, inputDescription, outputDescription, body); + } + + private ImmutableArray BindOutputDescription(FunctionOutputDescriptionSyntaxNode? node) + { + if (node is null) + { + return ImmutableArray.Empty; + } + var outputs = node.OutputList.Where(p => p.IsNode).Select(p => p.AsNode()!); + var builder = ImmutableArray.CreateBuilder(); + foreach (var output in outputs) + { + if (output.Kind != TokenKind.IdentifierNameExpression) + { + throw new Exception($"Invalid function output kind {output.Kind}."); + } + + builder.Add(BindParameterSymbol((IdentifierNameExpressionSyntaxNode)output)); + } + + return builder.ToImmutable(); + } + + private ImmutableArray BindInputDescription(FunctionInputDescriptionSyntaxNode? node) + { + if (node is null) + { + return ImmutableArray.Empty; + } + + var parameters = node.ParameterList.Where(p => p.IsNode).Select(p => p.AsNode()!); + var builder = ImmutableArray.CreateBuilder(); + foreach (var parameter in parameters) + { + if (parameter.Kind != TokenKind.IdentifierNameExpression) + { + throw new Exception($"Invalid function parameter kind {parameter.Kind}."); + } + + builder.Add(BindParameterSymbol((IdentifierNameExpressionSyntaxNode)parameter)); + } + + return builder.ToImmutable(); + } + + private ParameterSymbol BindParameterSymbol(IdentifierNameExpressionSyntaxNode parameter) + { + return new ParameterSymbol(parameter.Text); } private BoundForStatement BindForStatement(ForStatementSyntaxNode node) diff --git a/Parser/Binding/BoundProgram.cs b/Parser/Binding/BoundProgram.cs index b6cfc94..ab7eea6 100644 --- a/Parser/Binding/BoundProgram.cs +++ b/Parser/Binding/BoundProgram.cs @@ -5,16 +5,33 @@ namespace Parser.Binding { public class BoundProgram { - public BoundProgram(BoundRoot nullRoot, ImmutableArray diagnostics) + public BoundProgram( + ImmutableArray diagnostics, + FunctionSymbol? mainFunction, + FunctionSymbol? scriptFunction, + ImmutableDictionary functions) { - NullRoot = nullRoot; Diagnostics = diagnostics; + MainFunction = mainFunction; + ScriptFunction = scriptFunction; + Functions = functions; } public ImmutableArray Diagnostics { get; } - public BoundRoot NullRoot { get; } + /// + /// A "main" function (first in a file without any global statements). + /// + public FunctionSymbol? MainFunction { get; } - public BoundFile Root => NullRoot.File; + /// + /// A "script" function (generated from all global statements in a file if there are any). + /// + public FunctionSymbol? ScriptFunction { get; } + + /// + /// So-called "local" functions. + /// + public ImmutableDictionary Functions { get; } } } diff --git a/Parser/Binding/BoundRoot.cs b/Parser/Binding/BoundRoot.cs index 5de782a..3f18f1b 100644 --- a/Parser/Binding/BoundRoot.cs +++ b/Parser/Binding/BoundRoot.cs @@ -141,12 +141,21 @@ namespace Parser.Binding public class BoundFunctionDeclaration : BoundStatement { - public BoundFunctionDeclaration(SyntaxNode syntax) + public BoundFunctionDeclaration(SyntaxNode syntax, string name, ImmutableArray inputDescription, ImmutableArray outputDescription, BoundStatement body) : base(syntax) { + Name = name; + InputDescription = inputDescription; + OutputDescription = outputDescription; + Body = body; } public override BoundNodeKind Kind => BoundNodeKind.FunctionDeclaration; + + public string Name { get; } + public ImmutableArray InputDescription { get; } + public ImmutableArray OutputDescription { get; } + public BoundStatement Body { get; } } public class BoundGotoStatement : BoundStatement diff --git a/Parser/Binding/FunctionSymbol.cs b/Parser/Binding/FunctionSymbol.cs new file mode 100644 index 0000000..bf5ee25 --- /dev/null +++ b/Parser/Binding/FunctionSymbol.cs @@ -0,0 +1,23 @@ +using System.Collections.Immutable; + +namespace Parser.Binding +{ + public class FunctionSymbol + { + public FunctionSymbol( + string name, + ImmutableArray parameters, + FunctionDeclarationSyntaxNode? declaration) + { + Name = name; + Parameters = parameters; + Declaration = declaration; + } + + public string Name { get; } + + public ImmutableArray Parameters { get; } + + public FunctionDeclarationSyntaxNode? Declaration { get; } + } +} diff --git a/Parser/Binding/ParameterSymbol.cs b/Parser/Binding/ParameterSymbol.cs new file mode 100644 index 0000000..5b5f234 --- /dev/null +++ b/Parser/Binding/ParameterSymbol.cs @@ -0,0 +1,12 @@ +namespace Parser.Binding +{ + public class ParameterSymbol + { + public ParameterSymbol(string name) + { + Name = name; + } + + public string Name { get; } + } +} diff --git a/Parser/Evaluator.cs b/Parser/Evaluator.cs index d5714b8..bb06ca0 100644 --- a/Parser/Evaluator.cs +++ b/Parser/Evaluator.cs @@ -26,8 +26,30 @@ namespace Parser internal EvaluationResult Evaluate() { - var result = EvaluateFile(_program.Root); - return new EvaluationResult(result, _diagnostics.ToImmutableArray()); + if (_program.MainFunction is { } mainFunction) + { + if (mainFunction.Parameters.Length > 0) + { + _diagnostics.ReportNotEnoughInputs( + new TextSpan(mainFunction.Declaration.Position, mainFunction.Declaration.Position + mainFunction.Declaration.FullWidth), + mainFunction.Name); + return new EvaluationResult(null, _diagnostics.ToImmutableArray()); + } + else + { + var result = EvaluateBlockStatement(_program.Functions[mainFunction]); + return new EvaluationResult(result, _diagnostics.ToImmutableArray()); + } + } + else if (_program.ScriptFunction is { } scriptFunction) + { + var result = EvaluateBlockStatement(_program.Functions[scriptFunction]); + return new EvaluationResult(result, _diagnostics.ToImmutableArray()); + } + else + { + return new EvaluationResult(null, _diagnostics.ToImmutableArray()); + } } private MObject? EvaluateFile(BoundFile root) diff --git a/Parser/Internal/DiagnosticsBag.cs b/Parser/Internal/DiagnosticsBag.cs index 76f1540..949b51a 100644 --- a/Parser/Internal/DiagnosticsBag.cs +++ b/Parser/Internal/DiagnosticsBag.cs @@ -38,6 +38,11 @@ namespace Parser.Internal Report(span, "Unexpected end of file."); } + internal void ReportNotEnoughInputs(TextSpan span, string functionName) + { + Report(span, $"Not enough inputs for function '{functionName}'."); + } + internal void ReportUnexpectedCharacterWhileParsingNumber(TextSpan span, char c) { Report(span, $"Unexpected character '{c}' while parsing a number."); diff --git a/Parser/Internal/MParserGreen.cs b/Parser/Internal/MParserGreen.cs index 2c2e250..dfce71e 100644 --- a/Parser/Internal/MParserGreen.cs +++ b/Parser/Internal/MParserGreen.cs @@ -175,7 +175,7 @@ namespace Parser.Internal if (CurrentToken.Kind == TokenKind.TildeToken) { var notToken = EatToken(); - builder.Add(notToken); + builder.Add(Factory.IdentifierNameExpressionSyntax(notToken)); } else {