@effekt-lang/effekt 0.47.0 → 0.48.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 CHANGED
Binary file
@@ -4,6 +4,7 @@ import effekt
4
4
  import exception
5
5
  import list
6
6
  import option
7
+ import stream
7
8
 
8
9
  /// A mutable 0-indexed fixed-sized array.
9
10
  extern type Array[T]
@@ -641,3 +642,55 @@ def println(l: Array[Int]): Unit = println(show(l))
641
642
  def println(l: Array[Double]): Unit = println(show(l))
642
643
  def println(l: Array[Bool]): Unit = println(show(l))
643
644
  def println(l: Array[String]): Unit = println(show(l))
645
+
646
+
647
+ // Streaming
648
+ // ---------
649
+
650
+ /// Turns an `array` into a producer of a push stream
651
+ /// by emitting each contained value from 0 to length - 1.
652
+ def each[T](array: Array[T]): Unit / emit[T] = {
653
+ val n = array.size
654
+ def go(i: Int): Unit = {
655
+ if (i < n) {
656
+ do emit(array.unsafeGet(i))
657
+ go(i + 1)
658
+ }
659
+ }
660
+ go(0)
661
+ }
662
+
663
+ def feed[T, R](array: Array[T]) { reader: () => R / read[T] }: R = {
664
+ var i = 0
665
+ try {
666
+ reader()
667
+ } with read[T] {
668
+ resume {
669
+ if (i < array.size) {
670
+ val c = i
671
+ i = c + 1
672
+ array.unsafeGet(c)
673
+ } else {
674
+ do stop()
675
+ }
676
+ }
677
+ }
678
+ }
679
+
680
+ def collect[A] { stream: () => Unit / emit[A] }: Array[A] =
681
+ returning::collect[A, Unit]{stream}.second
682
+
683
+ namespace returning {
684
+ def collect[A, R] { stream: () => R / emit[A] }: (R, Array[A]) = {
685
+ var i = 0
686
+ var a = allocate(1)
687
+ try {
688
+ (stream(), a.resize(i))
689
+ } with emit[A] { (v) =>
690
+ if (i >= a.size) { a = a.resize(2 * a.size) }
691
+ a.unsafeSet(i, v)
692
+ i = i + 1
693
+ resume(())
694
+ }
695
+ }
696
+ }
@@ -62,11 +62,11 @@ def arrayBuffer[T](initialCapacity: Int): Buffer[T] at global = {
62
62
  tail.set(mod(tail.get + 1, initialCapacity))
63
63
  }
64
64
  }
65
- buffer
65
+ box buffer
66
66
  }
