This tutorial helps you go from zero to writing useful FScript scripts.
You will learn:
- how to run a script,
- how to model data with records/lists/maps/options/unions,
- how to use pattern matching,
- how to split scripts with
import, - how to expose host-callable functions with
[<export>].
brew install magnusopera/tap/fscriptThen run scripts with:
fscript your-script.fssPass script arguments after --:
fscript your-script.fss -- arg1 arg2You can also run from stdin, show version, or start REPL:
cat your-script.fss | fscript -r .
fscript version
fscriptTry REPL right after install:
fscriptgit clone https://github.com/MagnusOpera/FScript.git
cd FScript
make buildThen run scripts with:
./src/FScript/bin/Debug/net10.0/fscript your-script.fssAnd the same extra CLI modes:
cat your-script.fss | ./src/FScript/bin/Debug/net10.0/fscript -r .
cat your-script.fss | ./src/FScript/bin/Debug/net10.0/fscript -r . -- arg1 arg2
./src/FScript/bin/Debug/net10.0/fscript version
./src/FScript/bin/Debug/net10.0/fscriptQuick REPL check after local build:
./src/FScript/bin/Debug/net10.0/fscriptFor REPL-specific behavior (multiline submission, retained state, exit controls), see docs/guides/repl.md.
Create hello.fss:
let name = "FScript"
print $"Hello {name}"Run it:
fscript hello.fssCLI-injected environment metadata is available in scripts:
match Env.ScriptName with
| Some name -> print name
| None -> print "stdin"
for arg in Env.Arguments do
print argFScript is expression-first and immutable. FScript is also indentation-based: layout defines blocks.
let a = 40
let b = 2
let result = a + b
print $"result = {result}"Short block example:
let describe x =
if x > 0 then
"positive"
else
"zero-or-negative"Pipelines are supported:
let text =
"fscript"
|> fun x -> $"{x}-lang"
print textFScript supports the following scalar/basic types:
stringintfloatunit(())bool
let projectName = "fscript" // string
let retries = 3 // int
let ratio = 0.75 // float
let enabled = true // bool
let nothing = () // unitFScript provides expression-based control flow:
if ... then ... elif ... else ...for ... in ... do ...match ... with ...
let classify n =
if n < 0 then
"negative"
elif n = 0 then
"zero"
else
"positive"for value in [1; 2; 3] do
print $"{value}"let describe value =
match value with
| Some x -> $"some:{x}"
| None -> "none"let numbers = [1; 2; 3]
let doubled = numbers |> List.map (fun n -> n * 2)
print $"{doubled}"let maybeName = Some "Ada"
let label = maybeName |> Option.defaultValue "unknown"
print labellet pair = ("pkg", 3)type Project = { Name: string; Version: int }
let p = { Name = "core"; Version = 1 }
print p.Namelet scores = { ["math"] = 18; ["science"] = 20 }
let maybeMath = scores |> Map.tryGet "math"
print $"{maybeMath}"Map keys are string:
let status = { ["200"] = "ok"; ["404"] = "not-found" }type Result =
| Ok of string
| Error of stringUsage:
let success = Ok "done"
let failure = Error "failed"Pattern matching is central in FScript.
let readName value =
match value with
| Some name -> name
| None -> "missing"let describe items =
match items with
| head :: tail -> $"head={head}, tail-size={List.length tail}"
| _ -> "empty"let describePair pair =
match pair with
| ("ok", code) -> $"success:{code}"
| (kind, code) -> $"{kind}:{code}"let cityLabel address =
match address with
| { City = c } -> clet removeKey key values =
match values with
| { [current] = _; ..tail } when current = key -> tail
| _ -> valueslet statusMessage result =
match result with
| Ok value -> $"ok: {value}"
| Error message -> $"error: {message}"Use when to add a boolean condition to a case.
let classify n =
match n with
| x when x < 0 -> "negative"
| x when x = 0 -> "zero"
| _ -> "positive"Record and map patterns are partial in FScript. You can match only the fields/keys you care about; extra entries are allowed.
type User = { Name: string; Role: string; Team: string }
let label user =
match user with
| { Name = n; Role = "admin" } -> $"admin:{n}"
| { Name = n } -> $"user:{n}"let metadata =
{ ["name"] = "core"
["owner"] = "platform"
["tier"] = "gold" }
let summary values =
match values with
| { ["name"] = name; ["owner"] = owner } -> $"{name} by {owner}"
| _ -> "unknown"You can also capture the remaining map entries:
let removeName values =
match values with
| { ["name"] = _; ..rest } -> rest
| _ -> valuesCurried functions are the default.
let add x y = x + y
let inc = add 1
print $"{inc 41}"Recursive functions use let rec:
let rec fib n =
if n < 2 then n
else fib (n - 1) + fib (n - 2)FScript ships with a preloaded stdlib focused on functional collection workflows.
Common families:
List.*Option.*Map.*
Examples:
let values = [1; 2; 3]
let doubled = values |> List.map (fun n -> n * 2)
let chosen = Some 42 |> Option.defaultValue 0
let m = { ["a"] = 1; ["b"] = 2 }
let hasA = m |> Map.containsKey "a"Full reference:
You can split scripts using import.
main.fss:
import "shared/math.fss" as Math
print $"{Math.sum 20 22}"shared/math.fss:
let sum a b = a + bNotes:
- imported files are
.fss, - import cycles are fatal,
- imported symbols must be accessed through the explicit alias (
Math.sum,Common.join, ...).
FScript is designed to be embedded. At host boundary, scripts can expose explicit entry points while the host controls capabilities and security.
Mark top-level bindings with [<export>] when a host must discover them:
[<export>] let dispatch (context: { Command: string }) =
[{ Command = "echo"; Arguments = context.Command; ErrorLevel = 0 }]
// Export descriptor map
{
[nameof dispatch] = [Dispatch; Never]
}Security/hosting teaser:
- core FScript evaluation is pure in-memory computation,
- side effects exist only through host-exposed externs,
- sandboxing is enforced by host decisions (filesystem root, allowed functions, execution controls).
When embedding, keep this mindset:
- expose only needed externs,
- keep filesystem/network boundaries explicit,
- use host-level timeout/cancellation/resource limits.
- see
docs/specs/embedding-fscript-language.mdfor the embedding API, - see
docs/specs/external-functions.mdfor extern design/registration, - see
docs/specs/sandbox-and-security.mdfor the full security model.
- Sample scripts:
- Specifications index:
docs/specs/README.md - Architecture index:
docs/architecture/README.md samples/