Implemented native function calls

This commit is contained in:
Brandon Dyck 2023-09-27 18:16:19 -06:00
parent 43fcb615fb
commit ab38d85465
5 changed files with 128 additions and 46 deletions

View File

@ -111,7 +111,7 @@ public partial record Indexer(Expr Left, Expr Index) : Expr(Left.Start)
return visitor.visitIndexerExpr(context, this); 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, TResult>(TContext context, IExprVisitor<TContext, TResult> visitor) public override TResult accept<TContext, TResult>(TContext context, IExprVisitor<TContext, TResult> visitor)
{ {

View File

@ -1,8 +1,11 @@
using System; using System;
using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using AST = Finn.AST; using AST = Finn.AST;
using System.Runtime.CompilerServices;
using System.Net;
namespace Finn; namespace Finn;
@ -23,7 +26,7 @@ public class Env
public Env() public Env()
{ {
this.enclosing = null; enclosing = null;
} }
public Env(Env enclosing) public Env(Env enclosing)
@ -31,17 +34,18 @@ public class Env
this.enclosing = enclosing; this.enclosing = enclosing;
} }
public object this[Token identifier] public object this[string name]
{ {
set set
{ {
var name = (string)identifier.Literal!; // Redefinition within the same scope should never happen, but front
if (values.ContainsKey(name)) // end should catch it.
{
throw new RuntimeError(identifier, $"Cannot redefine variable {name} in same scope.");
}
values[name] = value; values[name] = value;
} }
}
public object this[Token identifier]
{
get get
{ {
var name = (string)identifier.Literal!; 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<object[], object> Function) : Callable
{
public object Call(Interpreter interpreter, object[] arguments)
{
return this.Function(arguments);
}
public override string ToString() => "<native func>";
}
public class UserFunction : Callable
{
public object Call(Interpreter interpreter, object[] arguments)
{
throw new NotImplementedException();
}
}
public class Interpreter : AST.IExprVisitor<Env, object> public class Interpreter : AST.IExprVisitor<Env, object>
{ {
private class PatternMismatchException : Exception private class PatternMismatchException : Exception
@ -115,7 +142,7 @@ public class Interpreter : AST.IExprVisitor<Env, object>
{ {
return ValueTuple.Create(); return ValueTuple.Create();
} }
env[pattern.Identifier] = obj; env[(string)pattern.Identifier.Literal!] = obj;
return ValueTuple.Create(); return ValueTuple.Create();
} }
@ -272,11 +299,21 @@ public class Interpreter : AST.IExprVisitor<Env, object>
} }
} }
private readonly Env globals = new Env();
public Interpreter()
{
globals["clock"] = new NativeFunction((args) =>
{
return (double)DateTimeOffset.Now.ToUnixTimeSeconds();
});
}
public void Interpret(AST.Expr expression) public void Interpret(AST.Expr expression)
{ {
try try
{ {
var value = evaluate(new Env(), expression); var value = evaluate(globals, expression);
Console.WriteLine(value); Console.WriteLine(value);
} }
catch (RuntimeError err) catch (RuntimeError err)
@ -387,7 +424,19 @@ public class Interpreter : AST.IExprVisitor<Env, object>
public object visitCallExpr(Env env, AST.Call expr) 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) public object visitGroupingExpr(Env env, AST.Grouping expr)

View File

@ -335,6 +335,15 @@ class Parser
if (match(TokenType.LParen)) if (match(TokenType.LParen))
{ {
return finishCall(expr);
}
return expr;
}
private Expr finishCall(Expr callee)
{
var lParen = previous();
var args = new List<Expr?>(); var args = new List<Expr?>();
while (!check(TokenType.RParen)) while (!check(TokenType.RParen))
{ {
@ -352,10 +361,13 @@ class Parser
} }
} }
consume("Expect ')' after arguments.", TokenType.RParen); consume("Expect ')' after arguments.", TokenType.RParen);
return new Call(expr, args.ToArray());
}
return expr; 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() private Expr operand()

View File

@ -1,2 +1,8 @@
Implement Finn functions
Figure out multiple-binding let-expr semantics Figure out multiple-binding let-expr semantics
Inject error handling into parser and scanner 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`

View File

@ -1,5 +1,9 @@
type Field = { Type: string; Name: string } type Field = { Type: string; Name: string }
type Type = { Name: string; Fields: list<Field>; Start: string }
type Type =
{ Name: string
Fields: list<Field>
Start: string }
let exprTypes: Type list = let exprTypes: Type list =
[ { Name = "Sequence" [ { Name = "Sequence"
@ -17,7 +21,8 @@ let exprTypes: Type list =
{ Name = "Literal" { Name = "Literal"
Fields = Fields =
[ { Type = "System.Object" [ { Type = "System.Object"
Name = "Value" }; {Type="Token"; Name="Token"} ] Name = "Value" }
{ Type = "Token"; Name = "Token" } ]
Start = "Token" } Start = "Token" }
{ Name = "Unary" { Name = "Unary"
Fields = [ { Type = "Token"; Name = "Op" }; { Type = "Expr"; Name = "Right" } ] Fields = [ { Type = "Token"; Name = "Op" }; { Type = "Expr"; Name = "Right" } ]
@ -33,10 +38,15 @@ let exprTypes: Type list =
Fields = [ { Type = "Token"; Name = "Value" } ] Fields = [ { Type = "Token"; Name = "Value" } ]
Start = "Value" } Start = "Value" }
{ Name = "List" { Name = "List"
Fields = [ { Type = "Expr[]"; Name = "Elements" }; {Type="Token"; Name = "LBracket"} ] Fields =
[ { Type = "Expr[]"; Name = "Elements" }
{ Type = "Token"; Name = "LBracket" } ]
Start = "LBracket" } Start = "LBracket" }
{ Name = "Variant" { 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" } Start = "Backtick" }
{ Name = "Record" { Name = "Record"
Fields = Fields =
@ -52,7 +62,10 @@ let exprTypes: Type list =
Fields = [ { Type = "Expr"; Name = "Left" }; { Type = "Expr"; Name = "Index" } ] Fields = [ { Type = "Expr"; Name = "Left" }; { Type = "Expr"; Name = "Index" } ]
Start = "Left.Start" } Start = "Left.Start" }
{ Name = "Call" { 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" } Start = "Left.Start" }
{ Name = "Let" { Name = "Let"
Fields = Fields =
@ -74,7 +87,10 @@ let patternTypes =
Fields = [ { Type = "Token?"; Name = "Identifier" }; { Type = "Token"; Name = "Token" } ] Fields = [ { Type = "Token?"; Name = "Identifier" }; { Type = "Token"; Name = "Token" } ]
Start = "Token" } Start = "Token" }
{ Name = "VariantPattern" { 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" } Start = "Backtick" }
{ Name = "FieldPattern" { Name = "FieldPattern"
Fields = [ { Type = "Token"; Name = "Name" }; { Type = "Pattern?"; Name = "Pattern" } ] Fields = [ { Type = "Token"; Name = "Name" }; { Type = "Pattern?"; Name = "Pattern" } ]
@ -85,8 +101,7 @@ let patternTypes =
Name = "Fields" } Name = "Fields" }
{ Type = "SimplePattern?" { Type = "SimplePattern?"
Name = "Rest" } Name = "Rest" }
{ Type = "Token" { Type = "Token"; Name = "LBrace" } ]
Name = "LBrace"} ]
Start = "LBrace" } ] Start = "LBrace" } ]
let visitorMethod baseName t = $"visit{t.Name}{baseName}" let visitorMethod baseName t = $"visit{t.Name}{baseName}"