67
67
  def refBuffer[T](): Buffer[T] at global = {
68
68
  val content = ref(None())
69
- new Buffer[T] {
69
+ box new Buffer[T] {
70
70
  def capacity() = if (content.get.isEmpty) 1 else 0
71
71
  def full?() = content.get.isDefined
72
72
  def empty?() = isEmpty(content.get)
@@ -1,5 +1,8 @@
1
1
  module bytearray
2
2
 
3
+ import effekt
4
+ import stream
5
+
3
6
  /**
4
7
  * A memory managed, mutable, fixed-length array of bytes.
5
8
  *
@@ -200,3 +203,55 @@ def compareStringBytes(left: String, right: String): Ordering = {
200
203
  val r = right.fromString
201
204
  compareByteArray(l, r)
202
205
  }
206
+
207
+
208
+ // Streaming
209
+ // ---------
210
+
211
+ /// Turns `bytes` into a producer of a push stream
212
+ /// by emitting each contained value from 0 to length - 1.
213
+ def each(bytes: ByteArray): Unit / emit[Byte] = {
214
+ val n = bytes.size
215
+ def go(i: Int): Unit = {
216
+ if (i < n) {
217
+ do emit(bytes.unsafeGet(i))
218
+ go(i + 1)
219
+ }
220
+ }
221
+ go(0)
222
+ }
223
+
224
+ def feed[R](bytes: ByteArray) { reader: () => R / read[Byte] }: R = {
225
+ var i = 0
226
+ try {
227
+ reader()
228
+ } with read[Byte] {
229
+ resume {
230
+ if (i < bytes.size) {
231
+ val c = i
232
+ i = c + 1
233
+ bytes.unsafeGet(c)
234
+ } else {
235
+ do stop()
236
+ }
237
+ }
238
+ }
239
+ }
240
+
241
+ def collect { stream: () => Unit / emit[Byte] }: ByteArray =
242
+ returning::collect[Unit]{stream}.second
243
+
244
+ namespace returning {
245
+ def collect[R] { stream: () => R / emit[Byte] }: (R, ByteArray) = {
246
+ var i = 0
247
+ var a = allocate(1)
248
+ try {
249
+ (stream(), a.resize(i))
250
+ } with emit[Byte] { (v) =>
251
+ if (i >= a.size) { a = a.resize(2 * a.size) }
252
+ a.unsafeSet(i, v)
253
+ i = i + 1
254
+ resume(())
255
+ }
256
+ }
257
+ }
@@ -5,6 +5,7 @@ import effekt
5
5
  import exception
6
6
  import option
7
7
  import result
8
+ import stream
8
9
 
9
10
 
10
11
  /// Checks if the given character is an ASCII whitespace
@@ -175,3 +176,89 @@ def utf16UnitCount(codepoint: Char): Int = codepoint match {
175
176
  extern def charWidth(c: Char) at {}: Int =
176
177
  // JavaScript strings are UTF-16 where every unicode character after 0xffff takes two units
177
178
  js "(${c} > 0xffff) ? 2 : 1"
179
+
180
+
181
+ def writeLine { body: () => Unit / emit[Char] }: Unit / emit[Char] =
182
+ returning::writeLine[Unit]{body}
183
+
184
+ def readLine { body: () => Unit / read[Char] }: Unit / {read[Char], stop} =
185
+ returning::readLine[Unit]{body}
186
+
187
+ namespace returning {
188
+ def writeLine[R] { body: () => R / emit[Char] }: R / emit[Char] = {
189
+ val result = body()
190
+ do emit('\n')
191
+ return result
192
+ }
193
+
194
+ def readLine[R] { body: () => R / read[Char] }: R / {read[Char], stop} = {
195
+ var stopped = false
196
+ try {
197
+ body()
198
+ } with read[Char] {
199
+ if(stopped){
200
+ resume { do stop() }
201
+ } else {
202
+ do read[Char] match {
203
+ case '\n' => stopped = true; resume { do stop() }
204
+ case char => resume { return char }
205
+ }
206
+ }
207
+ }
208
+ }
209
+ }
210
+
211
+ def decodeChar(): Char / {read[Byte], stop} = {
212
+ val b = do read().toInt
213
+ if (b < 128) {
214
+ b.toChar
215
+ } else if (b < 224) {
216
+ val part1 = bitwiseShl(bitwiseAnd(b, 31), 6)
217
+ val part2 = bitwiseAnd(do read().toInt, 63)
218
+ bitwiseOr(part1, part2).toChar
219
+ } else if (b < 240) {
220
+ val part1 = bitwiseShl(bitwiseAnd(b, 15), 12)
221
+ val part2 = bitwiseShl(bitwiseAnd(do read().toInt, 63), 6)
222
+ val part3 = bitwiseAnd(do read().toInt, 63)
223
+ bitwiseOr(bitwiseOr(part1, part2), part3).toChar
224
+ } else {
225
+ val part1 = bitwiseShl(bitwiseAnd(b, 7), 18)
226
+ val part2 = bitwiseShl(bitwiseAnd(do read().toInt, 63), 12)
227
+ val part3 = bitwiseShl(bitwiseAnd(do read().toInt, 63), 6)
228
+ val part4 = bitwiseAnd(do read().toInt, 63)
229
+ bitwiseOr(bitwiseOr(bitwiseOr(part1, part2), part3), part4).toChar
230
+ }
231
+ }
232
+
233
+ def encodeChar(char: Char): Unit / emit[Byte] = {
234
+ val code = char.toInt
235
+ if (code < 128) {
236
+ do emit(code.toByte)
237
+ } else if (code < 2048) {
238
+ do emit(bitwiseOr(192, bitwiseShr(code, 6)).toByte)
239
+ do emit(bitwiseOr(128, bitwiseAnd(code, 63)).toByte)
240
+ } else if (code < 65536) {
241
+ do emit(bitwiseOr(224, bitwiseShr(code, 12)).toByte)
242
+ do emit(bitwiseOr(128, bitwiseAnd(bitwiseShr(code, 6), 63)).toByte)
243
+ do emit(bitwiseOr(128, bitwiseAnd(code, 63)).toByte)
244
+ } else {
245
+ do emit(bitwiseOr(240, bitwiseShr(code, 18)).toByte)
246
+ do emit(bitwiseOr(128, bitwiseAnd(bitwiseShr(code, 12), 63)).toByte)
247
+ do emit(bitwiseOr(128, bitwiseAnd(bitwiseShr(code, 6), 63)).toByte)
248
+ do emit(bitwiseOr(128, bitwiseAnd(code, 63)).toByte)
249
+ }
250
+ }
251
+
252
+ def decodeUTF8[R] { reader: () => R / read[Char] }: R / read[Byte] =
253
+ try {
254
+ reader()
255
+ } with read[Char] {
256
+ resume { decodeChar() }
257
+ }
258
+
259
+ def encodeUTF8[R] { stream: () => R / emit[Char] }: R / emit[Byte] =
260
+ try {
261
+ stream()
262
+ } with emit[Char] { char =>
263
+ resume(encodeChar(char))
264
+ }
@@ -404,6 +404,9 @@ def square(x: Double): Double = x * x
404
404
  def neg(n: Double): Double =
405
405
  0.0 - n
406
406
 
407
+ def min(n: Double, m: Double): Double =
408
+ if (n < m) n else m
409
+
407
410
  def max(n: Double, m: Double): Double =
408
411
  if (n > m) n else m
409
412
 
@@ -464,3 +464,14 @@ namespace internal {
464
464
  """
465
465
 
466
466
  }
467
+
468
+ namespace internal {
469
+ def checkResult(result: Int): Int / Exception[IOError] =
470
+ if (result < 0) {
471
+ val ioError = fromNumber(internal::errorNumber(result));
472
+ do raise[IOError](ioError, message(ioError))
473
+ } else {
474
+ result
475
+ }
476
+ }
477
+
@@ -71,6 +71,77 @@ def appendFile(path: String, contents: String): Unit / Exception[IOError] = {
71
71
  go()
72
72
  }
73
73
 
74
+ def writeFileUTF8[R](path: String) { stream: () => R / emit[Char] }: R / Exception[IOError] =
75
+ writeFile(path) { encodeUTF8 { stream() } }
76
+
77
+ def readFileUTF8[R](path: String) { reader: () => R / read[Char] }: R / Exception[IOError] =
78
+ readFile(path) { decodeUTF8 { reader() } }
79
+
80
+ def writeFile[R](path: String) { stream: () => R / emit[Byte] }: R / Exception[IOError] = {
81
+
82
+ val file = openForWriting(path);
83
+ with on[IOError].finalize { close(file) }
84
+
85
+ val chunkSize = 1048576 // 1MB
86
+ val buffer = bytearray::allocate(chunkSize)
87
+ var offset = 0
88
+
89
+ def push(i: Int, n: Int): Unit = {
90
+ val r = write(file, buffer, i, n, -1)
91
+ if (r < n) {
92
+ push(i + r, n - r)
93
+ }
94
+ }
95
+
96
+ try {
97
+ val r = stream()
98
+ push(0, offset)
99
+ return r
100
+ } with emit[Byte] { (byte) =>
101
+ if (offset >= buffer.size) {
102
+ push(0, buffer.size)
103
+ offset = 0
104
+ }
105
+ buffer.unsafeSet(offset, byte)
106
+ offset = offset + 1
107
+ resume(())
108
+ }
109
+ }
110
+
111
+ def readFile[R](path: String) { reader: () => R / read[Byte] }: R / Exception[IOError] = {
112
+
113
+ val file = openForReading(path);
114
+ with on[IOError].finalize { close(file) }
115
+
116
+ val chunkSize = 1048576 // 1MB
117
+ val buffer = bytearray::allocate(chunkSize)
118
+ var offset = 0
119
+ var length = 0
120
+
121
+ def pull(): Unit / stop = {
122
+ read(file, buffer, 0, chunkSize, -1) match {
123
+ case 0 =>
124
+ do stop()
125
+ case n =>
126
+ length = n
127
+ }
128
+ }
129
+
130
+ try {
131
+ reader()
132
+ } with read[Byte] {
133
+ resume {
134
+ if (offset >= length) {
135
+ pull()
136
+ offset = 0
137
+ }
138
+ val byte = buffer.unsafeGet(offset)
139
+ offset = offset + 1
140
+ return byte
141
+ }
142
+ }
143
+ }
144
+
74
145
  /// An abstract interface applications can program against.
75
146
  ///
76
147
  /// Can be interpreted with the `filesystem` handler, or virtualized etc.
@@ -192,7 +263,6 @@ namespace internal {
192
263
  ret void
193
264
  """
194
265
 
195
- /// The buffer must be kept alive by using it after the call
196
266
  extern def read(file: Int, buffer: ByteArray, offset: Int, size: Int, position: Int) at async: Int =
197
267
  jsNode "$effekt.capture(callback => read(${file}, ${buffer}, ${offset}, ${size}, ${position}, callback))"
198
268
  llvm """
@@ -200,7 +270,6 @@ namespace internal {
200
270
  ret void
201
271
  """
202
272
 
203
- /// The buffer must be kept alive by using it after the call
204
273
  extern def write(file: Int, buffer: ByteArray, offset: Int, size: Int, position: Int) at async: Int =
205
274
  jsNode "$effekt.capture(callback => write(${file}, ${buffer}, ${offset}, ${size}, ${position}, callback))"
206
275
  llvm """
@@ -222,13 +291,6 @@ namespace internal {
222
291
  ret void
223
292
  """
224
293
 
225
- def checkResult(result: Int): Int / Exception[IOError] =
226
- if (result < 0) {
227
- val ioError = fromNumber(internal::errorNumber(result));
228
- do raise[IOError](ioError, message(ioError))
229
- } else {
230
- result
231
- }
232
294
  }
233
295
 
234
296
  namespace examples {
@@ -1,84 +1,116 @@
1
1
  module io/network
2
2
 
3
+ import stream
3
4
  import bytearray
4
- import io
5
5
 
6
- namespace js {
7
- extern jsNode """
8
- const net = require('node:net');
6
+ import io/error
9
7
 
10
- function listen(server, port, host, listener) {
11
- server.listen(port, host);
12
- server.on('connection', listener);
13
- }
14
- """
15
8
 
16
- extern type JSServer // = net.Server
17
- extern type JSSocket // = net.Socket
18
- extern def server() at io: JSServer =
19
- jsNode "net.createServer()"
20
- extern def listen(server: JSServer, port: Int, host: String, listener: JSSocket => Unit at {io, async, global}) at io: Unit =
21
- jsNode "listen(${server}, ${port}, ${host}, (socket) => $effekt.runToplevel((ks, k) => (${listener})(socket, ks, k)))"
9
+ /// A TCP handle. Should not be inspected.
10
+ type Connection = Int
22
11
 
23
- extern def send(socket: JSSocket, data: ByteArray) at async: Unit =
24
- jsNode "$effekt.capture(callback => ${socket}.write(${data}, callback))"
12
+ /// Reads data from a TCP connection into a buffer at the given offset.
13
+ def read(handle: Connection, buffer: ByteArray, offset: Int, size: Int): Int / Exception[IOError] =
14
+ internal::checkResult(internal::read(handle, buffer, offset, size))
25
15
 
26
- extern def receive(socket: JSSocket) at async: ByteArray =
27
- jsNode "$effekt.capture(callback => ${socket}.once('data', callback))"
28
-
29
- extern def end(socket: JSSocket) at async: Unit =
30
- jsNode "$effekt.capture(k => ${socket}.end(k))"
31
- }
16
+ /// Writes data from a buffer at the given offset to a TCP connection.
17
+ def write(handle: Connection, buffer: ByteArray, offset: Int, size: Int): Int / Exception[IOError] =
18
+ internal::checkResult(internal::write(handle, buffer, offset, size))
32
19
 
33
- interface Socket {
34
- def send(message: ByteArray): Unit
35
- def receive(): ByteArray
36
- def end(): Unit
37
- }
38
-
39
- def server(host: String, port: Int, handler: () => Unit / Socket at {io, async, global}): Unit = {
40
- val server = js::server();
41
- js::listen(server, port, host, box { socket =>
42
- println("New connection")
43
- spawn(box {
44
- try handler()
45
- with Socket {
46
- def send(message) =
47
- resume(js::send(socket, message))
48
- def receive() =
49
- resume(js::receive(socket))
50
- def end() =
51
- resume(js::end(socket))
52
- }
53
- })
54
- })
55
- }
20
+ /// Establishes a TCP connection to the specified host and port.
21
+ def connect(host: String, port: Int): Connection / Exception[IOError] =
22
+ internal::checkResult(internal::connect(host, port))
56
23
 
24
+ /// Closes a TCP connection and releases associated resources.
25
+ def close(handle: Connection): Unit =
26
+ internal::close(handle)
57
27
 
58
- namespace examples {
59
- def helloWorldApp(): Unit / Socket = {
60
- val request = do receive().toString;
28
+ /// A TCP listener. Should not be inspected.
29
+ type Listener = Int
61
30
 
62
- println("Received a request: " ++ request)
31
+ /// Creates a TCP listener bound to the specified host and port.
32
+ def bind(host: String, port: Int): Listener / Exception[IOError] =
33
+ internal::checkResult(internal::bind(host, port))
63
34
 
64
- def respond(s: String): Unit / Socket =
65
- do send(s.fromString)
35
+ /// Starts listening for incoming connections and handles them with the provided handler function.
36
+ /// Runs until `shutdown` is called on this `Listener`.
37
+ def listen(listener: Listener, handler: Connection => Unit at {io, async, global}): Unit / Exception[IOError] = {
38
+ internal::checkResult(internal::listen(listener, handler)); ()
39
+ }
66
40
 
67
- if (request.startsWith("GET /")) {
68
- respond("HTTP/1.1 200 OK\r\n\r\nHello from Effekt!")
69
- } else {
70
- respond("HTTP/1.1 400 Bad Request\r\n\r\n")
41
+ /// Stops a TCP listener and releases associated resources.
42
+ def shutdown(listener: Listener): Unit =
43
+ internal::shutdown(listener)
44
+
45
+
46
+ def streaming[R](connection: Connection) { program: () => R / {read[ByteArray], emit[ByteArray]} }: R / Exception[IOError] =
47
+ try {
48
+ program()
49
+ } with emit[ByteArray] { bytes =>
50
+ write(connection, bytes, 0, bytes.size)
51
+ resume(())
52
+ } with read[ByteArray] { () =>
53
+ val bytes = bytearray::allocate(4096)
54
+ read(connection, bytes, 0, bytes.size) match {
55
+ case r and r == bytes.size => resume { return bytes }
56
+ case r => resume { bytes.resize(r) }
71
57
  }
72
- do end()
73
58
  }
74
59
 
75
- // A server that just shows "Hello from Effekt!" on localhost:8080
76
- def main() = {
77
- val port = 8080
78
- println("Starting server on http://localhost:" ++ port.show)
60
+ namespace internal {
61
+
62
+ extern llvm """
63
+ declare void @c_tcp_connect(%Pos, %Int, %Stack)
64
+ declare void @c_tcp_read(%Int, %Pos, %Int, %Int, %Stack)
65
+ declare void @c_tcp_write(%Int, %Pos, %Int, %Int, %Stack)
66
+ declare void @c_tcp_close(%Int, %Stack)
67
+ declare void @c_tcp_bind(%Pos, %Int, %Int, %Stack)
68
+ declare void @c_tcp_listen(%Int, %Pos, %Stack)
69
+ declare void @c_tcp_shutdown(%Int, %Stack)
70
+ """
71
+
72
+ extern def connect(host: String, port: Int) at async: Int =
73
+ llvm """
74
+ call void @c_tcp_connect(%Pos ${host}, %Int ${port}, %Stack %stack)
75
+ ret void
76
+ """
77
+
78
+ extern def read(handle: Int, buffer: ByteArray, offset: Int, size: Int) at async: Int =
79
+ llvm """
80
+ call void @c_tcp_read(%Int ${handle}, %Pos ${buffer}, %Int ${offset}, %Int ${size}, %Stack %stack)
81
+ ret void
82
+ """
83
+
84
+ extern def write(handle: Int, buffer: ByteArray, offset: Int, size: Int) at async: Int =
85
+ llvm """
86
+ call void @c_tcp_write(%Int ${handle}, %Pos ${buffer}, %Int ${offset}, %Int ${size}, %Stack %stack)
87
+ ret void
88
+ """
89
+
90
+ extern def close(handle: Int) at async: Unit =
91
+ llvm """
92
+ call void @c_tcp_close(%Int ${handle}, %Stack %stack)
93
+ ret void
94
+ """
95
+
96
+ extern def bind(host: String, port: Int) at io: Int =
97
+ llvm """
98
+ %result = call %Int @c_tcp_bind(%Pos ${host}, %Int ${port})
99
+ ret %Int %result
100
+ """
101
+
102
+ extern def listen(listener: Int, handler: Int => Unit at {io, async, global}) at async: Int =
103
+ llvm """
104
+ call void @c_tcp_listen(%Int ${listener}, %Pos ${handler}, %Stack %stack)
105
+ ret void
106
+ """
107
+
108
+ extern def shutdown(handle: Int) at async: Unit =
109
+ llvm """
110
+ call void @c_tcp_shutdown(%Int ${handle}, %Stack %stack)
111
+ ret void
112
+ """
79
113
 
80
- server("localhost", port, box {
81
- helloWorldApp()
82
- })
83
- }
84
114
  }
115
+
116
+
@@ -16,8 +16,7 @@ extern def spawn(task: Task[Unit]) at async: Unit =
16
16
  js "$effekt.capture(k => { setTimeout(() => k($effekt.unit), 0); return $effekt.run(${task}) })"
17
17
  llvm """
18
18
  call void @c_yield(%Stack %stack)
19
- %unboxed = call ccc %Neg @coercePosNeg(%Pos ${task})
20
- call void @run(%Neg %unboxed)
19
+ call void @run(%Pos ${task})
21
20
  ret void
22
21
  """
23
22
 
@@ -165,7 +165,7 @@ def decodeJson(): Unit / {Scan[Char], JsonBuilder, Exception[WrongFormat]} = {
165
165
  case 'f' => expectString("false"); do bool(false)
166
166
  case '-' => do number(readDouble())
167
167
  case d and d.isDigit => do number(readDouble())
168
- case '"' => do string(collectString { readQuotedString() })
168
+ case '"' => do string(collect { readQuotedString() })
169
169
  case '{' => do dict{ decodeJsonObject() }
170
170
  case '[' => do list{ decodeJsonList() }
171
171
  case _ => println("Unexpected " ++ c.toString); <>
@@ -179,7 +179,7 @@ def decodeJsonObject(): Unit / {Scan[Char], JsonObjectBuilder, Exception[WrongFo
179
179
  skipWhitespace()
180
180
  while(do peek[Char]() is c and (c != '}')) {
181
181
  if (not(first)) { expectString(",") }
182
- val k: String = collectString { readQuotedString() }
182
+ val k = string::collect { readQuotedString() }
183
183
  skipWhitespace()
184
184
  expectString(":")
185
185
  do field(k){
@@ -277,7 +277,7 @@ def build[R](){ body: => R / JsonBuilder }: (R, JsonValue) = {
277
277
  }
278
278
  (x, r)
279
279
  }
280
- def buildList[R](){ body: => R / JsonBuilder }: (R, List[JsonValue]) = returning::collectList[JsonValue, R] {
280
+ def buildList[R](){ body: => R / JsonBuilder }: (R, List[JsonValue]) = list::returning::collect[JsonValue, R] {
281
281
  try body() with JsonBuilder {
282
282
  def number(n) = { do emit(Number(n)); resume(()) }
283
283
  def bool(b) = { do emit(Bool(b)); resume(()) }
@@ -295,7 +295,7 @@ def buildList[R](){ body: => R / JsonBuilder }: (R, List[JsonValue]) = returning
295
295
  }
296
296
  }
297
297
  }
298
- def buildDict[R](){ body: => R / JsonObjectBuilder }: (R, List[(String, JsonValue)]) = returning::collectList[(String, JsonValue), R] {
298
+ def buildDict[R](){ body: => R / JsonObjectBuilder }: (R, List[(String, JsonValue)]) = list::returning::collect[(String, JsonValue), R] {
299
299
  try body() with JsonObjectBuilder {
300
300
  def field(k) = resume { {v} =>
301
301
  val x = build{v}
@@ -315,7 +315,7 @@ namespace test {
315
315
  // Read quoted string
316
316
  feed("\"\ta\n\ra\"") {
317
317
  with scanner[Char]
318
- println(collectString { readQuotedString() })
318
+ println(string::collect { readQuotedString() })
319
319
  }
320
320
 
321
321
  println("")