@effekt-lang/effekt 0.18.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
@@ -34,7 +34,7 @@ def emptyBuffer[T](capacity: Int): Buffer[T] at {global} = {
34
34
  }
35
35
  def arrayBuffer[T](initialCapacity: Int): Buffer[T] at {global} = {
36
36
  // TODO allocate buffer (and array) into a region r.
37
- val contents = emptyArray[T](initialCapacity)
37
+ val contents = array::allocate[T](initialCapacity)
38
38
  var head in global = 0
39
39
  var tail in global = 0
40
40
 
@@ -51,7 +51,7 @@ def arrayBuffer[T](initialCapacity: Int): Buffer[T] at {global} = {
51
51
  def read() = {
52
52
  if (buffer.empty?) None()
53
53
  else {
54
- val result: T = contents.remove(head).getOrElse { <> };
54
+ val result: T = contents.unsafeGet(head);
55
55
  head = mod(head + 1, initialCapacity)
56
56
  Some(result)
57
57
  }
@@ -59,7 +59,7 @@ def arrayBuffer[T](initialCapacity: Int): Buffer[T] at {global} = {
59
59
  def write(el: T) = {
60
60
  if (buffer.full?) <> // raise(BufferOverflow())
61
61
 
62
- contents.put(tail, el)
62
+ contents.unsafeSet(tail, el)
63
63
  tail = mod(tail + 1, initialCapacity)
64
64
  }
65
65
  }
@@ -6,7 +6,7 @@ module bytearray
6
6
  extern type ByteArray
7
7
  // = llvm "%Pos"
8
8
  // = js "Uint8Array"
9
-
9
+ // = chez "bytevector"
10
10
 
11
11
  /// Allocates a new bytearray with the given `size`, its values are undefined.
12
12
  extern global def allocate(size: Int): ByteArray =
@@ -15,6 +15,7 @@ extern global def allocate(size: Int): ByteArray =
15
15
  %arr = call %Pos @c_bytearray_new(%Int ${size})
16
16
  ret %Pos %arr
17
17
  """
18
+ chez "(make-bytevector ${size})"
18
19
 
19
20
  extern pure def size(arr: ByteArray): Int =
20
21
  js "${arr}.length"
@@ -22,6 +23,7 @@ extern pure def size(arr: ByteArray): Int =
22
23
  %size = call %Int @c_bytearray_size(%Pos ${arr})
23
24
  ret %Int %size
24
25
  """
26
+ chez "(bytevector-length ${arr})"
25
27
 
26
28
  extern global def unsafeGet(arr: ByteArray, index: Int): Byte =
27
29
  js "(${arr})[${index}]"
@@ -29,6 +31,7 @@ extern global def unsafeGet(arr: ByteArray, index: Int): Byte =
29
31
  %byte = call %Byte @c_bytearray_get(%Pos ${arr}, %Int ${index})
30
32
  ret %Byte %byte
31
33
  """
34
+ chez "(bytevector-u8-ref ${arr} ${index})"
32
35
 
33
36
  extern global def unsafeSet(arr: ByteArray, index: Int, value: Byte): Unit =
34
37
  js "bytearray$set(${arr}, ${index}, ${value})"
@@ -36,6 +39,7 @@ extern global def unsafeSet(arr: ByteArray, index: Int, value: Byte): Unit =
36
39
  %z = call %Pos @c_bytearray_set(%Pos ${arr}, %Int ${index}, %Byte ${value})
37
40
  ret %Pos %z
38
41
  """
42
+ chez "(bytevector-u8-set! ${arr} ${index} ${value})"
39
43
 
40
44
  def resize(source: ByteArray, size: Int): ByteArray = {
41
45
  val target = allocate(size)
@@ -75,6 +79,7 @@ extern pure def fromString(str: String): ByteArray =
75
79
  llvm """
76
80
  ret %Pos ${str}
77
81
  """
82
+ chez "(string->utf8 ${str})"
78
83
 
79
84
  extern pure def toString(arr: ByteArray): String =
80
85
  js "(new TextDecoder('utf-8').decode(${arr}))"
@@ -82,10 +87,68 @@ extern pure def toString(arr: ByteArray): String =
82
87
  llvm """
83
88
  ret %Pos ${arr}
84
89
  """
90
+ chez "(utf8->string ${arr})"
85
91
 
86
92
  extern js """
87
93
  function bytearray$set(bytes, index, value) {
88
94
  bytes[index] = value;
89
95
  return $effekt.unit;
90
96
  }
97
+
98
+ function bytearray$compare(arr1, arr2) {
99
+ const len = Math.min(arr1.length, arr2.length);
100
+
101
+ for (let i = 0; i < len; i++) {
102
+ if (arr1[i] !== arr2[i]) {
103
+ return arr1[i] < arr2[i] ? -1 : 1;
104
+ }
105
+ }
106
+
107
+ if (arr1.length !== arr2.length) {
108
+ return arr1.length < arr2.length ? -1 : 1;
109
+ } else {
110
+ return 0;
111
+ }
112
+ }
113
+ """
114
+
115
+ extern chez """
116
+ (define (bytearray$compare b1 b2)
117
+ (let ([len1 (bytevector-length b1)]
118
+ [len2 (bytevector-length b2)]
119
+ [minlen (min (bytevector-length b1) (bytevector-length b2))])
120
+ (let loop ([i 0])
121
+ (cond
122
+ [(= i minlen)
123
+ (cond [(< len1 len2) -1]
124
+ [(> len1 len2) 1]
125
+ [else 0])]
126
+ [(< (bytevector-u8-ref b1 i) (bytevector-u8-ref b2 i)) -1]
127
+ [(> (bytevector-u8-ref b1 i) (bytevector-u8-ref b2 i)) 1]
128
+ [else (loop (+ i 1))]))))
91
129
  """
130
+
131
+ extern pure def compareByteArrayImpl(b1: ByteArray, b2: ByteArray): Int =
132
+ js "bytearray$compare(${b1}, ${b2})"
133
+ llvm """
134
+ %x = call %Int @c_bytearray_compare(%Pos ${b1}, %Pos ${b2})
135
+ ret %Int %x
136
+ """
137
+ chez "(bytearray$compare ${b1} ${b2})"
138
+
139
+ def compareByteArray(b1: ByteArray, b2: ByteArray): Ordering = {
140
+ val ret = compareByteArrayImpl(b1, b2)
141
+ if (ret == 0) {
142
+ Equal()
143
+ } else if (ret < 0) {
144
+ Less()
145
+ } else { // ret > 0
146
+ Greater()
147
+ }
148
+ }
149
+
150
+ def compareStringBytes(left: String, right: String): Ordering = {
151
+ val l = left.fromString
152
+ val r = right.fromString
153
+ compareByteArray(l, r)
154
+ }
@@ -90,6 +90,7 @@ extern pure def show(value: Char): String =
90
90
 
91
91
  extern pure def show(value: Byte): String =
92
92
  js "'' + ${value}"
93
+ chez "(string ${value})"
93
94
  llvm """
94
95
  %z = call %Pos @c_bytearray_show_Byte(i8 ${value})
95
96
  ret %Pos %z
@@ -166,17 +167,24 @@ type Ordering {
166
167
  Greater()
167
168
  }
168
169
 
170
+ def compareInt(n: Int, m: Int) =
171
+ if (n == m) Equal()
172
+ else if (n < m) Less()
173
+ else Greater()
174
+
169
175
  extern pure def genericCompareImpl[R](x: R, y: R): Int =
170
176
  js "$effekt.compare(${x}, ${y})"
171
177
 
172
178
  /// Compares two values of the same type, returning an `Ordering`.
173
179
  /// Only available on the JavaScript backend.
174
180
  def genericCompare[R](x: R, y: R): Ordering = {
175
- genericCompareImpl(x, y) match {
176
- case -1 => Less()
177
- case 0 => Equal()
178
- case 1 => Greater()
179
- case _ => <{ "Impossible: JS comparison returned invalid value!" }>
181
+ val ret = genericCompareImpl(x, y)
182
+ if (ret == 0) {
183
+ Equal()
184
+ } else if (ret < 0) {
185
+ Less()
186
+ } else { // ret > 0
187
+ Greater()
180
188
  }
181
189
  }
182
190
 
@@ -390,7 +398,7 @@ extern pure def log(x: Double): Double =
390
398
  extern pure def log1p(x: Double): Double =
391
399
  js "Math.log1p(${x})"
392
400
  chez "(log (+ ${x} 1))"
393
- llvm "%z = call %Double @log1p(double ${x}) ret %Double %z"
401
+ llvm "%z = call %Double @log1p(double ${x}) ret %Double %z"
394
402
 
395
403
  extern pure def exp(x: Double): Double =
396
404
  js "Math.exp(${x})"
@@ -571,19 +579,19 @@ extern pure def infixGte(x: Double, y: Double): Bool =
571
579
  // TODO do we really need those? if yes, move to string.effekt
572
580
  extern pure def infixLt(x: String, y: String): Bool =
573
581
  js "(${x} < ${y})"
574
- chez "(< ${x} ${y})"
582
+ chez "(string<? ${x} ${y})"
575
583
 
576
584
  extern pure def infixLte(x: String, y: String): Bool =
577
585
  js "(${x} <= ${y})"
578
- chez "(<= ${x} ${y})"
586
+ chez "(string<=? ${x} ${y})"
579
587
 
580
588
  extern pure def infixGt(x: String, y: String): Bool =
581
589
  js "(${x} > ${y})"
582
- chez "(> ${x} ${y})"
590
+ chez "(string>? ${x} ${y})"
583
591
 
584
592
  extern pure def infixGte(x: String, y: String): Bool =
585
593
  js "(${x} >= ${y})"
586
- chez "(>= ${x} ${y})"
594
+ chez "(string>=? ${x} ${y})"
587
595
 
588
596
 
589
597
  // Boolean operations
@@ -647,10 +655,13 @@ extern pure def bitwiseXor(x: Int, y: Int): Int =
647
655
  extern pure def toByte(n: Int): Byte =
648
656
  js "${n}"
649
657
  llvm "%z = trunc %Int ${n} to %Byte ret %Byte %z"
658
+ chez "${n}"
650
659
 
651
660
  extern pure def toInt(n: Byte): Int =
652
661
  js "${n}"
653
662
  llvm "%z = zext %Byte ${n} to %Int ret %Int %z"
663
+ vm "effekt::toInt(Byte)"
664
+ chez "${n}"
654
665
 
655
666
 
656
667
  // Undefined and Null
@@ -666,11 +677,13 @@ extern pure def toInt(n: Byte): Int =
666
677
  extern pure def undefined[A](): A =
667
678
  js "undefined"
668
679
  chez "#f"
680
+ vm "effekt::undefined()"
669
681
 
670
682
  /// Is an FFI value undefined?
671
683
  extern pure def isUndefined[A](value: A): Bool =
672
684
  js "(${value} === undefined || ${value} === null)"
673
685
  chez "(eq? ${value} #f)"
686
+ vm "effekt::isUndefined[A](A)"
674
687
 
675
688
 
676
689
  // Tuples
@@ -723,6 +736,28 @@ def repeat(n: Int) { action: () => Unit } = each(0, n) { n => action() }
723
736
 
724
737
  def repeat(n: Int) { action: () {Control} => Unit } = each(0, n) { (n) {l} => action() {l} }
725
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
+
726
761
  namespace internal {
727
762
  namespace boxing {
728
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
 
@@ -57,14 +57,17 @@ def server(host: String, port: Int, handler: () => Unit / Socket at {io, async,
57
57
 
58
58
  namespace examples {
59
59
  def helloWorldApp(): Unit / Socket = {
60
- val request = do receive();
60
+ val request = do receive().toString;
61
61
 
62
- println("Received a request: " ++ request.toUTF8)
62
+ println("Received a request: " ++ request)
63
63
 
64
- if (request.toUTF8.startsWith("GET /")) {
65
- do send(fromUTF8("HTTP/1.1 200 OK\r\n\r\nHello from Effekt!"))
64
+ def respond(s: String): Unit / Socket =
65
+ do send(s.fromString)
66
+
67
+ if (request.startsWith("GET /")) {
68
+ respond("HTTP/1.1 200 OK\r\n\r\nHello from Effekt!")
66
69
  } else {
67
- do send("HTTP/1.1 400 Bad Request\r\n\r\n".fromUTF8)
70
+ respond("HTTP/1.1 400 Bad Request\r\n\r\n")
68
71
  }
69
72
  do end()
70
73
  }