diff --git a/Parser/Binding/Binder.cs b/Parser/Binding/Binder.cs index e0b5c87..9b8fccd 100644 --- a/Parser/Binding/Binder.cs +++ b/Parser/Binding/Binder.cs @@ -18,23 +18,24 @@ namespace Parser.Binding var binder = new Binder(); var boundRoot = binder.BindRoot(syntaxTree.NullRoot); var statements = ((BoundBlockStatement)boundRoot.File.Body).Statements; - var functionsBuilder = ImmutableDictionary.CreateBuilder(); + 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); + scriptFunction = new FunctionSymbol("%script"); var body = Block(globalStatements[0].Syntax, globalStatements); var loweredBody = Lowerer.Lower(body); - functionsBuilder.Add(scriptFunction, loweredBody); - } - else - { + var declaration = new BoundFunctionDeclaration( + syntax: globalStatements[0].Syntax, + name: "%script", + inputDescription: ImmutableArray.Empty, + outputDescription: ImmutableArray.Empty, + body: body); + var loweredFunction = LowerFunction(declaration); + functionsBuilder.Add(scriptFunction, loweredFunction); } var functions = statements.OfType().ToArray(); @@ -42,11 +43,9 @@ namespace Parser.Binding 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); + name: function.Name); + var loweredFunction = LowerFunction(function); + functionsBuilder.Add(functionSymbol, loweredFunction); if (first && globalStatements.Length == 0) { // the first function in a file will become "main". @@ -62,6 +61,17 @@ namespace Parser.Binding functionsBuilder.ToImmutable()); } + private static LoweredFunction LowerFunction(BoundFunctionDeclaration declaration) + { + var loweredBody = Lowerer.Lower(declaration.Body); + return new LoweredFunction( + declaration: declaration, + name: declaration.Name, + inputDescription: declaration.InputDescription, + outputDescription: declaration.OutputDescription, + body: loweredBody); + } + private BoundRoot BindRoot(RootSyntaxNode node) { var boundFile = BindFile(node.File); diff --git a/Parser/Binding/BoundProgram.cs b/Parser/Binding/BoundProgram.cs index ab7eea6..ebf8d9d 100644 --- a/Parser/Binding/BoundProgram.cs +++ b/Parser/Binding/BoundProgram.cs @@ -9,7 +9,7 @@ namespace Parser.Binding ImmutableArray diagnostics, FunctionSymbol? mainFunction, FunctionSymbol? scriptFunction, - ImmutableDictionary functions) + ImmutableDictionary functions) { Diagnostics = diagnostics; MainFunction = mainFunction; @@ -32,6 +32,6 @@ namespace Parser.Binding /// /// So-called "local" functions. /// - public ImmutableDictionary Functions { get; } + public ImmutableDictionary Functions { get; } } } diff --git a/Parser/Binding/BoundRoot.cs b/Parser/Binding/BoundRoot.cs index 3f18f1b..5f9d0ca 100644 --- a/Parser/Binding/BoundRoot.cs +++ b/Parser/Binding/BoundRoot.cs @@ -156,6 +156,21 @@ namespace Parser.Binding public ImmutableArray InputDescription { get; } public ImmutableArray OutputDescription { get; } public BoundStatement Body { get; } + + public BoundFunctionDeclaration WithBody(BoundStatement body) + { + if (body == Body) + { + return this; + } + + return new BoundFunctionDeclaration( + Syntax, + Name, + InputDescription, + OutputDescription, + body); + } } public class BoundGotoStatement : BoundStatement diff --git a/Parser/Binding/FunctionSymbol.cs b/Parser/Binding/FunctionSymbol.cs index bf5ee25..311d052 100644 --- a/Parser/Binding/FunctionSymbol.cs +++ b/Parser/Binding/FunctionSymbol.cs @@ -1,23 +1,13 @@ -using System.Collections.Immutable; - -namespace Parser.Binding +namespace Parser.Binding { public class FunctionSymbol { public FunctionSymbol( - string name, - ImmutableArray parameters, - FunctionDeclarationSyntaxNode? declaration) + string name) { Name = name; - Parameters = parameters; - Declaration = declaration; } public string Name { get; } - - public ImmutableArray Parameters { get; } - - public FunctionDeclarationSyntaxNode? Declaration { get; } } } diff --git a/Parser/Binding/LoweredFunction.cs b/Parser/Binding/LoweredFunction.cs new file mode 100644 index 0000000..a8d8c6e --- /dev/null +++ b/Parser/Binding/LoweredFunction.cs @@ -0,0 +1,26 @@ +using System.Collections.Immutable; + +namespace Parser.Binding +{ + public class LoweredFunction { + public LoweredFunction( + BoundFunctionDeclaration declaration, + string name, + ImmutableArray inputDescription, + ImmutableArray outputDescription, + BoundBlockStatement body) + { + Declaration = declaration; + Name = name; + InputDescription = inputDescription; + OutputDescription = outputDescription; + Body = body; + } + + public BoundFunctionDeclaration Declaration { get; } + public string Name { get; } + public ImmutableArray InputDescription { get; } + public ImmutableArray OutputDescription { get; } + public BoundBlockStatement Body { get; } + } +} diff --git a/Parser/Evaluator.cs b/Parser/Evaluator.cs index efa86c8..b03aa7d 100644 --- a/Parser/Evaluator.cs +++ b/Parser/Evaluator.cs @@ -5,6 +5,7 @@ using Parser.Objects; using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Linq; namespace Parser { @@ -15,6 +16,7 @@ namespace Parser private readonly DiagnosticsBag _diagnostics = new DiagnosticsBag(); private bool _inRepl = false; private readonly Stack _scopeStack = new Stack(); + private readonly Dictionary _functions = new Dictionary(); public Evaluator(BoundProgram program, CompilationContext context, bool inRepl) { @@ -23,28 +25,34 @@ namespace Parser var outerScope = new EvaluationScope(); _scopeStack.Push(outerScope); _inRepl = inRepl; + foreach (var pair in program.Functions) + { + _functions[pair.Key] = pair.Value; + } } internal EvaluationResult Evaluate() { - if (_program.MainFunction is { } mainFunction) + if (_program.MainFunction is { } mainFunctionSymbol) { - if (mainFunction.Parameters.Length > 0) + var mainFunction = _program.Functions[mainFunctionSymbol]; + if (mainFunction.InputDescription.Length > 0) { _diagnostics.ReportNotEnoughInputs( - new TextSpan(mainFunction.Declaration.Position, mainFunction.Declaration.Position + mainFunction.Declaration.FullWidth), + new TextSpan(mainFunction.Body.Syntax.Position, mainFunction.Body.Syntax.Position + mainFunction.Body.Syntax.FullWidth), mainFunction.Name); return new EvaluationResult(null, _diagnostics.ToImmutableArray()); } else { - var result = EvaluateBlockStatement(_program.Functions[mainFunction]); + var result = EvaluateBlockStatement(mainFunction.Body); return new EvaluationResult(result, _diagnostics.ToImmutableArray()); } } - else if (_program.ScriptFunction is { } scriptFunction) + else if (_program.ScriptFunction is { } scriptFunctionSymbol) { - var result = EvaluateBlockStatement(_program.Functions[scriptFunction]); + var scriptFunction = _program.Functions[scriptFunctionSymbol]; + var result = EvaluateBlockStatement(scriptFunction.Body); return new EvaluationResult(result, _diagnostics.ToImmutableArray()); } else @@ -304,10 +312,58 @@ namespace Parser } else { - throw new NotImplementedException("Functions are not supported."); + var resolvedFunction = ResolveFunction(function); + if (resolvedFunction is null) + { + _diagnostics.ReportFunctionNotFound( + new TextSpan( + node.Name.Syntax.Position, + node.Name.Syntax.Position + node.Name.Syntax.FullWidth), + function.Name); + return null; + } + else + { + // bring arguments into context + var newScope = new EvaluationScope(); + var counter = 0; + foreach (var expectedArgument in resolvedFunction.InputDescription) + { + if (counter >= arguments.Count) + { + break; + } + newScope.Variables.Add(expectedArgument.Name, arguments[counter]); + counter++; + } + + 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); + return null; + } + _scopeStack.Push(newScope); + var result = EvaluateBlockStatement(resolvedFunction.Body); + _scopeStack.Pop(); + return result; + } } } + private LoweredFunction? ResolveFunction(UnresolvedFunctionSymbol functionSymbol) + { + var maybeKey = _functions.Keys.FirstOrDefault(k => k.Name == functionSymbol.Name); + return maybeKey switch + { + { } key => _functions[key], + _ => null, + }; + } + private MObject? EvaluateDisp(List arguments) { if (arguments.Count != 1) @@ -319,11 +375,11 @@ namespace Parser return arguments[0]; } - private FunctionSymbol GetFunctionSymbol(BoundExpression functionName) + private UnresolvedFunctionSymbol GetFunctionSymbol(BoundExpression functionName) { if (functionName.Kind == BoundNodeKind.IdentifierNameExpression) { - return new FunctionSymbol(((BoundIdentifierNameExpression)functionName).Name); + return new UnresolvedFunctionSymbol(((BoundIdentifierNameExpression)functionName).Name); } throw new NotImplementedException($"Unknown function symbol '{functionName.Syntax.Text}'."); diff --git a/Parser/Internal/DiagnosticsBag.cs b/Parser/Internal/DiagnosticsBag.cs index 949b51a..a906357 100644 --- a/Parser/Internal/DiagnosticsBag.cs +++ b/Parser/Internal/DiagnosticsBag.cs @@ -92,5 +92,15 @@ namespace Parser.Internal { Report(span, $"Variable '{variableName}' not found."); } + + internal void ReportFunctionNotFound(TextSpan span, string functionName) + { + Report(span, $"Function '{functionName}' not found."); + } + + internal void ReportTooManyInputs(TextSpan span, string functionName) + { + Report(span, $"Too many inputs in the call to '{functionName}'."); + } } } \ No newline at end of file diff --git a/Parser/FunctionSymbol.cs b/Parser/UnresolvedFunctionSymbol.cs similarity index 54% rename from Parser/FunctionSymbol.cs rename to Parser/UnresolvedFunctionSymbol.cs index eb43cf0..76dfcda 100644 --- a/Parser/FunctionSymbol.cs +++ b/Parser/UnresolvedFunctionSymbol.cs @@ -1,10 +1,10 @@ namespace Parser { - internal class FunctionSymbol + internal class UnresolvedFunctionSymbol { public string Name { get; } - public FunctionSymbol(string name) + public UnresolvedFunctionSymbol(string name) { Name = name; } diff --git a/examples/helloworld/hello.m b/examples/helloworld/hello.m index 6b8f93b..fe26aae 100644 --- a/examples/helloworld/hello.m +++ b/examples/helloworld/hello.m @@ -1,13 +1,11 @@ x = 2; -y = 3; -disp(x + y * y); -disp('Hello world!'); +f(x); +f(x); -x = 2 * 3; -if x > 5 - y = 'Greater than 5'; -elseif x > 0 - y = 10; -end - -disp(y); +function f(x) + disp('X was'); + disp(x); + x = x + 1; + disp('X is') + disp(x); +end \ No newline at end of file