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);
}
}
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)
{

View File

@ -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<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>
{
private class PatternMismatchException : Exception
@ -115,7 +142,7 @@ public class Interpreter : AST.IExprVisitor<Env, object>
{
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<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)
{
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<Env, object>
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)

View File

@ -335,29 +335,41 @@ class Parser
if (match(TokenType.LParen))
{
var args = new List<Expr?>();
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<Expr?>();
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))

View File

@ -1,2 +1,8 @@
Implement Finn functions
Figure out multiple-binding let-expr semantics
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 Type = { Name: string; Fields: list<Field>; Start: string }
type Type =
{ Name: string
Fields: list<Field>
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}"