Skip to content

Commit f2d4c93

Browse files
committed
chore(release): 0.61.0
1 parent eb42370 commit f2d4c93

34 files changed

+1999
-0
lines changed

CHANGELOG.md

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

55
## [Unreleased]
66

7+
## [0.61.0]
8+
9+
710
- Fixed JSON and XML record deserialization to treat missing optional fields as `None` instead of failing the whole decode.
811
- Updated GitHub Actions .NET setup to use SDK `10.0.201`.
912

13+
**Full Changelog**: https://github.com/MagnusOpera/FScript/compare/0.60.1...0.61.0
14+
1015
## [0.60.1]
1116

1217

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: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
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+
## End-to-end host example (F#)
18+
19+
```fsharp
20+
open System
21+
open System.IO
22+
open FScript.Language
23+
open FScript.Runtime
24+
25+
// 1) Entry script loaded from host memory.
26+
let root = "/virtual/workspace"
27+
let entryFile = Path.Combine(root, "main.fss")
28+
let entrySource =
29+
"""
30+
import "shared/validation.fss" as Validation
31+
32+
[<export>]
33+
let run (name: string) =
34+
let normalized = Host.normalizeName name
35+
Validation.validate normalized
36+
"""
37+
38+
// 2) Imported files also come from host memory.
39+
let virtualSources =
40+
Map [
41+
Path.Combine(root, "shared/validation.fss"),
42+
"""
43+
let validate (name: string) =
44+
if String.toUpper name = name then
45+
$"VALID:{name}"
46+
else
47+
$"INVALID:{name}"
48+
"""
49+
]
50+
51+
let resolveImportedSource (fullPath: string) : string option =
52+
virtualSources |> Map.tryFind fullPath
53+
54+
// 3) Inject host function(s) as externs.
55+
let normalizeNameExtern =
56+
{ Name = "Host.normalizeName"
57+
Scheme = Forall([], TFun(TString, TString))
58+
Arity = 1
59+
Impl =
60+
fun _ args ->
61+
match args with
62+
| [ VString s ] -> VString (s.Trim().ToUpperInvariant())
63+
| _ -> failwith "Host.normalizeName expects one string argument" }
64+
65+
let hostContext =
66+
{ HostContext.RootDirectory = root
67+
DeniedPathGlobs = [] }
68+
69+
let externs = normalizeNameExtern :: Registry.all hostContext
70+
71+
// 4) Load script once (imports + extern typing + evaluation environment).
72+
let loaded =
73+
ScriptHost.loadSourceWithIncludes
74+
externs
75+
root
76+
entryFile
77+
entrySource
78+
resolveImportedSource
79+
80+
// 5) Resolve exported function type/signature before execution.
81+
let runSignature = loaded.ExportedFunctionSignatures |> Map.find "run"
82+
let parameterTypes = runSignature.ParameterTypes |> List.map Types.typeToString
83+
let returnType = Types.typeToString runSignature.ReturnType
84+
85+
printfn "run args: %A" parameterTypes
86+
printfn "run return: %s" returnType
87+
88+
// 6) Execute exported function.
89+
let result = ScriptHost.invoke loaded "run" [ VString " Ada " ]
90+
91+
match result with
92+
| VString value -> printfn "Result: %s" value
93+
| _ -> failwith "Unexpected result type"
94+
```
95+
96+
## Why this pattern works well in production
97+
98+
- **Load once, invoke many times**: keep parsed/inferred/evaluated script state in memory.
99+
- **Extern injection is explicit**: only functions you register are callable.
100+
- **Resolver is host-owned**: imports can come from DB, cache, or controlled virtual FS.
101+
- **Type resolution before invoke**: host can validate contracts before calling exports.
102+
103+
## Common extensions
104+
105+
- cache `LoadedScript` by tenant/project/version,
106+
- enforce root and deny-path policies via `HostContext`,
107+
- validate `ExportedFunctionSignatures` against host-side contracts,
108+
- 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: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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+
let sources =
41+
Map [
42+
"/virtual/shared/common.fss", "let inc x = x + 1"
43+
]
44+
45+
let resolver path =
46+
sources |> Map.tryFind path
47+
```
48+
49+
## Guidance
50+
51+
- Keep resolver deterministic: same path should produce same source for one run.
52+
- Normalize and constrain import roots to avoid path escape behavior.
53+
- 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)