@effekt-lang/effekt 0.64.0 → 0.66.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/effekt +0 -0
- package/libraries/chez/cps/effekt.ss +8 -12
- package/libraries/common/array.effekt +18 -3
- package/libraries/common/bytearray.effekt +11 -3
- package/libraries/common/effekt.effekt +25 -24
- package/libraries/common/io/channel.effekt +27 -20
- package/libraries/common/list.effekt +12 -0
- package/libraries/common/ref.effekt +7 -3
- package/libraries/common/string.effekt +11 -3
- package/libraries/common/test.effekt +2 -2
- package/libraries/js/effekt_runtime.js +85 -59
- package/licenses/eclipse public license, version 2.0 - epl-2.0.html +4 -4
- package/licenses/mit license - mit-license.html +21 -21
- package/licenses/the bsd license - bsd-license.html +20 -20
- package/licenses/the mit license - mit.html +21 -21
- package/package.json +1 -1
package/bin/effekt
CHANGED
|
Binary file
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
[r (diff-ref realDiff)]
|
|
48
48
|
[oldValue (ref-value r)])
|
|
49
49
|
(ref-value-set! r (diff-value realDiff))
|
|
50
|
+
(ref-generation-set! r (diff-generation realDiff))
|
|
50
51
|
(set-box! n (make-diff r oldValue (ref-generation r) currentDiff))
|
|
51
52
|
(applyDiffs currentDiff (cdr diffs)))]))
|
|
52
53
|
|
|
@@ -60,10 +61,11 @@
|
|
|
60
61
|
[store (snap-store snap)]
|
|
61
62
|
[storeRoot (store-root store)])
|
|
62
63
|
(reroot snapRoot storeRoot)
|
|
63
|
-
(store-root-set!
|
|
64
|
+
(store-root-set! store snapRoot)
|
|
65
|
+
(store-generation-set! store (+ (snap-generation snap) 1))))
|
|
64
66
|
|
|
65
67
|
; Cont a = a, MetaCont -> #
|
|
66
|
-
; Prompt =
|
|
68
|
+
; Prompt = Symbol
|
|
67
69
|
|
|
68
70
|
; MetaCont = Cont * Prompt * Store | Snapshot * MetaCont?
|
|
69
71
|
; Holds a "copy" of k, as well as its prompt and store
|
|
@@ -102,14 +104,8 @@
|
|
|
102
104
|
; Ref | MetaCont -> void
|
|
103
105
|
(define (deallocate _) (void))
|
|
104
106
|
|
|
105
|
-
(define _prompt 1)
|
|
106
|
-
(define (new-prompt)
|
|
107
|
-
(let ([prompt _prompt])
|
|
108
|
-
(set! _prompt (+ prompt 1))
|
|
109
|
-
prompt))
|
|
110
|
-
|
|
111
107
|
(define (top-level-k x _) x)
|
|
112
|
-
(define top-level-ks (make-meta-cont top-level-k
|
|
108
|
+
(define top-level-ks (make-meta-cont top-level-k (gensym "toplevel") (create-store) '()))
|
|
113
109
|
|
|
114
110
|
; a, MetaCont -> #
|
|
115
111
|
(define (return x ks)
|
|
@@ -121,7 +117,7 @@
|
|
|
121
117
|
|
|
122
118
|
; Program Prompt b, MetaCont, Cont b -> #
|
|
123
119
|
(define (reset prog ks k)
|
|
124
|
-
(let ([prompt (
|
|
120
|
+
(let ([prompt (gensym)])
|
|
125
121
|
(meta-cont-cont-set! ks k)
|
|
126
122
|
(prog prompt (make-meta-cont return prompt (create-store) ks) return)))
|
|
127
123
|
|
|
@@ -132,7 +128,7 @@
|
|
|
132
128
|
[snap (snapshot (meta-cont-store below))])
|
|
133
129
|
(meta-cont-store-set! below snap)
|
|
134
130
|
(meta-cont-rest-set! below above)
|
|
135
|
-
(if (
|
|
131
|
+
(if (symbol=? (meta-cont-prompt below) p)
|
|
136
132
|
(values below new-below)
|
|
137
133
|
(worker below new-below))))
|
|
138
134
|
(worker '() ks))
|
|
@@ -166,4 +162,4 @@
|
|
|
166
162
|
|
|
167
163
|
; Block b -> b
|
|
168
164
|
(define (run-top-level p)
|
|
169
|
-
(p top-level-ks top-level-k))
|
|
165
|
+
(p top-level-ks top-level-k))
|
|
@@ -677,18 +677,27 @@ def feed[T, R](array: Array[T]) { reader: () => R / read[T] }: R = {
|
|
|
677
677
|
}
|
|
678
678
|
}
|
|
679
679
|
|
|
680
|
+
/// Collects elements emitted by a push stream producer `stream` into a new array,
|
|
681
|
+
/// discarding the result of the producer.
|
|
682
|
+
/// The provided capacity is a hint for the expected number of elements.
|
|
683
|
+
/// It must be strictly larger than zero.
|
|
684
|
+
def collect[A](capacity: Int) { stream: () => Unit / emit[A] }: Array[A] =
|
|
685
|
+
returning::collect[A, Unit](capacity){stream}.second
|
|
686
|
+
|
|
680
687
|
/// Collects elements emitted by a push stream producer `stream` into a new array,
|
|
681
688
|
/// discarding the result of the producer.
|
|
682
689
|
def collect[A] { stream: () => Unit / emit[A] }: Array[A] =
|
|
683
|
-
|
|
690
|
+
collect[A](4){stream}
|
|
684
691
|
|
|
685
692
|
namespace returning {
|
|
686
693
|
|
|
687
694
|
/// Collects elements emitted by a push stream producer `stream` into a new array,
|
|
688
695
|
/// returning the result of the producer as well as the collected array.
|
|
689
|
-
|
|
696
|
+
/// The provided capacity is a hint for the expected number of elements.
|
|
697
|
+
/// It must be strictly larger than zero.
|
|
698
|
+
def collect[A, R](capacity: Int) { stream: () => R / emit[A] }: (R, Array[A]) = {
|
|
690
699
|
var i = 0
|
|
691
|
-
var a = unsafeAllocate(
|
|
700
|
+
var a = unsafeAllocate(capacity)
|
|
692
701
|
try {
|
|
693
702
|
(stream(), a.resize(i))
|
|
694
703
|
} with emit[A] { (v) =>
|
|
@@ -698,4 +707,10 @@ namespace returning {
|
|
|
698
707
|
resume(())
|
|
699
708
|
}
|
|
700
709
|
}
|
|
710
|
+
|
|
711
|
+
/// Collects elements emitted by a push stream producer `stream` into a new array,
|
|
712
|
+
/// returning the result of the producer as well as the collected array.
|
|
713
|
+
def collect[A, R] { stream: () => R / emit[A] }: (R, Array[A]) =
|
|
714
|
+
returning::collect[A, R](4){stream}
|
|
715
|
+
|
|
701
716
|
}
|
|
@@ -300,13 +300,17 @@ def feed[R](bytes: ByteArray) { reader: () => R / read[Byte] }: R = {
|
|
|
300
300
|
}
|
|
301
301
|
}
|
|
302
302
|
|
|
303
|
+
def collect(capacity: Int) { stream: () => Unit / emit[Byte] }: ByteArray =
|
|
304
|
+
returning::collect[Unit](capacity){stream}.second
|
|
305
|
+
|
|
303
306
|
def collect { stream: () => Unit / emit[Byte] }: ByteArray =
|
|
304
|
-
|
|
307
|
+
collect(4){stream}
|
|
305
308
|
|
|
306
309
|
namespace returning {
|
|
307
|
-
|
|
310
|
+
|
|
311
|
+
def collect[R](capacity: Int) { stream: () => R / emit[Byte] }: (R, ByteArray) = {
|
|
308
312
|
var i = 0
|
|
309
|
-
var a = allocate(
|
|
313
|
+
var a = allocate(capacity)
|
|
310
314
|
try {
|
|
311
315
|
(stream(), a.resize(i))
|
|
312
316
|
} with emit[Byte] { (v) =>
|
|
@@ -316,4 +320,8 @@ namespace returning {
|
|
|
316
320
|
resume(())
|
|
317
321
|
}
|
|
318
322
|
}
|
|
323
|
+
|
|
324
|
+
def collect[R] { stream: () => R / emit[Byte] }: (R, ByteArray) =
|
|
325
|
+
returning::collect(4){stream}
|
|
326
|
+
|
|
319
327
|
}
|
|
@@ -139,16 +139,20 @@ extern def show[R](value: R) at {}: String =
|
|
|
139
139
|
chez "(show_impl ${value})"
|
|
140
140
|
llvm "ret %Pos poison"
|
|
141
141
|
|
|
142
|
+
extern def genericShow[R](value: R) at {}: String =
|
|
143
|
+
js "$effekt.show(${value})"
|
|
144
|
+
chez "(show_impl ${value})"
|
|
145
|
+
|
|
142
146
|
// Strings
|
|
143
147
|
// =======
|
|
144
|
-
extern def
|
|
148
|
+
extern def infixPlusPlus(s1: String, s2: String) at {}: String =
|
|
145
149
|
js "${s1} + ${s2}"
|
|
146
150
|
chez "(string-append ${s1} ${s2})"
|
|
147
151
|
llvm """
|
|
148
152
|
%spz = call %Pos @c_bytearray_concatenate(%Pos ${s1}, %Pos ${s2})
|
|
149
153
|
ret %Pos %spz
|
|
150
154
|
"""
|
|
151
|
-
vm "effekt::
|
|
155
|
+
vm "effekt::infixPlusPlus(String, String)"
|
|
152
156
|
|
|
153
157
|
extern def length(str: String) at {}: Int =
|
|
154
158
|
js "${str}.length"
|
|
@@ -231,13 +235,10 @@ def println(o: Ordering): Unit = println(o.show)
|
|
|
231
235
|
// ==============
|
|
232
236
|
|
|
233
237
|
/// Structural equality: Not available in the LLVM backend
|
|
234
|
-
extern def
|
|
238
|
+
extern def genericEquals[R](x: R, y: R) at {}: Bool =
|
|
235
239
|
js "$effekt.equals(${x}, ${y})"
|
|
236
240
|
chez "(equal_impl ${x} ${y})"
|
|
237
241
|
|
|
238
|
-
def differsFrom[R](x: R, y: R): Bool =
|
|
239
|
-
not(equals(x, y))
|
|
240
|
-
|
|
241
242
|
// TODO fix this in the pattern matching compiler
|
|
242
243
|
extern def infixEq(x: Unit, y: Unit) at {}: Bool =
|
|
243
244
|
default { true }
|
|
@@ -345,29 +346,29 @@ extern def infixNeq(x: Byte, y: Byte) at {}: Bool =
|
|
|
345
346
|
|
|
346
347
|
// Math ops
|
|
347
348
|
// ========
|
|
348
|
-
extern def
|
|
349
|
+
extern def infixPlus(x: Int, y: Int) at {}: Int =
|
|
349
350
|
js "(${x} + ${y})"
|
|
350
351
|
chez "(+ ${x} ${y})"
|
|
351
352
|
llvm "%z = add %Int ${x}, ${y} ret %Int %z"
|
|
352
|
-
vm "effekt::
|
|
353
|
+
vm "effekt::infixPlus(Int, Int)"
|
|
353
354
|
|
|
354
|
-
extern def
|
|
355
|
+
extern def infixStar(x: Int, y: Int) at {}: Int =
|
|
355
356
|
js "(${x} * ${y})"
|
|
356
357
|
chez "(* ${x} ${y})"
|
|
357
358
|
llvm "%z = mul %Int ${x}, ${y} ret %Int %z"
|
|
358
|
-
vm "effekt::
|
|
359
|
+
vm "effekt::infixStar(Int, Int)"
|
|
359
360
|
|
|
360
|
-
extern def
|
|
361
|
+
extern def infixSlash(x: Int, y: Int) at {}: Int =
|
|
361
362
|
js "Math.floor(${x} / ${y})"
|
|
362
363
|
chez "(floor (/ ${x} ${y}))"
|
|
363
364
|
llvm "%z = sdiv %Int ${x}, ${y} ret %Int %z"
|
|
364
|
-
vm "effekt::
|
|
365
|
+
vm "effekt::infixSlash(Int, Int)"
|
|
365
366
|
|
|
366
|
-
extern def
|
|
367
|
+
extern def infixMinus(x: Int, y: Int) at {}: Int =
|
|
367
368
|
js "(${x} - ${y})"
|
|
368
369
|
chez "(- ${x} ${y})"
|
|
369
370
|
llvm "%z = sub %Int ${x}, ${y} ret %Int %z"
|
|
370
|
-
vm "effekt::
|
|
371
|
+
vm "effekt::infixMinus(Int, Int)"
|
|
371
372
|
|
|
372
373
|
extern def mod(x: Int, y: Int) at {}: Int =
|
|
373
374
|
js "(${x} % ${y})"
|
|
@@ -375,29 +376,29 @@ extern def mod(x: Int, y: Int) at {}: Int =
|
|
|
375
376
|
llvm "%z = srem %Int ${x}, ${y} ret %Int %z"
|
|
376
377
|
vm "effekt::mod(Int, Int)"
|
|
377
378
|
|
|
378
|
-
extern def
|
|
379
|
+
extern def infixPlus(x: Double, y: Double) at {}: Double =
|
|
379
380
|
js "(${x} + ${y})"
|
|
380
381
|
chez "(+ ${x} ${y})"
|
|
381
382
|
llvm "%z = fadd %Double ${x}, ${y} ret %Double %z"
|
|
382
|
-
vm "effekt::
|
|
383
|
+
vm "effekt::infixPlus(Double, Double)"
|
|
383
384
|
|
|
384
|
-
extern def
|
|
385
|
+
extern def infixStar(x: Double, y: Double) at {}: Double =
|
|
385
386
|
js "(${x} * ${y})"
|
|
386
387
|
chez "(* ${x} ${y})"
|
|
387
388
|
llvm "%z = fmul %Double ${x}, ${y} ret %Double %z"
|
|
388
|
-
vm "effekt::
|
|
389
|
+
vm "effekt::infixStar(Double, Double)"
|
|
389
390
|
|
|
390
|
-
extern def
|
|
391
|
+
extern def infixMinus(x: Double, y: Double) at {}: Double =
|
|
391
392
|
js "(${x} - ${y})"
|
|
392
393
|
chez "(- ${x} ${y})"
|
|
393
394
|
llvm "%z = fsub %Double ${x}, ${y} ret %Double %z"
|
|
394
|
-
vm "effekt::
|
|
395
|
+
vm "effekt::infixMinus(Double, Double)"
|
|
395
396
|
|
|
396
|
-
extern def
|
|
397
|
+
extern def infixSlash(x: Double, y: Double) at {}: Double =
|
|
397
398
|
js "(${x} / ${y})"
|
|
398
399
|
chez "(fl/ ${x} ${y})"
|
|
399
400
|
llvm "%z = fdiv %Double ${x}, ${y} ret %Double %z"
|
|
400
|
-
vm "effekt::
|
|
401
|
+
vm "effekt::infixSlash(Double, Double)"
|
|
401
402
|
|
|
402
403
|
extern def cos(x: Double) at {}: Double =
|
|
403
404
|
js "Math.cos(${x})"
|
|
@@ -661,10 +662,10 @@ extern def not(b: Bool) at {}: Bool =
|
|
|
661
662
|
"""
|
|
662
663
|
vm "effekt::not(Bool)"
|
|
663
664
|
|
|
664
|
-
def
|
|
665
|
+
def infixBarBar { first: => Bool } { second: => Bool }: Bool =
|
|
665
666
|
if (first()) true else second()
|
|
666
667
|
|
|
667
|
-
def
|
|
668
|
+
def infixAmpAmp { first: => Bool } { second: => Bool }: Bool =
|
|
668
669
|
if (first()) second() else false
|
|
669
670
|
|
|
670
671
|
|
|
@@ -3,29 +3,34 @@ module io/channel
|
|
|
3
3
|
import io
|
|
4
4
|
import io/signal
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
type Node[A, B] {
|
|
7
|
+
Nope()
|
|
8
|
+
Node(this: Signal[A, B], rest: Ref[Node[A, B]])
|
|
9
|
+
}
|
|
7
10
|
|
|
8
|
-
type Sender[A, B] = Ref[Node[A, B]]
|
|
9
|
-
type Receiver[A, B] = Ref[Node[A, B]]
|
|
11
|
+
type Sender[A, B] = Ref[Ref[Node[A, B]]]
|
|
12
|
+
type Receiver[A, B] = Ref[Ref[Node[A, B]]]
|
|
10
13
|
|
|
11
14
|
/// Creates a new rendezvous channel and returns the receiving and the sending end.
|
|
12
15
|
/// Every send must be matched by exactly one receive upon which two values are exchanged.
|
|
13
16
|
def channel[A, B](): (Sender[A, B], Receiver[A, B]) = {
|
|
14
|
-
val node =
|
|
17
|
+
val node = ref(Nope())
|
|
15
18
|
(ref(node), ref(node))
|
|
16
19
|
}
|
|
17
20
|
|
|
18
21
|
/// Sends a value to a channel and blocks until a receiver arrives.
|
|
19
22
|
def send[A, B](sender: Sender[A, B], value: A): B = {
|
|
20
|
-
val
|
|
21
|
-
rest.get() match {
|
|
22
|
-
case
|
|
23
|
-
val
|
|
24
|
-
rest
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
val current = sender.get()
|
|
24
|
+
val (this, rest) = current.get() match {
|
|
25
|
+
case Nope() =>
|
|
26
|
+
val this = signal()
|
|
27
|
+
val rest = ref(Nope())
|
|
28
|
+
current.set(Node(this, rest))
|
|
29
|
+
(this, rest)
|
|
30
|
+
case Node(this, rest) =>
|
|
31
|
+
(this, rest)
|
|
28
32
|
}
|
|
33
|
+
sender.set(rest)
|
|
29
34
|
// TODO avoid yield
|
|
30
35
|
yield()
|
|
31
36
|
this.unsafeFire(value)
|
|
@@ -33,15 +38,17 @@ def send[A, B](sender: Sender[A, B], value: A): B = {
|
|
|
33
38
|
|
|
34
39
|
/// Receives a value from a channel and blocks until a sender arrives.
|
|
35
40
|
def receive[A, B](receiver: Receiver[A, B], value: B): A = {
|
|
36
|
-
val
|
|
37
|
-
rest.get() match {
|
|
38
|
-
case
|
|
39
|
-
val
|
|
40
|
-
rest
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
val current = receiver.get()
|
|
42
|
+
val (this, rest) = current.get() match {
|
|
43
|
+
case Nope() =>
|
|
44
|
+
val this = signal()
|
|
45
|
+
val rest = ref(Nope())
|
|
46
|
+
current.set(Node(this, rest))
|
|
47
|
+
(this, rest)
|
|
48
|
+
case Node(this, rest) =>
|
|
49
|
+
(this, rest)
|
|
44
50
|
}
|
|
51
|
+
receiver.set(rest)
|
|
45
52
|
// TODO avoid yield
|
|
46
53
|
yield()
|
|
47
54
|
this.unsafeWait(value)
|
|
@@ -419,6 +419,18 @@ def reverseOnto[A](l: List[A], other: List[A]): List[A] =
|
|
|
419
419
|
def append[A](l: List[A], other: List[A]): List[A] =
|
|
420
420
|
l.reverse.reverseOnto(other)
|
|
421
421
|
|
|
422
|
+
/// `l ++ other` is an alias for `l.append(other)`.
|
|
423
|
+
///
|
|
424
|
+
/// Example:
|
|
425
|
+
/// ```
|
|
426
|
+
/// > [1, 2, 3] ++ [4, 5, 6]
|
|
427
|
+
/// [1, 2, 3, 4, 5, 6]
|
|
428
|
+
/// ```
|
|
429
|
+
///
|
|
430
|
+
/// O(N)
|
|
431
|
+
def infixPlusPlus[A](l: List[A], other: List[A]): List[A] =
|
|
432
|
+
append(l, other)
|
|
433
|
+
|
|
422
434
|
/// Flatten a list of lists into a single list.
|
|
423
435
|
///
|
|
424
436
|
/// Examples:
|
|
@@ -43,7 +43,7 @@ extern def unsafeAllocate[T]() at global: Ref[T] =
|
|
|
43
43
|
store i64 0, ptr %ref, align 8
|
|
44
44
|
store ptr @c_ref_erase_field, ptr %refEraser, align 8
|
|
45
45
|
store i64 0, ptr %fieldTag, align 8
|
|
46
|
-
store ptr null, ptr %fieldData_pointer, align 8
|
|
46
|
+
store ptr null, ptr %fieldData_pointer, align 8
|
|
47
47
|
|
|
48
48
|
%refWrap = insertvalue %Pos { i64 0, ptr poison }, ptr %ref, 1
|
|
49
49
|
ret %Pos %refWrap
|
|
@@ -99,7 +99,7 @@ extern def set[T](ref: Ref[T], value: T) at global: Unit =
|
|
|
99
99
|
|
|
100
100
|
tail call void @erasePositive(%Pos %field)
|
|
101
101
|
store %Pos ${value}, ptr %field_pointer
|
|
102
|
-
tail call void @erasePositive(%Pos ${ref})
|
|
102
|
+
tail call void @erasePositive(%Pos ${ref})
|
|
103
103
|
|
|
104
104
|
ret %Pos zeroinitializer
|
|
105
105
|
"""
|
|
@@ -107,4 +107,8 @@ extern def set[T](ref: Ref[T], value: T) at global: Unit =
|
|
|
107
107
|
|
|
108
108
|
/// Modifies the inner contents of the `ref` with a function `f`
|
|
109
109
|
def map[T](ref: Ref[T]) { f: T => T }: Unit =
|
|
110
|
-
ref.set(f(ref.get))
|
|
110
|
+
ref.set(f(ref.get))
|
|
111
|
+
|
|
112
|
+
/// `r := v` serves as an alias for `r.set(v)`
|
|
113
|
+
def infixColonEq[T](ref: Ref[T], v: T): Unit =
|
|
114
|
+
ref.set(v)
|
|
@@ -251,12 +251,20 @@ def each(string: String): Unit / emit[Char] =
|
|
|
251
251
|
}
|
|
252
252
|
}
|
|
253
253
|
|
|
254
|
+
def collect(capacity: Int) { stream: () => Unit / emit[Char] }: String =
|
|
255
|
+
returning::collect[Unit](capacity){stream}.second
|
|
256
|
+
|
|
254
257
|
def collect { stream: () => Unit / emit[Char] }: String =
|
|
255
|
-
|
|
258
|
+
collect(4){stream}
|
|
256
259
|
|
|
257
260
|
namespace returning {
|
|
258
|
-
|
|
259
|
-
|
|
261
|
+
|
|
262
|
+
def collect[R](capacity: Int) { stream: () => R / emit[Char] }: (R, String) = {
|
|
263
|
+
val (result, bytes) = bytearray::returning::collect[R](capacity) { encodeUTF8 { stream } }
|
|
260
264
|
(result, bytes.toString)
|
|
261
265
|
}
|
|
266
|
+
|
|
267
|
+
def collect[R] { stream: () => R / emit[Char] }: (R, String) =
|
|
268
|
+
returning::collect(4){stream}
|
|
269
|
+
|
|
262
270
|
}
|
|
@@ -46,10 +46,10 @@ def assert(obtained: String, expected: String): Unit / { Assertion, Formatted }
|
|
|
46
46
|
assertEqual(obtained, expected) { (x, y) => x == y } { x => show(x) }
|
|
47
47
|
|
|
48
48
|
def assertEqual[A](obtained: A, expected: A, msg: String): Unit / Assertion =
|
|
49
|
-
assertEqual(obtained, expected, msg) { (x, y) => x.
|
|
49
|
+
assertEqual(obtained, expected, msg) { (x, y) => x.genericEquals(y) }
|
|
50
50
|
|
|
51
51
|
def assertEqual[A](obtained: A, expected: A): Unit / { Assertion, Formatted } =
|
|
52
|
-
assertEqual(obtained, expected) { (x, y) => x.
|
|
52
|
+
assertEqual(obtained, expected) { (x, y) => x.genericEquals(y) } { x => x.genericShow }
|
|
53
53
|
|
|
54
54
|
def assertEqual[A](obtained: A, expected: A, msg: String) { equals: (A, A) => Bool }: Unit / Assertion =
|
|
55
55
|
do assert(equals(obtained, expected), msg)
|
|
@@ -1,43 +1,60 @@
|
|
|
1
|
-
|
|
2
|
-
//
|
|
3
|
-
// get: O(1)
|
|
4
|
-
// set: O(1)
|
|
5
|
-
// capture: O(1)
|
|
6
|
-
// restore: O(|write operations since capture|)
|
|
1
|
+
|
|
2
|
+
// Sentinel: a node whose .value is Mem is the current root.
|
|
7
3
|
const Mem = null
|
|
8
4
|
|
|
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
|
-
|
|
5
|
+
// reusable buffer for rerooting
|
|
6
|
+
const _rerootPath = []
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A mutable reference inside an `Arena`.
|
|
10
|
+
*/
|
|
11
|
+
class Ref {
|
|
12
|
+
constructor(v, s) {
|
|
13
|
+
this.value = v;
|
|
14
|
+
this.generation = s.generation;
|
|
15
|
+
this.store = s;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
set(v) {
|
|
19
|
+
const s = this.store;
|
|
20
|
+
const r_gen = this.generation;
|
|
21
|
+
const s_gen = s.generation;
|
|
22
|
+
if (r_gen === s_gen) {
|
|
23
|
+
this.value = v;
|
|
24
|
+
} else {
|
|
25
|
+
const root = { value: Mem };
|
|
26
|
+
s.root.value = { ref: this, value: this.value, generation: r_gen, root };
|
|
27
|
+
|
|
28
|
+
s.root = root;
|
|
29
|
+
this.value = v;
|
|
30
|
+
this.generation = s_gen;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* A snapshottable arena: a bag of `Ref`s whose collective state can be
|
|
37
|
+
* captured in O(1) and restored in O(#writes since capture).
|
|
38
|
+
*/
|
|
39
|
+
class Arena {
|
|
40
|
+
constructor() {
|
|
41
|
+
this.root = { value: Mem };
|
|
42
|
+
this.generation = 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Allocate a new reference with initial value v.
|
|
47
|
+
*/
|
|
48
|
+
fresh(v) {
|
|
49
|
+
return new Ref(v, this);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Region support (not implemented!).
|
|
54
|
+
*/
|
|
55
|
+
newRegion() {
|
|
56
|
+
return this;
|
|
57
|
+
}
|
|
41
58
|
}
|
|
42
59
|
|
|
43
60
|
function snapshot(s) {
|
|
@@ -46,34 +63,43 @@ function snapshot(s) {
|
|
|
46
63
|
return snap
|
|
47
64
|
}
|
|
48
65
|
|
|
49
|
-
function reroot(
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
66
|
+
function reroot(target) {
|
|
67
|
+
// 1. Walk from target toward the Mem node, collecting the path into `_rerootPath`
|
|
68
|
+
_rerootPath.length = 0
|
|
69
|
+
let cur = target
|
|
70
|
+
while (cur.value !== Mem) {
|
|
71
|
+
_rerootPath.push(cur)
|
|
72
|
+
cur = cur.value.root
|
|
73
|
+
}
|
|
74
|
+
// cur is now Mem (the current root)
|
|
75
|
+
|
|
76
|
+
// 2. Walk back from current root toward target,
|
|
77
|
+
// reverse each edge, restore each ref.
|
|
78
|
+
for (let i = _rerootPath.length - 1; i >= 0; i--) {
|
|
79
|
+
const node = _rerootPath[i]
|
|
80
|
+
const diff = node.value // forward diff
|
|
81
|
+
const r = diff.ref
|
|
82
|
+
cur.value = { ref: r, value: r.value, generation: r.generation, root: node }
|
|
83
|
+
r.value = diff.value // restore ref's old value
|
|
84
|
+
r.generation = diff.generation // ... and old generation
|
|
85
|
+
node.value = Mem
|
|
86
|
+
cur = node
|
|
87
|
+
}
|
|
62
88
|
}
|
|
63
89
|
|
|
64
90
|
function restore(store, snap) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
91
|
+
if (snap.root.value !== Mem) {
|
|
92
|
+
// fast path: continuation is resumed immediately with no writes in between
|
|
93
|
+
reroot(snap.root)
|
|
94
|
+
store.root = snap.root
|
|
95
|
+
}
|
|
68
96
|
store.generation = snap.generation + 1
|
|
69
97
|
}
|
|
70
98
|
|
|
71
99
|
// Common Runtime
|
|
72
100
|
// --------------
|
|
73
|
-
let _prompt = 1;
|
|
74
|
-
|
|
75
101
|
const TOPLEVEL_K = (x, ks) => { throw { computationIsDone: true, result: x } }
|
|
76
|
-
const TOPLEVEL_KS = { prompt:
|
|
102
|
+
const TOPLEVEL_KS = { stack: null, prompt: Symbol("toplevel"), arena: new Arena(), rest: null }
|
|
77
103
|
|
|
78
104
|
function THUNK(f) {
|
|
79
105
|
f.thunk = true
|
|
@@ -92,9 +118,9 @@ const RETURN = (x, ks) => ks.rest.stack(x, ks.rest)
|
|
|
92
118
|
|
|
93
119
|
// HANDLE(ks, ks, (p, ks, k) => { STMT })
|
|
94
120
|
function RESET(prog, ks, k) {
|
|
95
|
-
const prompt =
|
|
121
|
+
const prompt = Symbol(); // gensym
|
|
96
122
|
const rest = { stack: k, prompt: ks.prompt, arena: ks.arena, rest: ks.rest }
|
|
97
|
-
return prog(prompt, { prompt, arena: Arena(
|
|
123
|
+
return prog(prompt, { stack: null, prompt, arena: new Arena(), rest }, RETURN)
|
|
98
124
|
}
|
|
99
125
|
|
|
100
126
|
function SHIFT(p, body, ks, k) {
|