|
| 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. |
0 commit comments