@effekt-lang/effekt 0.19.0 → 0.20.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
@@ -736,6 +736,28 @@ def repeat(n: Int) { action: () => Unit } = each(0, n) { n => action() }
736
736
 
737
737
  def repeat(n: Int) { action: () {Control} => Unit } = each(0, n) { (n) {l} => action() {l} }
738
738
 
739
+
740
+ // Splices
741
+ // =======
742
+ //
743
+ // The Effekt compiler translates the string interpolation
744
+ //
745
+ // "foo ${42} bar ${43}"
746
+ //
747
+ // to the following stream of `literal`s and `splice[Int]`s:
748
+ //
749
+ // do literal("foo "); do splice(42); do literal(" bar "); do splice(43)
750
+ //
751
+ // The stream is wrapped into a handler function, which defaults to `stringbuffer::s`
752
+ //
753
+ // s { ... }
754
+ //
755
+ // but can be specified by prefixing the string interpolation `custom"..."`.
756
+
757
+ effect literal(s: String): Unit
758
+ effect splice[A](x: A): Unit
759
+
760
+
739
761
  namespace internal {
740
762
  namespace boxing {
741
763
  // For boxing polymorphic values
@@ -0,0 +1,103 @@
1
+ module heap
2
+ import resizable_array
3
+
4
+ /// Resizable 2-ary min-heap, backed by a resizable array
5
+ /// `cmp` defines the ordering of elements
6
+ record Heap[T](rawContents: ResizableArray[T], cmp: (T, T) => Ordering at {})
7
+
8
+ /// Make a new Heap with the given comparison operation
9
+ def heap[T](cmp: (T,T) => Ordering at {}) =
10
+ Heap[T](resizableArray(), cmp)
11
+
12
+ /// Make a new Heap with the given comparison operation and initial capacity
13
+ def heap[T](cmp: (T,T) => Ordering at {}, capacity: Int) =
14
+ Heap[T](resizableArray(capacity), cmp)
15
+
16
+ namespace internal {
17
+ def left(idx: Int) = 2 * idx + 1
18
+ def right(idx: Int) = 2 * idx + 2
19
+ def parent(idx: Int) = (idx - 1) / 2
20
+
21
+ def bubbleUp[A](heap: Heap[A], idx: Int) = {
22
+ val arr = heap.rawContents
23
+ arr.boundsCheck(idx) // idx > parent(idx), parent(parent(idx)) etc
24
+
25
+ def go(idx: Int): Unit = {
26
+ if (idx > 0 and (heap.cmp)(arr.unsafeGet(parent(idx)), arr.unsafeGet(idx)) is Greater()) {
27
+ arr.unsafeSwap(parent(idx), idx)
28
+ go(parent(idx))
29
+ }
30
+ }
31
+ go(idx)
32
+ }
33
+ def sinkDown[A](heap: Heap[A], idx: Int) = {
34
+ val arr = heap.rawContents
35
+
36
+ def infixLt(x: A, y: A) = {
37
+ if ((heap.cmp)(x,y) is Less()) { true } else { false }
38
+ }
39
+
40
+ def go(idx: Int): Unit = {
41
+ if (right(idx) < arr.size) {
42
+ val v = arr.unsafeGet(idx)
43
+ val l = arr.unsafeGet(left(idx))
44
+ val r = arr.unsafeGet(right(idx))
45
+ if (l < v && r < v) {
46
+ // swap with the smaller one
47
+ if (l < r) {
48
+ arr.unsafeSwap(left(idx), idx)
49
+ go(left(idx))
50
+ } else {
51
+ arr.unsafeSwap(right(idx), idx)
52
+ go(right(idx))
53
+ }
54
+ } else if (l < v) {
55
+ arr.unsafeSwap(left(idx), idx)
56
+ go(left(idx))
57
+ } else if (r < v) {
58
+ arr.unsafeSwap(right(idx), idx)
59
+ go(right(idx))
60
+ }
61
+ } else if (left(idx) < arr.size) {
62
+ if (arr.unsafeGet(left(idx)) < arr.unsafeGet(idx)) {
63
+ arr.unsafeSwap(left(idx), idx)
64
+ go(left(idx))
65
+ }
66
+ } // else: we are at the bottom
67
+ }
68
+ go(idx)
69
+ }
70
+ }
71
+
72
+ /// Insert value into heap
73
+ ///
74
+ /// O(log n) worst case if capacity suffices, O(1) average
75
+ def insert[T](heap: Heap[T], value: T): Unit = {
76
+ with on[OutOfBounds].panic();
77
+ val idx = heap.rawContents.add(value)
78
+ internal::bubbleUp(heap, idx)
79
+ }
80
+
81
+ /// find and return (but not remove) the minimal element in this heap
82
+ ///
83
+ /// O(1)
84
+ def findMin[T](heap: Heap[T]): T / Exception[OutOfBounds] = {
85
+ heap.rawContents.get(0)
86
+ }
87
+
88
+ /// find and remove the minimal element in this heap
89
+ ///
90
+ /// O(log n)
91
+ def deleteMin[T](heap: Heap[T]): T / Exception[OutOfBounds] = {
92
+ val res = heap.rawContents.get(0)
93
+ heap.rawContents.unsafeSet(0, heap.rawContents.popRight())
94
+ internal::sinkDown(heap, 0)
95
+ res
96
+ }
97
+
98
+ /// Number of elements in the heap
99
+ ///
100
+ /// O(1)
101
+ def size[T](heap: Heap[T]): Int = {
102
+ heap.rawContents.size
103
+ }
@@ -6,35 +6,13 @@ import io
6
6
  import io/error
7
7
 
8
8
 
9
- /// Represents the file opening modes with their respective flags
10
- /// Backends rely on the order of these
11
- type Mode {
12
- // Most common modes
13
- ReadOnly() // 'r' exception if does not exist
14
- WriteOnly() // 'w' created if does not exist, truncated if exists
15
- AppendOnly() // 'a' created if does not exist
16
-
17
- // Other modes
18
- ReadWrite() // 'w+' created if does not exist, truncated if exists
19
- ReadAppend() // 'a+' created if does not exist
20
- AppendExclusive() // 'ax' created if does not exist, fails if exists
21
- ReadAppendExclusive() // 'ax+' created if does not exist, fails if exists
22
- AppendSync() // 'as' created if does not exist, append in synchronous mode
23
- ReadAppendSync() // 'as+' created if does not exist, append in synchronous mode
24
- ReadSync() // 'rs' exception if does not exist, read in synchronous mode
25
- ReadWriteSync() // 'rs+' exception if does not exist, read/write in synchronous mode
26
- WriteExclusive() // 'wx' created if does not exist, truncated if exists, fails if exists
27
- ReadWriteExclusive() // 'wx+' created if does not exist, truncated if exists, fails if exists
28
- }
29
-
30
-
31
9
  /// A file descriptor. Should not be inspected.
32
10
  type File = Int
33
11
 
34
12
 
35
13
  /// Reads a file at given path as utf8 encoded string.
36
14
  def readFile(path: String): String / Exception[IOError] = {
37
- val file = open(path, ReadOnly());
15
+ val file = openForReading(path);
38
16
  with on[IOError].finalize { close(file) }
39
17
 
40
18
  val chunkSize = 1048576 // 1MB
@@ -59,7 +37,7 @@ def readFile(path: String): String / Exception[IOError] = {
59
37
 
60
38
  /// Writes the (utf8 encoded) string `contents` into the specified file.
61
39
  def writeFile(path: String, contents: String): Unit / Exception[IOError] = {
62
- val file = open(path, WriteOnly());
40
+ val file = openForWriting(path);
63
41
  with on[IOError].finalize { close(file) }
64
42
 
65
43
  val chunkSize = 1048576 // 1MB
@@ -75,6 +53,23 @@ def writeFile(path: String, contents: String): Unit / Exception[IOError] = {
75
53
  go()
76
54
  }
77
55
 
56
+ /// Appends the (utf8 encoded) string `contents` to the specified file.
57
+ def appendFile(path: String, contents: String): Unit / Exception[IOError] = {
58
+ val file = openForAppending(path);
59
+ with on[IOError].finalize { close(file) }
60
+
61
+ val chunkSize = 1048576 // 1MB
62
+ val buffer = contents.fromString
63
+ var offset = 0;
64
+
65
+ def go(): Unit = {
66
+ val n = write(file, buffer, offset, min(buffer.size - offset, chunkSize), -1)
67
+ offset = offset + n
68
+ if (offset < buffer.size) { go() }
69
+ }
70
+
71
+ go()
72
+ }
78
73
 
79
74
  /// An abstract interface applications can program against.
80
75
  ///
@@ -95,8 +90,14 @@ def filesystem[R] { program: => R / Files }: R / Exception[IOError] = // TODO mo
95
90
  }
96
91
 
97
92
 
98
- def open(path: String, mode: Mode): File / Exception[IOError] =
99
- internal::checkResult(internal::open(path, mode))
93
+ def openForReading(path: String): File / Exception[IOError] =
94
+ internal::checkResult(internal::openForReading(path))
95
+
96
+ def openForWriting(path: String): File / Exception[IOError] =
97
+ internal::checkResult(internal::openForWriting(path))
98
+
99
+ def openForAppending(path: String): File / Exception[IOError] =
100
+ internal::checkResult(internal::openForAppending(path))
100
101
 
101
102
  def read(file: File, buffer: ByteArray, offset: Int, size: Int, position: Int): Int / Exception[IOError] =
102
103
  internal::checkResult(internal::read(file, buffer, offset, size, position))
@@ -111,40 +112,6 @@ def close(file: File): Unit / Exception[IOError] = {
111
112
  namespace internal {
112
113
 
113
114
  extern js """
114
- function modeName(mode) {
115
- switch (mode.__tag) {
116
- case 0: // ReadOnly()
117
- return 'r';
118
- case 1: // WriteOnly()
119
- return 'w';
120
- case 2: // AppendOnly()
121
- return 'a';
122
- case 3: // ReadWrite()
123
- return 'w+';
124
- case 4: // ReadAppend()
125
- return 'a+';
126
- case 5: // AppendExclusive()
127
- return 'ax';
128
- case 6: // ReadAppendExclusive()
129
- return 'ax+';
130
- case 7: // AppendSync()
131
- return 'as';
132
- case 8: // ReadAppendSync()
133
- return 'as+';
134
- case 9: // ReadSync()
135
- return 'rs';
136
- case 10: // ReadWriteSync()
137
- return 'rs+';
138
- case 11: // WriteExclusive()
139
- return 'wx';
140
- case 12: // ReadWriteExclusive()
141
- return 'wx+';
142
- default:
143
- // Invalid tag value
144
- return null;
145
- }
146
- }
147
-
148
115
  /**
149
116
  * Nodejs file operations expect buffers, but we represent buffers as typed arrays.
150
117
  * This function converts between the two without copying.
@@ -157,8 +124,8 @@ namespace internal {
157
124
  extern jsNode """
158
125
  const fs = require("fs");
159
126
 
160
- function open(path, mode, callback) {
161
- fs.open(path, modeName(mode), (err, file) => {
127
+ function open(path, flags, callback) {
128
+ fs.open(path, flags, (err, file) => {
162
129
  if (err) { callback(err.errno) } else { callback(file) }
163
130
  })
164
131
  }
@@ -185,16 +152,32 @@ namespace internal {
185
152
  """
186
153
 
187
154
  extern llvm """
188
- declare void @c_fs_open(%Pos, %Pos, %Stack)
155
+ declare void @c_fs_open_reading(%Pos, %Stack)
156
+ declare void @c_fs_open_writing(%Pos, %Stack)
157
+ declare void @c_fs_open_appending(%Pos, %Stack)
189
158
  declare void @c_fs_read(%Int, %Pos, %Int, %Int, %Int, %Stack)
190
159
  declare void @c_fs_write(%Int, %Pos, %Int, %Int, %Int, %Stack)
191
160
  declare void @c_fs_close(%Int, %Stack)
192
161
  """
193
162
 
194
- extern async def open(path: String, mode: Mode): Int =
195
- jsNode "$effekt.capture(callback => open(${path}, ${mode}, callback))"
163
+ extern async def openForReading(path: String): Int =
164
+ jsNode "$effekt.capture(callback => open(${path}, 'r', callback))"
165
+ llvm """
166
+ call void @c_fs_open_reading(%Pos ${path}, %Stack %stack)
167
+ ret void
168
+ """
169
+
170
+ extern async def openForWriting(path: String): Int =
171
+ jsNode "$effekt.capture(callback => open(${path}, 'w', callback))"
172
+ llvm """
173
+ call void @c_fs_open_writing(%Pos ${path}, %Stack %stack)
174
+ ret void
175
+ """
176
+
177
+ extern async def openForAppending(path: String): Int =
178
+ jsNode "$effekt.capture(callback => open(${path}, 'a', callback))"
196
179
  llvm """
197
- call void @c_fs_open(%Pos ${path}, %Pos ${mode}, %Stack %stack)
180
+ call void @c_fs_open_appending(%Pos ${path}, %Stack %stack)
198
181
  ret void
199
182
  """
200
183
 
@@ -0,0 +1,235 @@
1
+ module resizable_array
2
+
3
+ import ref
4
+ import array
5
+
6
+ record ResizableArray[T](rawSizePtr: Ref[Int], rawContentPtr: Ref[Array[T]])
7
+
8
+ // These numbers should be optimized based on benchmarking
9
+ // According to https://en.wikipedia.org/wiki/Dynamic_array most use 1.5 or 2
10
+
11
+ /// Factor by which to grow the capacity when it becomes too small
12
+ val growFactor = 1.5
13
+ /// shrink array when size / capacity falls below this threshold
14
+ /// should be < 1/growFactor
15
+ val shrinkThreshold = 0.4
16
+
17
+ /// Number of elements in the dynamic array
18
+ ///
19
+ /// O(1)
20
+ def size[T](arr: ResizableArray[T]) = arr.rawSizePtr.get
21
+
22
+ /// Allocate a new, empty dynamic array with given initial capacity
23
+ def resizableArray[T](capacity: Int): ResizableArray[T] = {
24
+ ResizableArray(ref(0), ref(allocate(capacity)))
25
+ }
26
+
27
+ /// Allocate a new, empty dynamic array
28
+ def resizableArray[T](): ResizableArray[T] = resizableArray(8)
29
+
30
+ /// Throw an OutOfBounds exception if index is not a valid index into arr
31
+ def boundsCheck[T](arr: ResizableArray[T], index: Int): Unit / Exception[OutOfBounds] = {
32
+ if (index < 0 && index >= arr.size) {
33
+ do raise(OutOfBounds(), "Array index out of bounds: " ++ show(index))
34
+ }
35
+ }
36
+
37
+ /// get the element at position index in the array
38
+ ///
39
+ /// precondition: index is a valid index into the array
40
+ ///
41
+ /// O(1)
42
+ def unsafeGet[T](arr: ResizableArray[T], index: Int): T = {
43
+ arr.rawContentPtr.get.unsafeGet(index)
44
+ }
45
+ /// get the element at position index in the array
46
+ ///
47
+ /// O(1)
48
+ def get[T](arr: ResizableArray[T], index: Int): T / Exception[OutOfBounds] = {
49
+ arr.boundsCheck(index);
50
+ arr.unsafeGet(index)
51
+ }
52
+
53
+ /// set the element at position index in the array
54
+ ///
55
+ /// precondition: index is a valid index into the array
56
+ ///
57
+ /// O(1)
58
+ def unsafeSet[T](arr: ResizableArray[T], index: Int, value: T): Unit = {
59
+ arr.rawContentPtr.get.unsafeSet(index, value)
60
+ }
61
+
62
+ /// set the element at position index in the array
63
+ ///
64
+ /// O(1)
65
+ def set[T](arr: ResizableArray[T], index: Int, value: T): Unit / Exception[OutOfBounds] = {
66
+ arr.boundsCheck(index);
67
+ arr.unsafeSet(index, value)
68
+ }
69
+
70
+ /// swap the elements at the given positions in the array
71
+ ///
72
+ /// precondition: both are valid indices into the array
73
+ ///
74
+ /// O(1)
75
+ def unsafeSwap[T](arr: ResizableArray[T], index1: Int, index2: Int): Unit = {
76
+ val raw = arr.rawContentPtr.get
77
+ val tmp = raw.unsafeGet(index1)
78
+ raw.unsafeSet(index1, raw.unsafeGet(index2))
79
+ raw.unsafeSet(index2, tmp)
80
+ }
81
+
82
+ /// swap the elements at the given positions in the array
83
+ ///
84
+ /// O(1)
85
+ def swap[T](arr: ResizableArray[T], index1: Int, index2: Int): Unit / Exception[OutOfBounds] = {
86
+ arr.boundsCheck(max(index1, index2))
87
+ arr.unsafeSwap(index1, index2)
88
+ }
89
+
90
+ /// Change the dynamic to have exactly the given capacity
91
+ ///
92
+ /// precondition: given capacity is at least the size of the array
93
+ ///
94
+ /// O(n)
95
+ def unsafeSetCapacity[T](arr: ResizableArray[T], capacity: Int): Unit = {
96
+ with on[OutOfBounds].panic
97
+ val oldRaw = arr.rawContentPtr.get
98
+ if (oldRaw.size != capacity) {
99
+ val newRaw = array::allocate(capacity)
100
+ oldRaw.copy(0, newRaw, 0, arr.size)
101
+ arr.rawContentPtr.set(newRaw)
102
+ }
103
+ }
104
+
105
+ /// Change the resizable array to have exactly the given capacity.
106
+ /// This only changes the size of the backing array, not the `size`.
107
+ ///
108
+ /// O(n)
109
+ def setCapacity[T](arr: ResizableArray[T], capacity: Int): Unit / Exception[OutOfBounds] = {
110
+ if (arr.size > capacity) {
111
+ do raise(OutOfBounds(), "Cannot change capacity of ResizableArray to " ++ capacity.show ++ " below size " ++ arr.size.show)
112
+ }
113
+ arr.unsafeSetCapacity(capacity)
114
+ }
115
+
116
+ /// If the shrinkThreshold is reached, shrink by growFactor, otherwise do nothing
117
+ ///
118
+ /// O(n)
119
+ def maybeShrink[T](arr: ResizableArray[T]): Unit = {
120
+ if(arr.size.toDouble < arr.rawContentPtr.get.size.toDouble * shrinkThreshold) {
121
+ val newCap = max(arr.size, (arr.rawContentPtr.get.size.toDouble / growFactor).ceil)
122
+ arr.unsafeSetCapacity(newCap)
123
+ }
124
+ }
125
+
126
+ /// makes sure capacity is at least the given one
127
+ ///
128
+ /// O(given capacity - current capacity) amortized, O(n) worst case // TODO ?
129
+ def ensureCapacity[T](arr: ResizableArray[T], capacity: Int): Unit / Exception[OutOfBounds] = {
130
+ if (arr.rawContentPtr.get.size < capacity) {
131
+ val curCapd: Double = arr.rawContentPtr.get.size.toDouble
132
+ val minGrowCapacity: Int = (curCapd * growFactor + 1.0).toInt
133
+ val newCapacity = max(capacity, minGrowCapacity)
134
+ arr.setCapacity(newCapacity)
135
+ }
136
+ }
137
+
138
+ /// Set the value at given position, resizing if necessary
139
+ /// Note: New elements might be uninitialized!!!
140
+ ///
141
+ /// O(max(1,index - n)) amortized, O(n) worst case if index > capacity
142
+ def setResizing[T](arr: ResizableArray[T], index: Int, value: T): Unit / Exception[OutOfBounds] = {
143
+ if (index < 0) {
144
+ do raise(OutOfBounds(), "Negative index " ++ index.show)
145
+ }
146
+ if (index < arr.size) {
147
+ arr.rawContentPtr.get.unsafeSet(index, value)
148
+ }
149
+ ensureCapacity(arr, index + 1)
150
+ arr.rawContentPtr.get.unsafeSet(index, value)
151
+ arr.rawSizePtr.set(index + 1)
152
+ }
153
+
154
+ /// Add a new element at the end of the resizable array.
155
+ /// Return the index of the new element
156
+ ///
157
+ /// O(1) amortized, O(n) worst case
158
+ def add[T](arr: ResizableArray[T], value: T): Int = {
159
+ with on[OutOfBounds].panic();
160
+ val idx = arr.size
161
+ arr.setResizing(idx, value)
162
+ idx
163
+ }
164
+
165
+ /// Remove and return the rightmost element in the resizable array.
166
+ ///
167
+ /// O(1) amortized, O(n) worst case
168
+ def popRight[T](arr: ResizableArray[T]): T / Exception[OutOfBounds] = {
169
+ arr.boundsCheck(arr.size - 1)
170
+ arr.rawSizePtr.set(arr.size - 1)
171
+ val r = arr.unsafeGet(arr.size)
172
+ arr.maybeShrink()
173
+ r
174
+ }
175
+
176
+
177
+ def foreachIndex[T](arr: ResizableArray[T]){ body: (Int, T) => Unit }: Unit = {
178
+ each(0, arr.size) { i =>
179
+ body(i, arr.rawContentPtr.get.unsafeGet(i))
180
+ }
181
+ }
182
+ def foreachIndex[T](arr: ResizableArray[T]){ body: (Int, T) {Control} => Unit }: Unit = {
183
+ each(0, arr.size) { (i){label} =>
184
+ body(i, arr.rawContentPtr.get.unsafeGet(i)){label}
185
+ }
186
+ }
187
+ def foreach[T](arr: ResizableArray[T]){ body: T => Unit }: Unit = {
188
+ each(0, arr.size) { i =>
189
+ body(arr.rawContentPtr.get.unsafeGet(i))
190
+ }
191
+ }
192
+ def foreach[T](arr: ResizableArray[T]){ body: (T) {Control} => Unit }: Unit = {
193
+ each(0, arr.size) { (i){label} =>
194
+ body(arr.rawContentPtr.get.unsafeGet(i)){label}
195
+ }
196
+ }
197
+ def foreachIndexReversed[T](arr: ResizableArray[T]){ body: (Int, T){Control} => Unit }: Unit = {
198
+ var i = arr.size - 1
199
+ loop { {l} =>
200
+ if (i < 0) { l.break() }
201
+ body(i, arr.rawContentPtr.get.unsafeGet(i)){l}
202
+ i = i - 1
203
+ }
204
+ }
205
+ def foreachIndexReversed[T](arr: ResizableArray[T]){ body: (Int, T) => Unit }: Unit = {
206
+ var i = arr.size - 1
207
+ while(i >= 0) {
208
+ body(i, arr.rawContentPtr.get.unsafeGet(i))
209
+ i = i - 1
210
+ }
211
+ }
212
+ def foreachReversed[T](arr: ResizableArray[T]){ body: (T){Control} => Unit }: Unit = {
213
+ var i = arr.size - 1
214
+ loop { {l} =>
215
+ if (i < 0) { l.break() }
216
+ body(arr.rawContentPtr.get.unsafeGet(i)){l}
217
+ i = i - 1
218
+ }
219
+ }
220
+ def foreachReversed[T](arr: ResizableArray[T]){ body: T => Unit }: Unit = {
221
+ var i = arr.size - 1
222
+ while (i >= 0) {
223
+ body(arr.rawContentPtr.get.unsafeGet(i))
224
+ i = i - 1
225
+ }
226
+ }
227
+
228
+ def toList[T](arr: ResizableArray[T]): List[T] = {
229
+ def go(i: Int, acc: List[T]): List[T] = {
230
+ if (i < 0) { acc } else {
231
+ go(i - 1, Cons(arr.unsafeGet(i), acc))
232
+ }
233
+ }
234
+ go(arr.size - 1, Nil())
235
+ }
@@ -263,7 +263,7 @@ def zip[A, B] { stream1: () => Unit / emit[A] } { stream2: () => Unit / emit[B]
263
263
 
264
264
  def writeFile[R](path: String) { stream: () => R / emit[Byte] }: R / Exception[IOError] = {
265
265
 
266
- val file = open(path, WriteOnly());
266
+ val file = openForWriting(path);
267
267
  with on[IOError].finalize { close(file) }
268
268
 
269
269
  val chunkSize = 1048576 // 1MB
@@ -291,7 +291,7 @@ def writeFile[R](path: String) { stream: () => R / emit[Byte] }: R / Exception[I
291
291
 
292
292
  def readFile[R](path: String) { reader: () => R / read[Byte] }: R / Exception[IOError] = {
293
293
 
294
- val file = open(path, ReadOnly());
294
+ val file = openForReading(path);
295
295
  with on[IOError].finalize { close(file) }
296
296
 
297
297
  val chunkSize = 1048576 // 1MB
@@ -0,0 +1,65 @@
1
+ module stringbuffer
2
+
3
+ import bytearray
4
+
5
+ interface StringBuffer {
6
+ def write(str: String): Unit
7
+ def flush(): String
8
+ }
9
+
10
+ def stringBuffer[A] { prog: => A / StringBuffer }: A = {
11
+ val initialCapacity = 128
12
+ var buffer = bytearray::allocate(initialCapacity)
13
+ // next free index to write to
14
+ var pos = 0
15
+
16
+ def ensureCapacity(sizeToAdd: Int): Unit = {
17
+ val cap = buffer.size - pos + 1
18
+ if (sizeToAdd <= cap) ()
19
+ else {
20
+ // Double the capacity while ensuring the required capacity
21
+ val newSize = max(buffer.size * 2, buffer.size + sizeToAdd)
22
+ buffer = buffer.resize(newSize)
23
+ }
24
+ }
25
+
26
+ try { prog() }
27
+ with StringBuffer {
28
+ def write(str) = {
29
+ val bytes = fromString(str)
30
+ ensureCapacity(bytes.size)
31
+ bytes.foreach { b =>
32
+ buffer.unsafeSet(pos, b)
33
+ pos = pos + 1
34
+ }
35
+ resume(())
36
+ }
37
+ def flush() = {
38
+ // resize buffer to strip trailing zeros that otherwise would be converted into 0x00 characters
39
+ val str = bytearray::resize(buffer, pos).toString()
40
+ // after flushing, the stringbuffer should be empty again
41
+ buffer = bytearray::allocate(initialCapacity)
42
+ resume(str)
43
+ }
44
+ }
45
+ }
46
+
47
+ /// Handler for string interpolation using a string buffer
48
+ def s { prog: () => Unit / { literal, splice[String] } }: String =
49
+ stringBuffer {
50
+ try { prog(); do flush() }
51
+ with splice[String] { x => resume(do write(x)) }
52
+ with literal { s => resume(do write(s)) }
53
+ }
54
+
55
+ namespace examples {
56
+ def main() = {
57
+ with stringBuffer
58
+ do write("hello")
59
+ do write(", world")
60
+ // prints `hello, world`
61
+ println(do flush())
62
+ // prints the empty string
63
+ println(do flush())
64
+ }
65
+ }