Skip to content

Commit 072059d

Browse files
committed
Treat arity-0 externs and exports as values
1 parent 0d67e17 commit 072059d

7 files changed

Lines changed: 75 additions & 12 deletions

File tree

samples/types-showcase.fss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ type Subscription =
2020

2121

2222
let scores =
23-
Map.empty 0
23+
Map.empty
2424
|> Map.add "math" 20
2525
|> Map.add "science" 18
2626

src/FScript.Language/Eval.fs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -449,9 +449,17 @@ module Eval =
449449
| Some _ -> VUnionCtor(typeName, caseName)
450450
caseName, value))
451451

452+
let externContext =
453+
{ Apply = applyFunctionValue evalExpr typeDefs unknownSpan }
454+
452455
let mutable env : Env =
453456
(builtinIgnore :: externs)
454-
|> List.fold (fun acc ext -> acc.Add(ext.Name, VExternal (ext, []))) Map.empty
457+
|> List.fold (fun acc ext ->
458+
if ext.Arity = 0 then
459+
let value = ext.Impl externContext []
460+
acc.Add(ext.Name, value)
461+
else
462+
acc.Add(ext.Name, VExternal (ext, []))) Map.empty
455463
env <-
456464
constructorValues
457465
|> List.fold (fun acc (name, value) -> acc.Add(name, value)) env

src/FScript.Runtime/MapExterns.fs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ open FScript.Language
55
module MapExterns =
66
let empty : ExternalFunction =
77
{ Name = "Map.empty"
8-
Scheme = Forall([ 0 ], TFun(TVar 0, TStringMap (TVar 0)))
9-
Arity = 1
8+
Scheme = Forall([ 0 ], TStringMap (TVar 0))
9+
Arity = 0
1010
Impl = fun _ -> function
11-
| [ _ ] -> VStringMap Map.empty
12-
| _ -> raise (HostCommon.evalError "Map.empty expects one argument") }
11+
| [] -> VStringMap Map.empty
12+
| _ -> raise (HostCommon.evalError "Map.empty expects no arguments") }
1313

