diff --git a/Expr.g.cs b/Expr.g.cs index 91b55a1..398355e 100644 --- a/Expr.g.cs +++ b/Expr.g.cs @@ -111,7 +111,7 @@ public partial record Indexer(Expr Left, Expr Index) : Expr(Left.Start) return visitor.visitIndexerExpr(context, this); } } -public partial record Call(Expr Left, Expr?[] Arguments) : Expr(Left.Start) +public partial record Call(Expr Left, Expr?[] Arguments, Token LParen) : Expr(Left.Start) { public override TResult accept(TContext context, IExprVisitor visitor) { diff --git a/Interpreter.cs b/Interpreter.cs index 6edb57d..bed3074 100644 --- a/Interpreter.cs +++ b/Interpreter.cs @@ -1,8 +1,11 @@ using System; +using System.Linq; using System.Collections.Generic; using System.Collections.Immutable; using AST = Finn.AST; +using System.Runtime.CompilerServices; +using System.Net; namespace Finn; @@ -23,7 +26,7 @@ public class Env public Env() { - this.enclosing = null; + enclosing = null; } public Env(Env enclosing) @@ -31,17 +34,18 @@ public class Env this.enclosing = enclosing; } - public object this[Token identifier] + public object this[string name] { set { - var name = (string)identifier.Literal!; - if (values.ContainsKey(name)) - { - throw new RuntimeError(identifier, $"Cannot redefine variable {name} in same scope."); - } + // Redefinition within the same scope should never happen, but front + // end should catch it. values[name] = value; } + } + + public object this[Token identifier] + { get { var name = (string)identifier.Literal!; @@ -61,6 +65,29 @@ public class Env } } +public interface Callable +{ + object Call(Interpreter interpreter, object[] arguments); +} + +public record NativeFunction(Func Function) : Callable +{ + public object Call(Interpreter interpreter, object[] arguments) + { + return this.Function(arguments); + } + + public override string ToString() => ""; +} + +public class UserFunction : Callable +{ + public object Call(Interpreter interpreter, object[] arguments) + { + throw new NotImplementedException(); + } +} + public class Interpreter : AST.IExprVisitor { private class PatternMismatchException : Exception @@ -115,7 +142,7 @@ public class Interpreter : AST.IExprVisitor { return ValueTuple.Create(); } - env[pattern.Identifier] = obj; + env[(string)pattern.Identifier.Literal!] = obj; return ValueTuple.Create(); } @@ -272,11 +299,21 @@ public class Interpreter : AST.IExprVisitor } } + private readonly Env globals = new Env(); + + public Interpreter() + { + globals["clock"] = new NativeFunction((args) => + { + return (double)DateTimeOffset.Now.ToUnixTimeSeconds(); + }); + } + public void Interpret(AST.Expr expression) { try { - var value = evaluate(new Env(), expression); + var value = evaluate(globals, expression); Console.WriteLine(value); } catch (RuntimeError err) @@ -387,7 +424,19 @@ public class Interpreter : AST.IExprVisitor public object visitCallExpr(Env env, AST.Call expr) { - throw new System.NotImplementedException(); + var callee = evaluate(env, expr.Left); + object[] args = new object[expr.Arguments.Length]; + for (int i = 0; i < args.Length; i++) + { + var argExpr = expr.Arguments[i]; + if (argExpr is null) + { + throw new NotImplementedException("partial application not implemented"); + } + args[i] = evaluate(env, argExpr); + } + Callable function = (Callable)callee; + return function.Call(this, args); } public object visitGroupingExpr(Env env, AST.Grouping expr) diff --git a/Parser.cs b/Parser.cs index 7d24687..0bfff10 100644 --- a/Parser.cs +++ b/Parser.cs @@ -335,29 +335,41 @@ class Parser if (match(TokenType.LParen)) { - var args = new List(); - while (!check(TokenType.RParen)) - { - if (match(TokenType.Blank)) - { - args.Add(null); - } - else - { - args.Add(expression()); - } - if (!match(TokenType.Comma)) - { - break; - } - } - consume("Expect ')' after arguments.", TokenType.RParen); - return new Call(expr, args.ToArray()); + return finishCall(expr); } return expr; } + private Expr finishCall(Expr callee) + { + var lParen = previous(); + var args = new List(); + while (!check(TokenType.RParen)) + { + if (match(TokenType.Blank)) + { + args.Add(null); + } + else + { + args.Add(expression()); + } + if (!match(TokenType.Comma)) + { + break; + } + } + consume("Expect ')' after arguments.", TokenType.RParen); + + const int MaxArgs = 25; + if (args.Count > MaxArgs) + { + error(peek(), $"Can't have more than {MaxArgs} arguments."); + } + return new Call(callee, args.ToArray(), lParen); + } + private Expr operand() { if (match(TokenType.Number, TokenType.String)) diff --git a/TODO.txt b/TODO.txt index 5106079..359602a 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,2 +1,8 @@ +Implement Finn functions Figure out multiple-binding let-expr semantics -Inject error handling into parser and scanner \ No newline at end of file +Inject error handling into parser and scanner +Think about a way to hide record fields + - Maybe a compile-time primitive for generating globally unique symbols? + Would also need a syntax to use those symbols. + - Use @ as an operator to convert a comptime-known string to an identifier +Replace `with` keyword with `but` \ No newline at end of file diff --git a/ast_classes.fsx b/ast_classes.fsx index 151153b..aa49c4f 100644 --- a/ast_classes.fsx +++ b/ast_classes.fsx @@ -1,5 +1,9 @@ type Field = { Type: string; Name: string } -type Type = { Name: string; Fields: list; Start: string } + +type Type = + { Name: string + Fields: list + Start: string } let exprTypes: Type list = [ { Name = "Sequence" @@ -12,13 +16,14 @@ let exprTypes: Type list = { Type = "Expr"; Name = "Right" } ] Start = "Left.Start" } { Name = "Grouping" - Fields = [ { Type = "Expr"; Name = "Expression" } ;{Type="Token";Name="LParen"}] - Start = "LParen"} + Fields = [ { Type = "Expr"; Name = "Expression" }; { Type = "Token"; Name = "LParen" } ] + Start = "LParen" } { Name = "Literal" Fields = [ { Type = "System.Object" - Name = "Value" }; {Type="Token"; Name="Token"} ] - Start = "Token"} + Name = "Value" } + { Type = "Token"; Name = "Token" } ] + Start = "Token" } { Name = "Unary" Fields = [ { Type = "Token"; Name = "Op" }; { Type = "Expr"; Name = "Right" } ] Start = "Op" } @@ -33,18 +38,23 @@ let exprTypes: Type list = Fields = [ { Type = "Token"; Name = "Value" } ] Start = "Value" } { Name = "List" - Fields = [ { Type = "Expr[]"; Name = "Elements" }; {Type="Token"; Name = "LBracket"} ] + Fields = + [ { Type = "Expr[]"; Name = "Elements" } + { Type = "Token"; Name = "LBracket" } ] Start = "LBracket" } { Name = "Variant" - Fields = [ { Type = "Token"; Name = "Tag" }; { Type = "Expr?"; Name = "Argument" }; {Type="Token";Name="Backtick"}] + Fields = + [ { Type = "Token"; Name = "Tag" } + { Type = "Expr?"; Name = "Argument" } + { Type = "Token"; Name = "Backtick" } ] Start = "Backtick" } { Name = "Record" Fields = [ { Type = "Field[]" Name = "Extensions" } { Type = "BaseRecord?"; Name = "Base" } - { Type = "Token"; Name= "LBrace"} ] - Start = "LBrace"} + { Type = "Token"; Name = "LBrace" } ] + Start = "LBrace" } { Name = "Selector" Fields = [ { Type = "Expr"; Name = "Left" }; { Type = "Token"; Name = "FieldName" } ] Start = "Left.Start" } @@ -52,29 +62,35 @@ let exprTypes: Type list = Fields = [ { Type = "Expr"; Name = "Left" }; { Type = "Expr"; Name = "Index" } ] Start = "Left.Start" } { Name = "Call" - Fields = [ { Type = "Expr"; Name = "Left" }; { Type = "Expr?[]"; Name = "Arguments" } ] + Fields = + [ { Type = "Expr"; Name = "Left" } + { Type = "Expr?[]"; Name = "Arguments" } + { Type = "Token"; Name = "LParen" } ] Start = "Left.Start" } { Name = "Let" Fields = [ { Type = "Binding[]" Name = "Bindings" } { Type = "Expr"; Name = "Body" } - { Type = "Token"; Name = "LetToken"} ] + { Type = "Token"; Name = "LetToken" } ] Start = "LetToken" } { Name = "When" Fields = [ { Type = "Expr"; Name = "Head" } { Type = "VarBinding[]" Name = "Cases" } - { Type = "Token"; Name = "WhenToken"} ] + { Type = "Token"; Name = "WhenToken" } ] Start = "WhenToken" } ] let patternTypes = [ { Name = "SimplePattern" - Fields = [ { Type = "Token?"; Name = "Identifier" }; {Type = "Token"; Name = "Token"}] + Fields = [ { Type = "Token?"; Name = "Identifier" }; { Type = "Token"; Name = "Token" } ] Start = "Token" } { Name = "VariantPattern" - Fields = [ { Type = "Token"; Name = "Tag" }; { Type = "Pattern?"; Name = "Argument" }; {Type="Token"; Name = "Backtick"} ] + Fields = + [ { Type = "Token"; Name = "Tag" } + { Type = "Pattern?"; Name = "Argument" } + { Type = "Token"; Name = "Backtick" } ] Start = "Backtick" } { Name = "FieldPattern" Fields = [ { Type = "Token"; Name = "Name" }; { Type = "Pattern?"; Name = "Pattern" } ] @@ -85,8 +101,7 @@ let patternTypes = Name = "Fields" } { Type = "SimplePattern?" Name = "Rest" } - { Type = "Token" - Name = "LBrace"} ] + { Type = "Token"; Name = "LBrace" } ] Start = "LBrace" } ] let visitorMethod baseName t = $"visit{t.Name}{baseName}"