aboutsummaryrefslogtreecommitdiff
path: root/vm.bqn
blob: 0293d039b8daf859fdfba5482169f1ef1e0276e4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# 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)
# - SetU: Define value
# - SetN: 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
    _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𝕩 ? 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_@
  }
  # 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}
  }
  # 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 } }
  # 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}
}