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
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
|
-- MIT License, Copyright (c) 2024 Marvin Borner
-- Based on Benn Lynn's ION machine and John Tromp's nf.c
-- TODO: clean everything up after tests are working
-- iter -> fold/etc, application infix vm abstraction,
-- monadic VM, map -> array?
-- (fairly literate translation from C code)
module Reducer.ION
( reduce
) where
import Data.Bits ( (.|.) )
import Data.Char ( chr
, ord
)
import Data.List ( intercalate )
import qualified Data.Map as M
import Data.Map ( Map )
import Debug.Trace
import Helper
import System.IO
ncomb :: Int
ncomb = 128
spTop :: Int
spTop = maxBound
data VM = VM
{ sp :: Int
, hp :: Int
, nvar :: Int
, mem :: Map Int Int
}
deriving Show
dumpMem :: VM -> String
dumpMem VM { mem } =
"--- mem ---\n"
++ intercalate
"\n"
( (\(a, b) -> show a ++ ": " ++ show b)
<$> filter (\(a, b) -> a <= 255 && b /= 0) (M.toList mem)
)
++ "\n--- /mem ---"
dumpStack :: VM -> String
dumpStack VM { mem, sp } =
"--- stack ---\n"
++ intercalate
"\n"
( (\(a, b) ->
show (a - sp) ++ ": " ++ show b ++ if a == sp then " <-" else ""
)
<$> filter (\(a, b) -> a > 255 && b /= 0) (M.toList mem)
)
++ "\n--- /stack ---"
isComb :: Int -> Bool
isComb n = n < ncomb
new :: VM
new = VM spTop ncomb 0 mempty
load :: Int -> VM -> Int
load k vm = M.findWithDefault 0 k (mem vm)
store :: Int -> Int -> VM -> VM
store k v vm = vm { mem = M.insert k v $ mem vm }
app :: Int -> Int -> VM -> (Int, VM)
app x y vm@VM { hp } = (hp, store hp x $ store (hp + 1) y $ vm { hp = hp + 2 })
-- push :: Int -> VM -> VM
-- push n vm@VM { sp } = store (sp - 1) n $ vm { sp = sp - 1 }
arg' :: Int -> VM -> Int
arg' n vm = load (load (sp vm + n) vm + 1) vm
arg :: Int -> VM -> (Int, VM)
arg n vm = (arg' n vm, vm)
-- app' :: (VM -> (Int, VM)) -> (VM -> (Int, VM)) -> VM -> (Int, VM)
-- app' f g vm =
-- let (x, vm1) = f vm
-- (y, vm2) = g vm1
-- in app x y vm2
apparg :: Int -> Int -> VM -> (Int, VM)
apparg m n vm = app (arg' m vm) (arg' n vm) vm
wor :: a -> b -> (a, b)
wor = (,)
com :: Char -> b -> (Int, b)
com = wor . ord
lazy :: Int -> (VM -> (Int, VM)) -> (VM -> (Int, VM)) -> VM -> VM
lazy d f a vm = do
let fix i _ | i > d = Nothing
fix i vm' =
if load (load (sp vm' + i + 1) vm') vm' == load (sp vm' + i) vm'
then fix (i + 1) vm'
else do
let vm1' = vm' { sp = sp vm' - 2 }
let cnvar = nvar vm1'
let (n1, vm2') = app (ord 'V') cnvar vm1' { nvar = cnvar + 1 }
let spi2 = load (sp vm2' + i + 2) vm2'
let vm3' = store (sp vm2' + i) spi2 vm2'
let (n2, vm4') = app spi2 n1 vm3'
let vm5' = store (sp vm4' + i + 1) n2 vm4'
let (n3, vm6') = app (ord 'L') n2 vm5'
let vm7' = store (sp vm6' + i + 2) n3 vm6'
let vm8' = store (load (sp vm7' + i + 3) vm7' + 1) n3 vm7'
Just $ vm8' { sp = sp vm8' + i }
case fix 1 vm of
Just vm' -> vm'
Nothing -> do
let (f', vm1) = f vm
let (a', vm2) = a vm1
let vm3 = vm2 { sp = sp vm + d }
let
dst = trace ("LAZY: " ++ show f' ++ " " ++ show a')
$ load (sp vm + d + 1) vm
store (sp vm3) f' (store dst f' (store (dst + 1) a' vm3))
-- numberArg :: Int -> VM -> Int
-- numberArg n vm = load (fst (arg n vm) + 1) vm
rules :: Int -> VM -> VM
-- rules ch vm = case chr ch of
rules ch vm = case trace (show $ chr ch) (chr ch) of
'M' -> lvm 0 (arg 1) (arg 1)
'Y' -> lvm 0 (arg 1) (app (ord 'Y') 1)
'I' ->
if trace ("I: " ++ show (arg' 2 vm) ++ " " ++ show (load (sp vm + 1) vm))
$ arg' 2 vm
== load (sp vm + 1) vm
then lvm 1 (arg 1) (arg 1)
else lvm 1 (arg 1) (arg 2)
'F' -> do
let xv = arg' 2 vm
if isComb xv
then lvm 1 (com 'I') (arg 2)
else lvm 1 (wor $ load xv vm) (wor $ load (xv + 1) vm)
'f' -> do
let x = load (load (sp vm + 2) vm + 1) vm
let (v, vm') = if isComb x
then do
let cnvar = nvar vm
let (a, vm1) =
app (ord 'V') cnvar vm { sp = sp vm + 1, nvar = cnvar + 1 }
let (b, vm2) = app x a vm1
let (c, vm3) = app (ord 'L') b vm2
(c, store (load (sp vm3 + 1) vm3 + 1) c vm3)
else (x, vm { sp = sp vm + 1 })
store (sp vm') v vm'
'K' -> do
let (xv, _) = arg 1 vm
if isComb xv
then lvm 1 (com 'I') (wor xv)
else lvm 1 (wor $ load xv vm) (wor $ load (xv + 1) vm)
'T' -> lvm 1 (arg 2) (arg 1)
'D' -> lvm 2 (arg 1) (arg 2)
'B' -> lvm 2 (arg 1) (apparg 2 3)
'C' -> lvm 2 (apparg 1 3) (arg 2)
'R' -> lvm 2 (apparg 2 3) (arg 1)
':' -> lvm 2 (apparg 3 1) (arg 2)
'S' -> lvm 2 (apparg 1 3) (apparg 2 3)
'L' -> store (sp vm) (arg' 1 vm) vm
'V' -> do
let iter vm'@VM { sp } | sp == spTop = vm' { sp = -1 }
iter vm'@VM { sp } | load (load (sp + 1) vm') vm' == load sp vm' = vm'
iter vm'@VM { sp } =
let parent = load (sp + 1) vm'
left = load parent vm'
vm'' = if left == ord 'L'
then vm' { nvar = nvar vm' - 1 }
else store parent (load (left + 1) vm') vm'
in iter vm'' { sp = sp + 1 }
let vm1 = iter vm { sp = sp vm + 1 }
if sp vm1 == -1
then vm1
else do
let parentVal = load (load (sp vm1 + 1) vm1 + 1) vm1
let (a, vm2) = app (ord 'f') (load (sp vm1) vm1) vm1
lazy 0 (wor a) (wor parentVal) (store (sp vm2) parentVal vm2)
_ -> error "invalid combinator"
where lvm n f g = lazy n f g vm
-- num n = numberArg n vm
eval :: VM -> VM
eval vm@VM { sp } | sp == -1 = vm { sp = spTop }
eval vm@VM { sp } =
-- let x1 = trace ("x1: " ++ show (load sp vm)) $ load sp vm
let x1 =
trace ("x1: " ++ show (load sp vm) ++ ", sp: " ++ show (spTop - sp))
$ load sp vm
x2 = load x1 vm
vm1 = store (sp - 1) x2 vm { sp = sp - 1 }
x3 = load x2 vm1
vm2 = store (sp - 2) x3 vm1 { sp = sp - 2 }
in if isComb x1
then trace ("1" ++ dumpStack vm ++ "\n" ++ dumpMem vm)
(eval $ rules x1 vm)
else if isComb x2
then trace ("2" ++ dumpStack vm1 ++ "\n" ++ dumpMem vm1)
(eval $ rules x2 vm1)
else if isComb x3
then trace ("3" ++ dumpStack vm2 ++ "\n" ++ dumpMem vm2)
(eval $ rules x3 vm2)
else trace ("continue") (eval vm2)
-- else eval $ store (sp - 1) (load n vm) vm { sp = sp - 1 }
run :: Int -> VM -> VM
run i vm@VM { sp } = eval $ store sp i vm
hasVar0 :: Int -> Int -> VM -> Bool
hasVar0 db depth vm = do
let f = load db vm
let a = load (db + 1) vm
case chr f of
'V' -> a == depth
'L' -> hasVar0 a (depth + 1) vm
_ -> hasVar0 f depth vm || hasVar0 a depth vm
eta :: Int -> VM -> (Int, VM)
eta x vm = do
let f = load x vm
let a = load (x + 1) vm
if not (isComb f) && load a vm == ord 'V' && load (a + 1) vm == 0 && not
(hasVar0 f 0 vm)
then (f, vm)
else app (ord 'L') x vm
dbIndex :: Int -> Int -> VM -> (Int, VM)
dbIndex x depth vm = do
let f = load x vm
let a = load (x + 1) vm
case
trace (show x ++ "," ++ show depth ++ ":" ++ show f ++ " " ++ show a)
(chr f)
of
'V' -> app f (depth - 1 - a) vm
'L' -> do
let (idx, vm1) = dbIndex a (depth + 1) vm
eta idx vm1
_ -> do
let (f', vm1) = dbIndex f depth vm
let (a', vm2) = dbIndex a depth vm1
app f' a' vm2
clapp :: (Int, Int) -> VM -> (Int, VM)
clapp (f, a) vm = case (chr f, chr a) of
('K', 'I') -> vord 'F'
('B', 'K') -> vord 'D'
('C', 'I') -> vord 'T'
('D', 'I') -> vord 'K'
(_, 'I') | load f vm == ord 'B' -> (load (f + 1) vm, vm)
(_, 'I') | load f vm == ord 'R' -> app (ord 'T') (load (f + 1) vm) vm
(_, 'T') | load f vm == ord 'B' && load (f + 1) vm == ord 'C' -> vord ':'
(_, 'I') | load f vm == ord 'S' && load (f + 1) vm == ord 'I' -> vord 'M'
_ -> app f a vm
where vord c = (ord c, vm)
unDoubleVar :: Int -> Int -> VM -> (Int, VM)
unDoubleVar n db vm = do
let f = load db vm
let a = load (db + 1) vm
let qf = load f vm == ord 'V' && load (f + 1) vm == n
let qa = load a vm == ord 'V' && load (a + 1) vm == n
if f == ord 'V'
then (db, vm)
else if f == ord 'L'
then
let (uda, vm') = unDoubleVar (n + 1) a vm
in if uda == 0 then (0, vm') else app (ord 'L') uda vm'
else if qf && qa
then app (ord 'V') n vm
else if qf || qa
then (0, vm)
else
let (udf, vm' ) = unDoubleVar n f vm
(uda, vm'') = unDoubleVar n a vm'
in if udf == 0
then (0, vm')
else if uda == 0 then (0, vm'') else app udf uda vm''
recursive :: Int -> Int -> VM -> (Int, VM)
recursive f a vm = do
let f' = load (a + 1) vm
let (f'', vm') = unDoubleVar 0 f' vm
-- logical subtleties!
if f == ord 'M' && load a vm == ord 'L' && load f' vm /= ord 'V'
then if f'' /= 0 then app (ord 'L') f'' vm' else (0, vm')
else (0, vm)
combineK :: (Int, Int, Int, Int) -> VM -> (Int, VM)
combineK huh@(n1, d1, n2, d2) vm = do
if trace (dumpMem vm ++ "\n" ++ show huh) $ n1 == 0
then if n2 == 0
then clapp (d1, d2) vm
else if load (n2 + 1) vm /= 0
then
let (n, vm1) = clapp (ord 'B', d1) vm
in combineK (0, n, load n2 vm1, d2) vm1
else combineK (0, d1, load n2 vm, d2) vm
else if load (n1 + 1) vm /= 0
then if n2 == 0
then
let (n, vm1) = clapp (ord 'R', d2) vm
in combineK (0, n, load n1 vm1, d1) vm1
else if load (n2 + 1) vm /= 0
then
let (n, vm1) = combineK (0, ord 'S', load n1 vm, d1) vm
in combineK (load n1 vm, n, load n2 vm1, d2) vm1
else
let (n, vm1) = combineK (0, ord 'C', load n1 vm, d1) vm
in combineK (load n1 vm, n, load n2 vm1, d2) vm1
else if n2 == 0
then combineK (load n1 vm, d1, 0, d2) vm
else if load (n2 + 1) vm /= 0
then if load n2 vm == 0 && load (n2 + 1) vm /= 0 && d2 == ord 'I'
then (d1, vm) -- eta optimization
else
let (n, vm1) = combineK (0, ord 'B', load n1 vm, d1) vm
in combineK (load n1 vm, n, load n2 vm1, d2) vm1
else combineK (load n1 vm, d1, load n2 vm, d2) vm
zipN :: Int -> Int -> VM -> (Int, VM)
zipN nf na vm | nf == 0 = (na, vm)
zipN nf na vm | na == 0 = (nf, vm)
zipN nf na vm = do
let (z, vm1) = zipN (load nf vm) (load na vm) vm
app z ((load (nf + 1) vm1) .|. (load (na + 1) vm1)) vm1
convertK :: Int -> VM -> (Int, Int, VM)
convertK db vm = do
let f = load db vm
let a = load (db + 1) vm
case chr f of
'V' -> do
let iter n 0 vm' = (n, vm')
iter n i vm' = let (n', vm1) = app n 0 vm' in iter n' (i - 1) vm1
let (nf, vm1) = let (n, vm') = app 0 1 vm in iter n a vm'
(nf, ord 'I', vm1)
'L' -> do
let (na, ca, vm1) = convertK a vm
let pn = load na vm1
if na == 0
then let (n, vm2) = clapp (ord 'K', ca) vm1 in (0, n, vm2)
else if load (na + 1) vm1 == 0
then let (n, vm2) = combineK (0, ord 'K', pn, ca) vm1 in (pn, n, vm2)
else (pn, ca, vm1)
_ -> do
let (nf, cf, vm1) = convertK f vm
let (cf', a', vm2) =
let (ca', vm') = recursive cf a vm1
in if nf == 0
then if ca' /= 0 then (ord 'Y', ca', vm') else (cf, a, vm')
else (cf, a, vm1)
let (na, ca, vm3) = convertK a' vm2
let (pn, vm4) = zipN nf na vm3
let (n, vm5) = combineK (nf, cf', na, ca) vm4
(pn, n, vm5)
toCLK :: Int -> VM -> (Int, VM)
toCLK db vm = do
let (n, cl, vm1) = convertK db vm
if n == 0 then (cl, vm1) else error "term not closed"
resolveExpression :: Int -> VM -> Expression
resolveExpression n _ | isComb n = error "unexpected combinator"
resolveExpression n vm = do
let f = load n vm
let a = load (n + 1) vm
case chr f of
'V' -> Idx a
'L' -> Abs $ resolveExpression a vm
_ -> App (resolveExpression f vm) (resolveExpression a vm)
parseExpression :: Expression -> VM -> IO (Int, VM)
parseExpression (Abs t) vm = do
(t', vm1) <- parseExpression t vm
pure $ app (ord 'L') t' vm1
parseExpression (App l r) vm = do
(l', vm1) <- parseExpression l vm
(r', vm2) <- parseExpression r vm1
pure $ app l' r' vm2
parseExpression (Idx i) vm = do
pure $ app (ord 'V') i vm
go :: String -> IO ()
go s = do
let vm = new
let term = parseBLC s
(db, vm1) <- parseExpression term vm
let (cl, vm2) = toCLK db vm1
let (i, vm3) = app (ord 'V') (nvar vm2) vm2 { nvar = nvar vm2 + 1 }
let (j, vm4) = app cl i vm3
let (k, vm5) = app (ord 'L') j vm4
putStrLn $ dumpStack vm2
let res = run k vm5
let (idx, fin) = dbIndex (load spTop res) 0 res
print $ resolveExpression idx fin
reduce :: Expression -> Expression
reduce e = e
|