1414
let add : ExternalFunction =
1515
{ Name = "Map.add"

src/FScript.Runtime/ScriptHost.fs

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ module ScriptHost =
88
{ TypeDefs: Map<string, Type>
99
Env: Env
1010
ExportedFunctionNames: string list
11+
ExportedValueNames: string list
1112
LastValue: Value }
1213

1314
let private isCallable value =
@@ -28,17 +29,29 @@ module ScriptHost =
2829
let program = FScript.parse source
2930
let typed = FScript.inferWithExterns externs program
3031
let state = Eval.evalProgramWithExternsState externs typed
31-
let functionNames =
32+
let exportedNames =
3233
declaredExportedNames typed
34+
|> List.distinct
35+
|> List.sort
36+
37+
let functionNames =
38+
exportedNames
3339
|> List.filter (fun name ->
3440
match state.Env.TryFind name with
3541
| Some value -> isCallable value
3642
| None -> false)
37-
|> List.distinct
38-
|> List.sort
43+
44+
let valueNames =
45+
exportedNames
46+
|> List.filter (fun name ->
47+
match state.Env.TryFind name with
48+
| Some value -> not (isCallable value)
49+
| None -> false)
50+
3951
{ TypeDefs = state.TypeDefs
4052
Env = state.Env
4153
ExportedFunctionNames = functionNames
54+
ExportedValueNames = valueNames
4255
LastValue = state.LastValue }
4356

4457
let loadFile (externs: ExternalFunction list) (path: string) : LoadedScript =
@@ -47,9 +60,23 @@ module ScriptHost =
4760
let listFunctions (loaded: LoadedScript) : string list =
4861
loaded.ExportedFunctionNames
4962

63+
let listValues (loaded: LoadedScript) : string list =
64+
loaded.ExportedValueNames
65+
66+
let getValue (loaded: LoadedScript) (valueName: string) : Value =
67+
if not (loaded.ExportedValueNames |> List.contains valueName) then
68+
raise (HostCommon.evalError $"Unknown exported value '{valueName}'")
69+
else
70+
match loaded.Env.TryFind valueName with
71+
| Some value -> value
72+
| None -> raise (HostCommon.evalError $"Unknown exported value '{valueName}'")
73+
5074
let invoke (loaded: LoadedScript) (functionName: string) (args: Value list) : Value =
5175
if not (loaded.ExportedFunctionNames |> List.contains functionName) then
52-
raise (HostCommon.evalError $"Unknown exported function '{functionName}'")
76+
if loaded.ExportedValueNames |> List.contains functionName then
77+
raise (HostCommon.evalError $"'{functionName}' is a value and cannot be invoked")
78+
else
79+
raise (HostCommon.evalError $"Unknown exported function '{functionName}'")
5380
else
5481
match loaded.Env.TryFind functionName with
5582
| Some fnValue when isCallable fnValue ->

tests/FScript.Language.Tests/HostExternTests.fs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,20 @@ type HostExternTests () =
1414

1515
[<Test>]
1616
member _.``Map externs support CRUD`` () =
17-
let script = "Map.empty 0 |> Map.add \"a\" 1 |> Map.tryGet \"a\""
17+
let script = "Map.empty |> Map.add \"a\" 1 |> Map.tryGet \"a\""
1818
match Helpers.evalWithExterns externs script with
1919
| VOption (Some (VInt 1L)) -> ()
2020
| _ -> Assert.Fail("Expected Some 1")
2121

22+
[<Test>]
23+
member _.``Map.empty behaves as a value and cannot be invoked`` () =
24+
match Helpers.evalWithExterns externs "Map.empty |> Map.count" with
25+
| VInt 0L -> ()
26+
| _ -> Assert.Fail("Expected empty map count 0")
27+
28+
let act () = Helpers.evalWithExterns externs "Map.empty ()" |> ignore
29+
act |> should throw typeof<TypeException>
30+
2231
[<Test>]
2332
member _.``Map.ofList builds map from tuple list`` () =
2433
let script = "Map.ofList [(\"a\", 1); (\"b\", 2)] |> Map.tryGet \"b\""

tests/FScript.Runtime.Tests/MapExternsTests.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ open FScript.Runtime.Tests.HostTestHelpers
99
type MapExternsTests () =
1010
[<Test>]
1111
member _.``map externs perform CRUD`` () =
12-
let m0 = invoke MapExterns.empty [ VUnit ]
12+
let m0 = invoke MapExterns.empty []
1313
let m1 = invoke MapExterns.add [ VString "a"; VInt 1L; m0 ]
1414
let found = invoke MapExterns.tryGet [ VString "a"; m1 ]
1515
let count = invoke MapExterns.count [ m1 ]

tests/FScript.Runtime.Tests/ScriptHostTests.fs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,25 @@ type ScriptHostTests () =
2525
| VInt 7L -> ()
2626
| _ -> Assert.Fail("Expected closure invocation result 7")
2727

28+
[<Test>]
29+
member _.``script_host exposes exported values separately from functions`` () =
30+
let externs = Registry.all { RootDirectory = Directory.GetCurrentDirectory() }
31+
let loaded = ScriptHost.loadSource externs "export let version = \"1.0\"\nexport let add x y = x + y"
32+
33+
Assert.That(ScriptHost.listFunctions loaded, Does.Contain("add"))
34+
Assert.That(ScriptHost.listValues loaded, Does.Contain("version"))
35+
36+
match ScriptHost.getValue loaded "version" with
37+
| VString "1.0" -> ()
38+
| _ -> Assert.Fail("Expected exported value")
39+
40+
[<Test>]
41+
member _.``script_host rejects invocation of exported value`` () =
42+
let externs = Registry.all { RootDirectory = Directory.GetCurrentDirectory() }
43+
let loaded = ScriptHost.loadSource externs "export let version = \"1.0\""
44+
let act () = ScriptHost.invoke loaded "version" [ VUnit ] |> ignore
45+
Assert.Throws<EvalException>(TestDelegate act) |> ignore
46+
2847
[<Test>]
2948
member _.``script_host hides non-exported functions`` () =
3049
let externs = Registry.all { RootDirectory = Directory.GetCurrentDirectory() }

0 commit comments

Comments
 (0)