Skip to content

Commit 6e06250

Browse files
committed
chore(release): 0.68.0
1 parent 6047f2b commit 6e06250

39 files changed

Lines changed: 2453 additions & 0 deletions

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,13 @@
33
All notable changes to FScript are documented in this file.
44

55
## [Unreleased]
6+
7+
## [0.68.0]
8+
69
- Added native `Task.spawn` and `Task.await` support with `'a task` types for concurrent thunk execution.
710

11+
**Full Changelog**: https://github.com/MagnusOpera/FScript/compare/0.67.1...0.68.0
12+
813
## [0.67.1]
914

1015

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
id: embedding-overview
3+
title: Embedding Overview
4+
slug: /embedding/overview
5+
---
6+
7+
FScript is designed for host applications that embed a scripting language safely.
8+
9+
## Embedding workflow
10+
11+
1. Reference language/runtime packages.
12+
2. Build a host context.
13+
3. Register extern functions your scripts can call.
14+
4. Load scripts (with include resolution if needed).
15+
5. Resolve exported function signatures.
16+
6. Execute exported functions and handle results/errors.
17+
18+
## NuGet packages
19+
20+
- [`MagnusOpera.FScript.Language`](https://www.nuget.org/packages/MagnusOpera.FScript.Language)
21+
- [`MagnusOpera.FScript.Runtime`](https://www.nuget.org/packages/MagnusOpera.FScript.Runtime)
22+
- [`MagnusOpera.FScript.TypeProvider`](https://www.nuget.org/packages/MagnusOpera.FScript.TypeProvider)
23+
24+
## Recommended reading order
25+
26+
1. [Real-World Embedding (Load, Resolve Type, Execute)](./real-world-embedding)
27+
2. [F# Type Provider and Use Cases](./type-provider)
28+
3. [Register Extern Functions](./register-externs)
29+
4. [Resolver and Includes](./resolver-and-includes)
30+
5. [Sandbox and Safety](./sandbox-and-safety)
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
---
2+
id: real-world-embedding
3+
title: Real-World Embedding
4+
sidebar_label: Real-World Embedding
5+
slug: /embedding/real-world-embedding
6+
---
7+
8+
This page shows a practical host flow with real concerns:
9+
10+
1. load a script (with `import` support),
11+
2. inject host functions,
12+
3. resolve exported function types,
13+
4. execute exported functions safely.
14+
15+
The example uses an in-memory source resolver so the host controls where imported source comes from.
16+
17+
## Export host-callable bindings
18+
19+
Use `[<export>]` on top-level bindings that the host must discover and invoke through embedding APIs such as `ScriptHost`.
20+
21+
Bindings are part of the runtime host surface only when they are explicitly marked `[<export>]`. This makes the host-callable contract intentional: the host can list exported functions, resolve their signatures, read exported values, and invoke exported functions after loading the script.
22+
23+
## End-to-end host example (F#)
24+
25+
```fsharp
26+
open System
27+
open System.IO
28+
open FScript.Language
29+
open FScript.Runtime
30+
31+
// 1) Entry script loaded from host memory.
32+
let root = "/virtual/workspace"
33+
let entryFile = Path.Combine(root, "main.fss")
34+
let normalizeVirtualPath (path: string) = Path.GetFullPath(path)
35+
let entrySource =
36+
"""
37+
import "shared/validation.fss" as Validation
38+
39+
[<export>]
40+
let run (name: string) =
41+
let normalized = Host.normalizeName name
42+
Validation.validate normalized
43+
"""
44+
45+
// 2) Imported files also come from host memory.
46+
let virtualSources =
47+
Map [
48+
normalizeVirtualPath (Path.Combine(root, "shared", "validation.fss")),
49+
"""
50+
let validate (name: string) =
51+
if String.toUpper name = name then
52+
$"VALID:{name}"
53+
else
54+
$"INVALID:{name}"
55+
"""
56+
]
57+
58+
let resolveImportedSource (fullPath: string) : string option =
59+
virtualSources |> Map.tryFind (Path.GetFullPath(fullPath))
60+
61+
// 3) Inject host function(s) as externs.
62+
let normalizeNameExtern =
63+
{ Name = "Host.normalizeName"
64+
Scheme = Forall([], TFun(TString, TString))
65+
Arity = 1
66+
Impl =
67+
fun _ args ->
68+
match args with
69+
| [ VString s ] -> VString (s.Trim().ToUpperInvariant())
70+
| _ -> failwith "Host.normalizeName expects one string argument" }
71+
72+
let hostContext =
73+
{ HostContext.RootDirectory = root
74+
DeniedPathGlobs = [] }
75+
76+
let externs = normalizeNameExtern :: Registry.all hostContext
77+
78+
// 4) Load script once (imports + extern typing + evaluation environment).
79+
let loaded =
80+
ScriptHost.loadSourceWithIncludes
81+
externs
82+
root
83+
entryFile
84+
entrySource
85+
resolveImportedSource
86+
87+
// 5) Resolve exported function type/signature before execution.
88+
let runSignature = loaded.ExportedFunctionSignatures |> Map.find "run"
89+
let parameterTypes = runSignature.ParameterTypes |> List.map Types.typeToString
90+
let returnType = Types.typeToString runSignature.ReturnType
91+
92+
printfn "run args: %A" parameterTypes
93+
printfn "run return: %s" returnType
94+
95+
// 6) Execute exported function.
96+
let result = ScriptHost.invoke loaded "run" [ VString " Ada " ]
97+
98+
match result with
99+
| VString value -> printfn "Result: %s" value
100+
| _ -> failwith "Unexpected result type"
101+
```
102+
103+
In that example, `run` is marked `[<export>]` because the host needs to find it in the loaded script, inspect its signature, and invoke it through `ScriptHost`.
104+
105+
## Why this pattern works well in production
106+
107+
- **Load once, invoke many times**: keep parsed/inferred/evaluated script state in memory.
108+
- **Extern injection is explicit**: only functions you register are callable.
109+
- **Resolver is host-owned**: imports can come from DB, cache, or controlled virtual FS.
110+
- **Type resolution before invoke**: host can validate contracts before calling exports.
111+
112+
## Common extensions
113+
114+
- cache `LoadedScript` by tenant/project/version,
115+
- enforce root and deny-path policies via `HostContext`,
116+
- validate `ExportedFunctionSignatures` against host-side contracts,
117+
- wrap invocation in timeout/cancellation boundaries at host level.
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
---
2+
id: register-externs
3+
title: Register Extern Functions
4+
slug: /embedding/register-externs
5+
---
6+
7+
Externs are host functions exposed to scripts.
8+
9+
Each extern defines:
10+
11+
- name,
12+
- type scheme,
13+
- arity,
14+
- implementation.
15+
16+
## Minimal pattern
17+
18+
```fsharp
19+
{ Name = "My.add"
20+
Scheme = Forall([], TFun(TInt, TFun(TInt, TInt)))
21+
Arity = 2
22+
Impl = fun ctx args -> ... }
23+
```
24+
25+
## Why schemes matter
26+
27+
Type schemes are injected into type inference, so script calls are type-checked before evaluation.
28+
29+
## Real function injection example
30+
31+
```fsharp
32+
open FScript.Language
33+
34+
let slugifyExtern =
35+
{ Name = "Host.slugify"
36+
Scheme = Forall([], TFun(TString, TString))
37+
Arity = 1
38+
Impl =
39+
fun _ args ->
40+
match args with
41+
| [ VString s ] ->
42+
s.Trim().ToLowerInvariant().Replace(" ", "-")
43+
|> VString
44+
| _ ->
45+
failwith "Host.slugify expects one string argument" }
46+
```
47+
48+
Use it with runtime externs:
49+
50+
```fsharp
51+
open FScript.Runtime
52+
53+
let hostContext =
54+
{ HostContext.RootDirectory = "."
55+
DeniedPathGlobs = [] }
56+
57+
let externs = slugifyExtern :: Registry.all hostContext
58+
```
59+
60+
## Higher-order externs (callback into script)
61+
62+
When an extern receives a script function as argument, use `ExternalCallContext.Apply` to invoke it.
63+
64+
```fsharp
65+
let applyTwiceExtern =
66+
{ Name = "Host.applyTwice"
67+
Scheme = Forall([], TFun(TFun(TInt, TInt), TFun(TInt, TInt)))
68+
Arity = 2
69+
Impl =
70+
fun ctx args ->
71+
match args with
72+
| [ fn; VInt n ] ->
73+
let once = ctx.Apply fn (VInt n)
74+
ctx.Apply fn once
75+
| _ ->
76+
failwith "Host.applyTwice expects (int -> int) and int" }
77+
```
78+
79+
## Design tips
80+
81+
- Keep extern APIs small and explicit.
82+
- Prefer pure externs when possible.
83+
- Return option-like outcomes for recoverable failures.
84+
- Choose stable names; extern names are script-level API surface.
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
---
2+
id: resolver-and-includes
3+
title: Resolver and Includes
4+
slug: /embedding/resolver-and-includes
5+
---
6+
7+
When scripts use `import`, you can choose how included source is resolved.
8+
9+
## Resolver-backed loading API
10+
11+
Use `ScriptHost.loadSourceWithIncludes` when the entry script is not read directly from disk and imports must be resolved by the host:
12+
13+
```fsharp
14+
ScriptHost.loadSourceWithIncludes
15+
externs
16+
rootDirectory
17+
entryFile
18+
entrySource
19+
resolveImportedSource
20+
```
21+
22+
`resolveImportedSource` has type:
23+
24+
```fsharp
25+
string -> string option
26+
```
27+
28+
It receives a normalized full path and should return source text (`Some`) or missing (`None`).
29+
30+
## Resolver strategies
31+
32+
- in-memory map (tests, cached scripts),
33+
- database-backed content,
34+
- object storage,
35+
- controlled virtual filesystem.
36+
37+
## Minimal resolver example
38+
39+
```fsharp
40+
open System.IO
41+
42+
let sources =
43+
Map [
44+
Path.GetFullPath(Path.Combine("/virtual/shared", "common.fss")), "let inc x = x + 1"
45+
]
46+
47+
let resolver path =
48+
sources |> Map.tryFind (Path.GetFullPath(path))
49+
```
50+
51+
## Guidance
52+
53+
- Keep resolver deterministic: same path should produce same source for one run.
54+
- Normalize and constrain import roots to avoid path escape behavior.
55+
- Treat missing includes as normal host errors and surface clear diagnostics.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
id: sandbox-and-safety
3+
title: Sandbox and Safety
4+
slug: /embedding/sandbox-and-safety
5+
---
6+
7+
FScript security is capability-based.
8+
9+
Scripts can only perform actions that your host exposes.
10+
11+
## Practical safety model
12+
13+
- Do not expose risky externs by default.
14+
- Restrict filesystem scope with root/deny policies.
15+
- Use cancellation and timeouts for execution control.
16+
- Treat script execution as untrusted input handling.
17+
18+
## Recommended defaults
19+
20+
1. Expose read-only externs first.
21+
2. Add write/network capability only when required.
22+
3. Log extern calls for observability.

0 commit comments

Comments
 (0)