# Create a variable slot.
# A slot also functions as a variable reference, one kind of reference.
# References support some of the following fields:
# - Get: Get value
# - GetC: Get value, and clear the slot (variable only)
# - SetN: Define value
# - SetU: Change value
# - SetQ: Set value and return 0 if possible, and return 1 if not
# - GetF: Get corresponding field from namespace 𝕩
MakeVar ← { program 𝕊 name:
v←@ # Value
Get ⇐ !∘"Runtime: Variable referenced before definition"
SetU ⇐ !∘"↩: Variable modified before definition"
SetN ⇐ {
Get ↩ {𝕊:v}
(SetU ↩ {v↩𝕩}) 𝕩
}
SetQ ⇐ 0∘SetN
GetC ⇐ {
r ← Get𝕩
Get↩SetU↩SetN↩!∘"Internal error: Variable used after clear"
r
}
GetF ⇐ {program 𝕩.Field name}
}
# Other kinds of references, and reference utilities
ref ← {
# Constant-matching reference, like 𝕊2: in a header
Matcher ⇐ {𝕊 const:
SetQ ⇐ const˙ ≢ ⊢
}
# Array destructuring ⟨a,b,c⟩←
Array ⇐ {𝕊 arr:
Get ⇐ {𝕩.Get@}¨ arr˙
# Common code for all setter functions
# 𝕨S𝕩 sets reference 𝕨 to 𝕩, and e indicates error handling
al ← ∨´ {⟨al⟩:al;0}¨ arr
_set_ ← {S _𝕣_ e:
Err ← {(e∾": "∾𝕩)!e≡@ ⋄ ⟨1⟩} # e≡@ indicates SetQ, which can't error
c ← (e≡@) ⊑ {𝔽}‿{𝔽⎊1} # GetF or Get in F can error
# Get field for reference 𝕨 from namespace, if possible
F ← {⟨G⇐GetF⟩𝕊𝕩:(G𝕩).Get@ ; !Err"Cannot extract non-name from namespace"}
{
0=•Type𝕩 ? Err⍟al "Can't use alias in list destructuring"
arr ≡○≢◶⟨Err∘"Target and value shapes don't match", S¨⟩ 𝕩 ;
6=•Type𝕩 ? (⊢ S F⟜𝕩)_c¨ arr ;
Err "Multiple targets but atomic value"
}
}
SetN ⇐ {𝕨.SetN𝕩}_set_"←"
SetU ⇐ {𝕨.SetU𝕩}_set_"↩"
SetQ ⇐ ∨´ {𝕨.SetQ𝕩}_set_@
# Create a merged reference array based on this one
Merge ⇐ {𝕊:
Split ← {
"[…]←: Value must have rank 1 or more" ! 1≤=𝕩
<˘ 𝕩
}
Get ⇐ > Get
SetN‿SetU‿SetQ ⇐ {𝕏⟜Split}¨ SetN‿SetU‿SetQ
}
}
# Alias, like ⇐vals in ⟨a‿b⇐vals⟩←
# It behaves like ⟨a‿b⟩← except when destructuring a namespace (GetF)
Alias ⇐ {env‿name 𝕊 r:
SetN‿SetU‿SetQ ⇐ r
GetF ⇐ {env.program 𝕩.Field name}
al ⇐ 1
}
# Destructuring placeholder ·
not ⇐ { SetU⇐SetN⇐⊢ ⋄ SetQ⇐0˙ }
}
# Create an environment: essentially, a list of variable slots and a
# reference to the parent environment.
MakeEnv ← { 𝕊⟨
p # Parent environment
v # Total number of slots (special names plus named variables)
n # ID numbers of named variables
e # Which named variables are exported
⟩:
ns ← v-≠n # Number of special names
parent ⇐ p
program ⇐ p.program # Determines the meaning of ID numbers
vars ⇐ program⊸MakeVar¨ (ns⥊¯1) ∾ n # Variables
# Return a namespace for this environment.
# A namespace is represented as a namespace with one field, Field.
# 𝕨 ns.Field 𝕩 returns the value of the field with ID 𝕩 in program 𝕨.
MakeNS ⇐ {𝕤
v ← @ ⊣´¨ n ⊔○(e⊸/) ns↓vars # Lookup table
Field ⇐ {𝕨𝕊i:
cross ← 𝕨 { 𝕨1⊘≡𝕩 ? ⊢ ; ⊑ 𝕩.names ⊐ ⊏⟜𝕨.names } program
(Cross i) ⊑ v
}
}
}
# Return a function that reads the variable from slot s at depth d.
# The input is taken from the bytecode stream.
VO ← { d←𝕏@, s←𝕏@, s⊑·{𝕩.vars}{𝕩.parent}⍟d }
# Namespace from environment
Namespace ← {𝕩.MakeNS@}
# Read field 𝕨 from program 𝕩, where 𝕨 is the environment and index
GetField ← { e‿i 𝕊 𝕩:
"Key lookup in non-namespace" ! 6=•Type𝕩
(e.program 𝕩.Field i).Get @
}
# Constants
nothing ← {⇐} # Used when 𝕨 is ·
skipMark ← {⇐} # Indicates body aborted instead of returning
# Execution stack: every body evaluation makes one of these
MakeStack ← {
s ← 𝕩 # Stack (a list)
cont ⇐ 1 # Whether to continue execution
rslt ⇐ skipMark # Result: skipMark to abort current body
Push ⇐ {s∾↩<𝕩} # Push a value
Pop ⇐ {t←-𝕩 ⋄ (s↓˜↩t) ⊢ ⌽t↑s} # Pop 𝕩 values; return as list
Peek ⇐ {𝕊:¯1⊑s} # Return but don't pop top value
Ret ⇐ {rslt↩𝕩 ⋄ cont↩0 ⋄ "Internal compiler error: Wrong stack size"!𝕨≥≠s}
Skip ⇐ {𝕊: cont↩0} # Exit with no return value
}
# All the opcodes
# Each one is a function that takes the next-opcode function so it can
# read values from the bytecode stream. It returns a function to be
# called on the stack s and environment e at evaluation time.
ops ← ((!∘"Unknown opcode")˙⊣´⊢)¨ ⊔˝ ⍉> ⟨
# Constants and drop
0‿{i←𝕏@ ⋄ {s𝕊e: s.Push i⊑e.program.consts } }
1‿{i←𝕏@ ⋄ {s𝕊e: s.Push e {𝕎𝕩}˜ i⊑e.program.blocks } }
6‿( {s𝕊e: s.Pop 1 }˙)
# Returns
7‿( {s𝕊e: 0 s.Ret ⊑s.Pop 1 }˙)
8‿( {s𝕊e: 1 s.Ret Namespace e }˙)
# Arrays
11‿{i←𝕏@ ⋄ {s𝕊e: s.Push ⌽s.Pop i } }
12‿{i←𝕏@ ⋄ {s𝕊e: s.Push ref.Array ⌽s.Pop i } }
13‿{i←𝕏@ ⋄ {s𝕊e: s.Push > ⌽s.Pop i } }
14‿{i←𝕏@ ⋄ {s𝕊e: s.Push {𝕩.Merge@} ref.Array ⌽s.Pop i } }
# Application
16‿( {s𝕊e: s.Push { f‿x: F x } s.Pop 2 }˙)
17‿( {s𝕊e: s.Push { w‿f‿x: w F x } s.Pop 3 }˙)
20‿( {s𝕊e: s.Push { g‿h: G H } s.Pop 2 }˙)
21‿( {s𝕊e: s.Push { f‿g‿h: F G H } s.Pop 3 }˙)
26‿( {s𝕊e: s.Push { f‿m : F _m } s.Pop 2 }˙)
27‿( {s𝕊e: s.Push { f‿m‿g: F _m_ g } s.Pop 3 }˙)
# Application with Nothing
18‿( {s𝕊e: s.Push { f‿x: F⍟(nothing⊸≢) x } s.Pop 2 }˙) # Like 16
19‿( {s𝕊e: s.Push { w‿f‿x: (nothing≢w˙)◶⟨F,w˙⊸F⟩⍟(nothing⊸≢) x } s.Pop 3 }˙) # Like 17
23‿( {s𝕊e: s.Push { f‿g‿h: {f≡nothing?G H;F G H} } s.Pop 3 }˙) # Like 21
22‿( {s𝕊e: "Left argument required" ! nothing≢s.Peek@ }˙)
# Variables
32‿{v←VO𝕩⋄ {s𝕊e: s.Push (V e).Get @ } }
34‿{v←VO𝕩⋄ {s𝕊e: s.Push (V e).GetC@ } }
33‿{v←VO𝕩⋄ {s𝕊e: s.Push V e } }
# Headers
42‿( {s𝕊e: {0:s.Skip@; 1:@; 𝕊:!"Predicate value must be 0 or 1"} ⊑s.Pop 1 }˙)
43‿( {s𝕊e: s.Push ref.Matcher ⊑s.Pop 1 }˙)
44‿( {s𝕊e: s.Push ref.not }˙)
# Assignment
47‿( {s𝕊e: s.Skip⍟⊢{r‿ v: r.SetQ v } s.Pop 2 }˙) # r:
48‿( {s𝕊e: s.Push { r‿ v: r.SetN⊸⊢ v } s.Pop 2 }˙) # r ←v
49‿( {s𝕊e: s.Push { r‿ v: r.SetU⊸⊢ v } s.Pop 2 }˙) # r ↩v
50‿( {s𝕊e: s.Push { r‿f‿x: r.SetU⊸⊢ (r.Get@)F x } s.Pop 3 }˙) # r F↩x
51‿( {s𝕊e: s.Push { r‿f : r.SetU⊸⊢ F r.Get@ } s.Pop 2 }˙) # r F↩
# Namespaces
64‿{i←𝕏@ ⋄ {s𝕊e: s.Push e‿i GetField ⊑s.Pop 1 } }
66‿{i←𝕏@ ⋄ {s𝕊e: s.Push e‿i ref.Alias ⊑s.Pop 1 } }
⟩
# Evaluate a body
RunBC ← { bc‿pos‿env: # bytecode, starting position, environment
Next ← {𝕊: (pos+↩1) ⊢ pos⊑bc }
stack ← MakeStack ⟨⟩
Step ← {𝕊:
op ← (Next@) ⊑ ops
op ↩ Op next
stack Op env
stack.cont # Changes to 0 on return or abort
}
_while_ ← {𝔽⍟𝔾∘𝔽_𝕣_𝔾∘𝔽⍟𝔾𝕩}
Step _while_ ⊢ 1
stack.rslt
}
# Evaluate a program, given the compiler output
{ VM bc‿consts‿blockInfo‿bodyInfo‿loc‿token:
bodies ← {start‿vars‿names‿export:
# Called when the body is evaluated
{parent 𝕊 args:
env ← MakeEnv parent‿vars‿names‿export
(⊢ {𝕩.SetN 𝕨}¨ ≠↑env.vars˙) args # Initialize arguments
RunBC bc‿start‿env
}
}¨ bodyInfo
blocks ← {type‿imm‿body:
# Handle operands
inner ← imm ⊑ type ⊑ ⟨
2⥊⟨{𝕊n: N ⟨⟩}⟩
{𝕊n: {d←N 𝕣‿𝕗 ⋄𝕨D𝕩}}‿{𝕊n: {N 𝕣‿𝕗 }}
{𝕊n: {d←N 𝕣‿𝕗‿𝕘⋄𝕨D𝕩}}‿{𝕊n: {N 𝕣‿𝕗‿𝕘}}
⟩
# Handle arguments
outer ← imm ⊑ ⟨
{
m‿d: {𝕊v: {M 𝕤‿𝕩‿nothing∾v;D 𝕤‿𝕩‿𝕨∾v}} ;
⟨b⟩: {𝕊v: {B 𝕤‿𝕩‿(𝕨⊣nothing)∾v}}
}
⊑
⟩
# Assemble bodies
nmc ← "No matching case"
Then ← {first 𝕊 next: {skipMark≢r←𝕨First𝕩 ? r ; 𝕨Next𝕩}}
run ← {
1=•Type 𝕩 ?
⟨(𝕩⊑bodies) Then {!∘nmc}⟩
;
"Internal compiler error: Invalid body indices" ! 1==𝕩
! (≠𝕩) ≡ 2-imm
e ← {imm ? ⟨nmc⟩ ;
(0=≠¨𝕩) nmc⍟⊣¨ "Left argument "⊸∾¨⟨"not allowed","required"⟩
}𝕩
𝕩 Then´⟜(⊏⟜bodies)˜⟜{!∘𝕩}¨ e
} body
{𝕊 parent:
Inner Outer {parent˙ 𝕏 ⊢}¨ run
}
}¨ blockInfo
program ← {
consts⇐consts
blocks⇐blocks
names⇐0⊑2⊑token
}
(⊑blocks){𝔽} {program⇐program}
}