Sigil Standard Library Specification
Version: 1.0.0 Last Updated: 2026-03-07
Overview
The Sigil standard library provides essential types and functions that are automatically available in every Sigil program. The design philosophy emphasizes:
- Minimal but complete - Only include truly universal functionality
- Functional-first - Pure functions, immutability by default
- Type-safe - Leverage strong type system
- Composable - Functions that work well together
- Zero-cost abstractions - Compile to efficient JavaScript
Implicit Prelude and Rooted Modules
The prelude is available in every Sigil module without qualification. Other modules are reached through rooted references such as §list, •topology, †runtime, and ※check::log.
Core Types
ConcurrentOutcome[T,E]
Implicit core prelude sum type:
t ConcurrentOutcome[T,E]=Aborted()|Failure(E)|Success(T)
Aborted[T,E]()=>ConcurrentOutcome[T,E]Failure[T,E](error:E)=>ConcurrentOutcome[T,E]Success[T,E](value:T)=>ConcurrentOutcome[T,E]
Option[T]
Represents an optional value - Sigil's null-safe alternative.
t Option[T]=Some(T)|None()
Constructors:
Some[T](value:T)=>Option[T]- Wraps a valueNone[T]()=>Option[T]- Represents absence
Functions:
mapOption(fn,opt)
bindOption(fn,opt)
unwrapOr(fallback,opt)
isSome(opt)
isNone(opt)
Result[T,E]
Represents a computation that may fail - Sigil's exception-free error handling.
t Result[T,E]=Ok(T)|Err(E)
Constructors:
Ok[T,E](value:T)=>Result[T,E]- Success caseErr[T,E](error:E)=>Result[T,E]- Error case
Functions:
mapResult(fn,res)
bindResult(fn,res)
unwrapOrResult(fallback,res)
isOk(res)
isErr(res)
List Operations
Implemented §list Functions
λall[T](pred:λ(T)=>Bool,xs:[T])=>Bool
λany[T](pred:λ(T)=>Bool,xs:[T])=>Bool
λcontains[T](item:T,xs:[T])=>Bool
λcount[T](item:T,xs:[T])=>Int
λcountIf[T](pred:λ(T)=>Bool,xs:[T])=>Int
λdrop[T](n:Int,xs:[T])=>[T]
λfind[T](pred:λ(T)=>Bool,xs:[T])=>Option[T]
λflatMap[T,U](fn:λ(T)=>[U],xs:[T])=>[U]
λfold[T,U](acc:U,fn:λ(U,T)=>U,xs:[T])=>U
λinBounds[T](idx:Int,xs:[T])=>Bool
λlast[T](xs:[T])=>Option[T]
λmax(xs:[Int])=>Option[Int]
λmin(xs:[Int])=>Option[Int]
λnth[T](idx:Int,xs:[T])=>Option[T]
λproduct(xs:[Int])=>Int
λremoveFirst[T](item:T,xs:[T])=>[T]
λreverse[T](xs:[T])=>[T]
λsortedAsc(xs:[Int])=>Bool
λsortedDesc(xs:[Int])=>Bool
λsum(xs:[Int])=>Int
λtake[T](n:Int,xs:[T])=>[T]
Safe element access uses Option[T]:
last([])=>None()find(pred,[])=>None()max([])=>None()min([])=>None()nth(-1,xs)=>None()nth(idx,xs)=>None()when out of bounds
Canonical list-processing restrictions
Sigil treats the list-processing surface as canonical:
- use
§list.allfor universal checks - use
§list.anyfor existential checks - use
§list.countIffor predicate counting - use
mapfor projection - use
filterfor filtering - use
§list.findfor first-match search - use
§list.flatMapfor flattening projection - use
reduce ... from ...or§list.foldfor reduction - use
§list.reversefor reversal
The validator rejects exact recursive clones of all, any, map, filter, find, flatMap, fold, and reverse, rejects #(xs filter pred) in favor of §list.countIf, and rejects recursive result-building of the form self(rest)⧺rhs. These are narrow AST-shape rules, not a general complexity prover.
Outside language/stdlib/, the validator also rejects exact top-level wrappers whose body is already a canonical helper surface, such as §list.sum(xs), §numeric.max(a,b), §string.trim(s), xs map fn, xs filter pred, or xs reduce fn from init. Sigil keeps one canonical helper surface instead of supporting thin local aliases for the same operation.
Implemented §numeric Helpers
t DivMod={quotient:Int,remainder:Int}
λabs(x:Int)=>Int
λclamp(hi:Int,lo:Int,x:Int)=>Int
λdivisible(d:Int,n:Int)=>Bool
λdivmod(a:Int,b:Int)=>DivMod
λgcd(a:Int,b:Int)=>Int
λinRange(max:Int,min:Int,x:Int)=>Bool
λisEven(x:Int)=>Bool
λisNegative(x:Int)=>Bool
λisNonNegative(x:Int)=>Bool
λisOdd(x:Int)=>Bool
λisPositive(x:Int)=>Bool
λisPrime(n:Int)=>Bool
λlcm(a:Int,b:Int)=>Int
λmax(a:Int,b:Int)=>Int
λmin(a:Int,b:Int)=>Int
λmod(a:Int,b:Int)=>Int
λpow(base:Int,exp:Int)=>Int
λrange(start:Int,stop:Int)=>[Int]
λsign(x:Int)=>Int
Implemented §random Functions
λintBetween(max:Int,min:Int)=>!Random Int
λpick[T](items:[T])=>!Random Option[T]
λshuffle[T](items:[T])=>!Random [T]
Semantics:
intBetweenis inclusive and order-insensitive over its two boundspick([])returnsNone()shufflereturns a full permutation of the input list- runtime behavior comes from the active world's
randomentry
String Operations
λcharAt(idx:Int,s:String)=>String
λcontains(s:String,search:String)=>Bool
λdrop(n:Int,s:String)=>String
λendsWith(s:String,suffix:String)=>Bool
λindexOf(s:String,search:String)=>Int
λintToString(n:Int)=>String
λisDigit(s:String)=>Bool
λjoin(separator:String,strings:[String])=>String
λlines(s:String)=>[String]
λreplaceAll(pattern:String,replacement:String,s:String)=>String
λrepeat(count:Int,s:String)=>String
λreverse(s:String)=>String
λsplit(delimiter:String,s:String)=>[String]
λstartsWith(prefix:String,s:String)=>Bool
λsubstring(end:Int,s:String,start:Int)=>String
λtake(n:Int,s:String)=>String
λtoLower(s:String)=>String
λtoUpper(s:String)=>String
λtrimEndChars(chars:String,s:String)=>String
λtrimStartChars(chars:String,s:String)=>String
λtrim(s:String)=>String
λunlines(lines:[String])=>String
File and Process Operations
Implemented §file Functions
λappendText(content:String,path:String)=>!Fs Unit
λexists(path:String)=>!Fs Bool
λlistDir(path:String)=>!Fs [String]
λmakeDir(path:String)=>!Fs Unit
λmakeDirs(path:String)=>!Fs Unit
λmakeTempDir(prefix:String)=>!Fs String
λreadText(path:String)=>!Fs String
λremove(path:String)=>!Fs Unit
λremoveTree(path:String)=>!Fs Unit
λwriteText(content:String,path:String)=>!Fs Unit
makeTempDir(prefix) creates a fresh temp directory and returns its absolute path. Cleanup remains explicit through removeTree.
Implemented §process Types and Functions
t Command={argv:[String],cwd:Option[String],env:{String↦String}}
t RunningProcess={pid:Int}
t ProcessResult={code:Int,stderr:String,stdout:String}
λcommand(argv:[String])=>Command
λexit(code:Int)=>!Process Unit
λwithCwd(command:Command,cwd:String)=>Command
λwithEnv(command:Command,env:{String↦String})=>Command
λrun(command:Command)=>!Process ProcessResult
λstart(command:Command)=>!Process RunningProcess
λwait(process:RunningProcess)=>!Process ProcessResult
λkill(process:RunningProcess)=>!Process Unit
Process rules:
- command execution is argv-based only
withEnvoverlays explicit variables on top of the inherited environment- non-zero exit codes are reported in
ProcessResult.code runcaptures stdout and stderr in memorykillis a normal termination request, not a timeout/escalation protocol
Implemented §terminal Types and Functions
t Key=Escape()|Text(String)
λclearScreen()=>!Terminal Unit
λdisableRawMode()=>!Terminal Unit
λenableRawMode()=>!Terminal Unit
λhideCursor()=>!Terminal Unit
λreadKey()=>!Terminal Key
λshowCursor()=>!Terminal Unit
λwrite(text:String)=>!Terminal Unit
Terminal rules:
- terminal interaction is raw-key oriented rather than line-oriented
readKeyreturns canonicalKeyvaluesEscape()represents the escape key and escape sequencesText(String)carries normalized plain-text key input- interactive programs should restore cursor visibility and raw-mode state before exit
Implemented §regex Types and Functions
t Regex={flags:String,pattern:String}
t RegexError={message:String}
t RegexMatch={captures:[String],end:Int,full:String,start:Int}
λcompile(flags:String,pattern:String)=>Result[Regex,RegexError]
λfind(input:String,regex:Regex)=>Option[RegexMatch]
λisMatch(input:String,regex:Regex)=>Bool
Regex rules:
- v1 semantics follow JavaScript
RegExp compilevalidates both flags and pattern before returningOkfindreturns the first match only- unmatched capture groups are returned as empty strings in
captures
Implemented §time Additions
λsleepMs(ms:Int)=>!Timer Unit
sleepMs is the canonical delay primitive for retry loops and harness orchestration.
Map Operations
Maps are a core collection concept, and helper functions live in ¶map.
t Entry[K,V]={key:K,value:V}
λempty[K,V]()=>{K↦V}
λentries[K,V](map:{K↦V})=>[Entry[K,V]]
λfilter[K,V](map:{K↦V},pred:λ(K,V)=>Bool)=>{K↦V}
λfold[K,V,U](fn:λ(U,K,V)=>U,init:U,map:{K↦V})=>U
λfromList[K,V](entries:[Entry[K,V]])=>{K↦V}
λget[K,V](key:K,map:{K↦V})=>Option[V]
λhas[K,V](key:K,map:{K↦V})=>Bool
λinsert[K,V](key:K,map:{K↦V},value:V)=>{K↦V}
λkeys[K,V](map:{K↦V})=>[K]
λmapValues[K,V,U](fn:λ(V)=>U,map:{K↦V})=>{K↦U}
λmerge[K,V](left:{K↦V},right:{K↦V})=>{K↦V}
λremove[K,V](key:K,map:{K↦V})=>{K↦V}
λsingleton[K,V](key:K,value:V)=>{K↦V}
λsize[K,V](map:{K↦V})=>Int
λvalues[K,V](map:{K↦V})=>[V]
JSON Operations
t JsonError={message:String}
t JsonValue=JsonArray([JsonValue])|JsonBool(Bool)|JsonNull|JsonNumber(Float)|JsonObject({String↦JsonValue})|JsonString(String)
λparse(input:String)=>Result[JsonValue,JsonError]
λstringify(value:JsonValue)=>String
λgetField(key:String,obj:{String↦JsonValue})=>Option[JsonValue]
λgetIndex(arr:[JsonValue],idx:Int)=>Option[JsonValue]
λasArray(value:JsonValue)=>Option[[JsonValue]]
λasBool(value:JsonValue)=>Option[Bool]
λasNumber(value:JsonValue)=>Option[Float]
λasObject(value:JsonValue)=>Option[{String↦JsonValue}]
λasString(value:JsonValue)=>Option[String]
λisNull(value:JsonValue)=>Bool
Notes:
parseis exception-safe and returnsErr({message})for invalid JSON.stringifyis canonical JSON output for the providedJsonValue.
Decode Operations
§decode is the canonical boundary layer from raw JsonValue to trusted internal Sigil values.
t DecodeError={message:String,path:[String]}
t Decoder[T]=λ(JsonValue)=>Result[T,DecodeError]
λrun[T](decoder:Decoder[T],value:JsonValue)=>Result[T,DecodeError]
λparse[T](decoder:Decoder[T],input:String)=>Result[T,DecodeError]
λsucceed[T](value:T)=>Decoder[T]
λfail[T](message:String)=>Decoder[T]
λmap[T,U](decoder:Decoder[T],fn:λ(T)=>U)=>Decoder[U]
λbind[T,U](decoder:Decoder[T],fn:λ(T)=>Decoder[U])=>Decoder[U]
λbool(value:JsonValue)=>Result[Bool,DecodeError]
λfloat(value:JsonValue)=>Result[Float,DecodeError]
λint(value:JsonValue)=>Result[Int,DecodeError]
λstring(value:JsonValue)=>Result[String,DecodeError]
λlist[T](decoder:Decoder[T])=>Decoder[[T]]
λdict[T](decoder:Decoder[T])=>Decoder[{String↦T}]
λfield[T](decoder:Decoder[T],key:String)=>Decoder[T]
λoptionalField[T](decoder:Decoder[T],key:String)=>Decoder[Option[T]]
Notes:
§jsonowns raw parsing and inspection.§decodeowns conversion into trusted internal types.DecodeError.pathrecords the nested field/index path of the failure.- If a field may be absent, keep the record exact and use
Option[T]for that field. - Sigil does not use open records or partial records for this boundary story.
Time Operations
t Instant={epochMillis:Int}
t TimeError={message:String}
λparseIso(input:String)=>Result[Instant,TimeError]
λformatIso(instant:Instant)=>String
λnow()=>!Clock Instant
λfromEpochMillis(millis:Int)=>Instant
λtoEpochMillis(instant:Instant)=>Int
λcompare(left:Instant,right:Instant)=>Int
λisBefore(left:Instant,right:Instant)=>Bool
λisAfter(left:Instant,right:Instant)=>Bool
Notes:
parseIsois strict ISO-8601 only.- Non-ISO text must be normalized before calling
parseIso.
Math Operations
The numeric helper surface is owned by §numeric; there is no separate math module today.
Logging Operations
λdebug(msg:String)=>!Log Unit
λeprintln(msg:String)=>!Log Unit
λprint(msg:String)=>!Log Unit
λprintln(msg:String)=>!Log Unit
λwarn(msg:String)=>!Log Unit
Module System
Import Syntax
Export Visibility
File extension determines visibility:
.lib.sigil files (libraries):
- All top-level declarations are automatically visible to other modules
- No
exportkeyword needed or allowed
.sigil files (executables):
- Export nothing directly
- Have
main()function
No import declarations, no aliasing, no export lists.
Standard Library Modules
core/prelude
Implicitly available. Contains the foundational vocabulary types:
Option[T]Result[T,E]SomeNoneOkErr
§file
UTF-8 filesystem helpers:
appendTextexistslistDirmakeDirmakeDirsmakeTempDirreadTextremoveremoveTreewriteText
§path
Filesystem path helpers:
basenamedirnameextnamejoinnormalizerelative
§io
Console and process I/O only (print, println, eprintln, warn, debug)
¶map
Dynamic keyed collection helpers over {K↦V} values.
§numeric
Integer helpers (abs, divmod, gcd, lcm, max, min, mod, predicates like isEven, and ranges).
§json
Typed JSON parsing and serialization (JsonValue, parse, stringify)
λparse(input:String)=>Result[JsonValue,JsonError]
λstringify(value:JsonValue)=>String
§decode
Canonical JSON-to-domain decoding (Decoder[T], DecodeError, run, parse)
λrun[T](decoder:Decoder[T],value:JsonValue)=>Result[T,DecodeError]
λparse[T](decoder:Decoder[T],input:String)=>Result[T,DecodeError]
§time
Time and instant handling (Instant, strict ISO parsing, clock access)
λparseIso(input:String)=>Result[Instant,TimeError]
λformatIso(instant:Instant)=>String
λnow()=>!Clock Instant
§topology
Canonical declaration layer for external HTTP and TCP runtime dependencies.
t Environment=Environment(String)
t HttpServiceDependency=HttpServiceDependency(String)
t TcpServiceDependency=TcpServiceDependency(String)
λenvironment(name:String)=>Environment
λhttpService(name:String)=>HttpServiceDependency
λtcpService(name:String)=>TcpServiceDependency
§config
Low-level helper layer for topology-backed environment config data.
Canonical project environment files now export world values built through †runtime, †http, and †tcp. §config remains available inside config modules for binding-shaped helper values, but it is no longer the exported environment ABI.
t BindingValue=EnvVar(String)|Literal(String)
t Bindings={httpBindings:[HttpBinding],tcpBindings:[TcpBinding]}
t HttpBinding={baseUrl:BindingValue,dependencyName:String}
t PortBindingValue=EnvVarPort(String)|LiteralPort(Int)
t TcpBinding={dependencyName:String,host:BindingValue,port:PortBindingValue}
λbindHttp(baseUrl:String,dependency:§topology.HttpServiceDependency)=>HttpBinding
λbindHttpEnv(dependency:§topology.HttpServiceDependency,envVar:String)=>HttpBinding
λbindTcp(dependency:§topology.TcpServiceDependency,host:String,port:Int)=>TcpBinding
λbindTcpEnv(dependency:§topology.TcpServiceDependency,hostEnvVar:String,portEnvVar:String)=>TcpBinding
λbindings(httpBindings:[HttpBinding],tcpBindings:[TcpBinding])=>Bindings
§httpClient
Canonical text-based HTTP client.
t Headers={String↦String}
t HttpError={kind:HttpErrorKind,message:String}
t HttpErrorKind=InvalidJson()|InvalidUrl()|Network()|Timeout()|Topology()
t HttpMethod=Delete()|Get()|Patch()|Post()|Put()
t HttpRequest={body:Option[String],dependency:§topology.HttpServiceDependency,headers:Headers,method:HttpMethod,path:String}
t HttpResponse={body:String,headers:Headers,status:Int,url:String}
λrequest(request:HttpRequest)=>!Http Result[HttpResponse,HttpError]
λget(dependency:§topology.HttpServiceDependency,headers:Headers,path:String)=>!Http Result[HttpResponse,HttpError]
λdelete(dependency:§topology.HttpServiceDependency,headers:Headers,path:String)=>!Http Result[HttpResponse,HttpError]
λpost(body:String,dependency:§topology.HttpServiceDependency,headers:Headers,path:String)=>!Http Result[HttpResponse,HttpError]
λput(body:String,dependency:§topology.HttpServiceDependency,headers:Headers,path:String)=>!Http Result[HttpResponse,HttpError]
λpatch(body:String,dependency:§topology.HttpServiceDependency,headers:Headers,path:String)=>!Http Result[HttpResponse,HttpError]
λgetJson(dependency:§topology.HttpServiceDependency,headers:Headers,path:String)=>!Http Result[JsonValue,HttpError]
λdeleteJson(dependency:§topology.HttpServiceDependency,headers:Headers,path:String)=>!Http Result[JsonValue,HttpError]
λpostJson(body:JsonValue,dependency:§topology.HttpServiceDependency,headers:Headers,path:String)=>!Http Result[JsonValue,HttpError]
λputJson(body:JsonValue,dependency:§topology.HttpServiceDependency,headers:Headers,path:String)=>!Http Result[JsonValue,HttpError]
λpatchJson(body:JsonValue,dependency:§topology.HttpServiceDependency,headers:Headers,path:String)=>!Http Result[JsonValue,HttpError]
λresponseJson(response:HttpResponse)=>Result[JsonValue,HttpError]
λemptyHeaders()=>Headers
λjsonHeaders()=>Headers
λheader(key:String,value:String)=>Headers
λmergeHeaders(left:Headers,right:Headers)=>Headers
Semantics:
- any successfully received HTTP response returns
Ok(HttpResponse), including404and500 - invalid URL, transport failure, topology resolution failure, and JSON parse failure return
Err(HttpError) - request and response bodies are UTF-8 text in v1
§httpServer
Canonical request/response HTTP server.
t Headers={String↦String}
t Request={body:String,headers:Headers,method:String,path:String}
t Response={body:String,headers:Headers,status:Int}
t Server={port:Int}
λresponse(body:String,contentType:String,status:Int)=>Response
λok(body:String)=>Response
λjson(body:String,status:Int)=>Response
λlisten(handler:λ(Request)=>Response,port:Int)=>!Http Server
λnotFound()=>Response
λnotFoundMsg(message:String)=>Response
λport(server:Server)=>Int
λserverError(message:String)=>Response
λlogRequest(request:Request)=>!Log Unit
λserve(handler:λ(Request)=>Response,port:Int)=>!Http Unit
λwait(server:Server)=>!Http Unit
Semantics:
serve(handler,port)is equivalent to blocking on a started serverlistenreturns a server handle that can be observed withportand awaited withwait- passing
0as the port asks the OS to choose any free ephemeral port port(server)returns the actual bound port, including after a0bindserveandwaitare long-lived once listening succeeds
§tcpClient
Canonical one-request, one-response TCP client.
t TcpError={kind:TcpErrorKind,message:String}
t TcpErrorKind=Connection()|InvalidAddress()|Protocol()|Timeout()|Topology()
t TcpRequest={dependency:§topology.TcpServiceDependency,message:String}
t TcpResponse={message:String}
λrequest(request:TcpRequest)=>!Tcp Result[TcpResponse,TcpError]
λsend(dependency:§topology.TcpServiceDependency,message:String)=>!Tcp Result[TcpResponse,TcpError]
Semantics:
- requests are UTF-8 text
- the client writes one newline-delimited message and expects one newline-delimited response
- address validation, socket failure, timeout, topology resolution failure, and framing failure return
Err(TcpError)
§tcpServer
Canonical one-request, one-response TCP server.
t Request={host:String,message:String,port:Int}
t Response={message:String}
t Server={port:Int}
λlisten(handler:λ(Request)=>Response,port:Int)=>!Tcp Server
λport(server:Server)=>Int
λresponse(message:String)=>Response
λserve(handler:λ(Request)=>Response,port:Int)=>!Tcp Unit
λwait(server:Server)=>!Tcp Unit
Semantics:
- the server reads one UTF-8 line per connection
- the handler returns one UTF-8 line response
- the server closes each connection after the response is written
serve(handler,port)is equivalent to blocking on a started serverlistenreturns a server handle that can be observed withportand awaited withwait- passing
0as the port asks the OS to choose any free ephemeral port port(server)returns the actual bound port, including after a0bindserveandwaitare long-lived once listening succeeds
Testing
Testing is built into the language with test declarations and the sigil test runner. There is no current §test module surface.
Implementation Notes
JavaScript Compilation
- Lists compile to JavaScript arrays
- Maps compile to JavaScript Map objects
- Strings are JavaScript strings (UTF-16)
- Integers are JavaScript numbers (beware 32-bit limits!)
- Floats are JavaScript numbers (IEEE 754 double)
Performance Considerations
- List operations are functional (immutable) - use sparingly for large lists
- For performance-critical code, consider using mutable collections explicitly
- String concatenation in loops is O(n²) - prefer §string.join when building from parts
Effect System
Effects are tracked at type level:
!Clock!Fs!Http!Log!Process!Random!Tcp!Timer- Pure functions have no effect annotation
Projects may define reusable multi-effect aliases in src/effects.lib.sigil.
Future Extensions
Planned for future stdlib versions:
- §crypto - Cryptographic functions
- §stream - Streaming I/O
- §concurrency - Threads and channels
See Also
- [Type System](/sigil/spec/type-system/) - Type inference and checking
- [Grammar](grammar.ebnf) - Language syntax
- Implementation: core/prelude.lib.sigil
Next: Implement standard library in stdlib/ directory.