@effekt-lang/effekt 0.29.0 → 0.31.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
@@ -1,7 +1,10 @@
1
1
  /// Warning: This library currently only works with ASCII characters, **not** unicode!
2
2
  module char
3
3
 
4
+ import effekt
4
5
  import exception
6
+ import option
7
+ import result
5
8
 
6
9
 
7
10
  /// Checks if the given character is an ASCII whitespace
@@ -15,56 +18,51 @@ def isWhitespace(c: Char): Bool = c match {
15
18
  case _ => false
16
19
  }
17
20
 
18
- /// Gets the value of a given ASCII digit in base 10
19
- def digitValue(char: Char): Option[Int] =
20
- // TODO use fail
21
+ /// Gets the value of a given ASCII digit in base 10,
22
+ /// throwing an exception on wrong format
23
+ def digitValue(char: Char): Int / Exception[WrongFormat] =
21
24
  if (char >= '0' && char <= '9') {
22
- Some(char.toInt - '0'.toInt)
25
+ char.toInt - '0'.toInt
23
26
  } else {
24
- None()
27
+ wrongFormat("Not a valid digit: '" ++ char.toString ++ "' in base 10")
25
28
  }
26
29
 
27
- /// Gets the value of a given ASCII digit in base 16
28
- def hexDigitValue(char: Char): Option[Int] =
29
- // TODO use fail
30
+ /// Gets the value of a given ASCII digit in base 16,
31
+ /// throwing an exception on wrong format
32
+ def hexDigitValue(char: Char): Int / Exception[WrongFormat] =
30
33
  char match {
31
- case char and char >= '0' && char <= '9' => Some(char.toInt - '0'.toInt)
32
- case char and char >= 'A' && char <= 'F' => Some((char.toInt - 'A'.toInt) + 10)
33
- case char and char >= 'a' && char <= 'f' => Some((char.toInt - 'a'.toInt) + 10)
34
- case _ => None()
34
+ case char and char >= '0' && char <= '9' => char.toInt - '0'.toInt
35
+ case char and char >= 'A' && char <= 'F' => (char.toInt - 'A'.toInt) + 10
36
+ case char and char >= 'a' && char <= 'f' => (char.toInt - 'a'.toInt) + 10
37
+ case _ => wrongFormat("Not a valid digit: '" ++ char.toString ++ "' in base 16")
35
38
  }
36
39
 
37
- /// Gets the value of a given ASCII digit in the given base up to 36
38
- def digitValue(char: Char, base: Int): Option[Int] = {
39
- // TODO use fail
40
- val perhapsDigit = char match {
41
- case char and char >= '0' && char <= '9' => Some(char.toInt - '0'.toInt)
42
- case char and char >= 'A' && char <= 'Z' => Some((char.toInt - 'A'.toInt) + 10)
43
- case char and char >= 'a' && char <= 'z' => Some((char.toInt - 'a'.toInt) + 10)
44
- case _ => None()
45
- }
46
- perhapsDigit match {
47
- case Some(digit) =>
48
- if (digit < base) {
49
- Some(digit)
50
- } else {
51
- None()
52
- }
53
- case None() => None()
40
+ /// Gets the value of a given ASCII digit in the given base up to 36,
41
+ /// throwing an exception on wrong format
42
+ def digitValue(char: Char, base: Int): Int / Exception[WrongFormat] = {
43
+ val digit = char match {
44
+ case char and char >= '0' && char <= '9' => char.toInt - '0'.toInt
45
+ case char and char >= 'A' && char <= 'Z' => (char.toInt - 'A'.toInt) + 10
46
+ case char and char >= 'a' && char <= 'z' => (char.toInt - 'a'.toInt) + 10
47
+ case _ => wrongFormat("Not a valid digit: '" ++ char.toString ++ "'")
54
48
  }
49
+ if (digit >= base) {
50
+ wrongFormat("Digit '" ++ digit.show ++ "' is too big for base " ++ base.show)
51
+ }
52
+ digit
55
53
  }
56
54
 
57
55
  /// Checks if the given character is an ASCII digit in base 10
58
- /// Use `digitValue(c: Char)` to get the numeric value out.
59
- def isDigit(char: Char): Bool = digitValue(char).isDefined
56
+ /// Prefer using `digitValue(c: Char)` to get the numeric value out.
57
+ def isDigit(char: Char): Bool = result[Int, WrongFormat] { digitValue(char) }.isSuccess
60
58
 
61
59
  /// Checks if the given character is an ASCII digit in base 16
62
- /// Use `hexDigitValue(c: Char)` to get the numeric value out.
63
- def isHexDigit(char: Char): Bool = hexDigitValue(char).isDefined
60
+ /// Prefer using `hexDigitValue(c: Char)` to get the numeric value out.
61
+ def isHexDigit(char: Char): Bool = result[Int, WrongFormat] { hexDigitValue(char)}.isSuccess
64
62
 
65
63
  /// Checks if the given character is an ASCII digit in base 10
66
- /// Use `digitValue(c: Char)` to get the numeric value out.
67
- def isDigit(char: Char, base: Int): Bool = digitValue(char, base).isDefined
64
+ /// Prefer using `digitValue(c: Char, base: Int)` to get the numeric value out.
65
+ def isDigit(char: Char, base: Int): Bool = result[Int, WrongFormat] { digitValue(char, base) }.isSuccess
68
66
 
69
67
  /// Checks if a given character is a 7-bit ASCII character
70
68
  def isASCII(c: Char): Bool = { c.toInt < 128 }
@@ -80,3 +78,100 @@ def isAlphanumeric(c: Char): Bool = isDigit(c) || isLower(c) || isUpper(c)
80
78
 
81
79
  /// Checks if a given character is an ASCII alphabetic character
82
80
  def isAlphabetic(c: Char): Bool = isLower(c) || isUpper(c)
81
+
82
+ // Characters
83
+ // ----------
84
+ //
85
+ // JS: Int (Unicode codepoints)
86
+ // Chez: ?
87
+ // LLVM: i64 representing utf-8 (varying length 1-4 bytes)
88
+
89
+ extern pure def toString(ch: Char): String =
90
+ js "String.fromCodePoint(${ch})"
91
+ chez "(string (integer->char ${ch}))"
92
+ llvm """
93
+ %z = call %Pos @c_bytearray_show_Char(%Int ${ch})
94
+ ret %Pos %z
95
+ """
96
+
97
+ // Since we currently represent Char by integers in all backends, we could reuse comparison
98
+ extern pure def toInt(ch: Char): Int =
99
+ js "${ch}"
100
+ chez "${ch}"
101
+ llvm "ret %Int ${ch}"
102
+ vm "string::toInt(Char)"
103
+
104
+ extern pure def toChar(codepoint: Int): Char =
105
+ js "${codepoint}"
106
+ chez "${codepoint}"
107
+ llvm "ret %Int ${codepoint}"
108
+ vm "string::toChar(Int)"
109
+
110
+
111
+ extern pure def infixLt(x: Char, y: Char): Bool =
112
+ js "(${x} < ${y})"
113
+ chez "(< ${x} ${y})"
114
+ llvm """
115
+ %z = icmp slt %Int ${x}, ${y}
116
+ %fat_z = zext i1 %z to i64
117
+ %adt_boolean = insertvalue %Pos zeroinitializer, i64 %fat_z, 0
118
+ ret %Pos %adt_boolean
119
+ """
120
+ vm "string::infixLt(Char, Char)"
121
+
122
+ extern pure def infixLte(x: Char, y: Char): Bool =
123
+ js "(${x} <= ${y})"
124
+ chez "(<= ${x} ${y})"
125
+ llvm """
126
+ %z = icmp sle %Int ${x}, ${y}
127
+ %fat_z = zext i1 %z to i64
128
+ %adt_boolean = insertvalue %Pos zeroinitializer, i64 %fat_z, 0
129
+ ret %Pos %adt_boolean
130
+ """
131
+ vm "string::infixLte(Char, Char)"
132
+
133
+ extern pure def infixGt(x: Char, y: Char): Bool =
134
+ js "(${x} > ${y})"
135
+ chez "(> ${x} ${y})"
136
+ llvm """
137
+ %z = icmp sgt %Int ${x}, ${y}
138
+ %fat_z = zext i1 %z to i64
139
+ %adt_boolean = insertvalue %Pos zeroinitializer, i64 %fat_z, 0
140
+ ret %Pos %adt_boolean
141
+ """
142
+ vm "string::infixGt(Char, Char)"
143
+
144
+ extern pure def infixGte(x: Char, y: Char): Bool =
145
+ js "(${x} >= ${y})"
146
+ chez "(>= ${x} ${y})"
147
+ llvm """
148
+ %z = icmp sge %Int ${x}, ${y}
149
+ %fat_z = zext i1 %z to i64
150
+ %adt_boolean = insertvalue %Pos zeroinitializer, i64 %fat_z, 0
151
+ ret %Pos %adt_boolean
152
+ """
153
+ vm "string::infixGte(Char, Char)"
154
+
155
+
156
+ /**
157
+ * Determines the number of bytes needed by a codepoint
158
+ *
159
+ * Also see: https://en.wikipedia.org/wiki/UTF-8
160
+ */
161
+ def utf8ByteCount(codepoint: Char): Int = codepoint match {
162
+ case c and c >= \u0000 and c <= \u007F => 1
163
+ case c and c >= \u0080 and c <= \u07FF => 2
164
+ case c and c >= \u0800 and c <= \uFFFF => 3
165
+ case c and c >= \u10000 and c <= \u10FFFF => 4
166
+ case c => panic("Not a valid code point")
167
+ }
168
+
169
+ def utf16UnitCount(codepoint: Char): Int = codepoint match {
170
+ case c and c >= \u0000 and c <= \uFFFF => 1
171
+ case c and c >= \u10000 and c <= \u10FFFF => 4
172
+ case c => panic("Not a valid code point")
173
+ }
174
+
175
+ extern pure def charWidth(c: Char): Int =
176
+ // JavaScript strings are UTF-16 where every unicode character after 0xffff takes two units
177
+ js "(${c} > 0xffff) ? 2 : 1"
@@ -42,11 +42,32 @@ extern def println(value: String): Unit =
42
42
  js "$effekt.println(${value})"
43
43
  chez "(println_impl ${value})"
44
44
  llvm """
45
- call void @c_io_println_String(%Pos ${value})
45
+ call void @c_io_println(%Pos ${value})
46
46
  ret %Pos zeroinitializer ; Unit
47
47
  """
48
48
  vm "effekt::println(String)"
49
49
 
50
+ extern jsNode """
51
+ $effekt.readln = function readln$impl(callback) {
52
+ const readline = require('node:readline').createInterface({
53
+ input: process.stdin,
54
+ output: process.stdout,
55
+ });
56
+ readline.question('', (answer) => {
57
+ readline.close();
58
+ callback(answer);
59
+ });
60
+ }
61
+ """
62
+
63
+ extern async def readln(): String =
64
+ jsNode "$effekt.capture(callback => $effekt.readln(callback))"
65
+ llvm """
66
+ %result = call %Pos @c_io_readln()
67
+ tail call void @resume_Pos(%Stack %stack, %Pos %result)
68
+ ret void
69
+ """
70
+
50
71
  def println(value: Int): Unit = println(value.show)
51
72
  def println(value: Unit): Unit = println(value.show)
52
73
  def println(value: Double): Unit = println(value.show)
@@ -736,6 +757,9 @@ def repeat(n: Int) { action: () => Unit } = each(0, n) { n => action() }
736
757
 
737
758
  def repeat(n: Int) { action: () {Control} => Unit } = each(0, n) { (n) {l} => action() {l} }
738
759
 
760
+ // NOTE: This is emitted by the close hole code action: do not remove unless you also adjust the code action
761
+ /// Scopes a local computation
762
+ def locally[R] { p: => R } : R = p()
739
763
 
740
764
  // Splices
741
765
  // =======
@@ -54,7 +54,7 @@ extern io def panic(msg: String): Nothing =
54
54
  js "(function() { throw ${msg} })()"
55
55
  chez "(raise ${msg})"
56
56
  llvm """
57
- call void @c_io_println_String(%Pos ${msg})
57
+ call void @c_io_println(%Pos ${msg})
58
58
  call void @exit(i32 1)
59
59
  ret %Pos zeroinitializer ; Unit
60
60
  """
@@ -1,6 +1,5 @@
1
1
  module json
2
2
 
3
- import char
4
3
  import scanner
5
4
  import stream
6
5
 
@@ -390,4 +389,4 @@ namespace test {
390
389
  resume(println(e))
391
390
  }
392
391
  }
393
- }
392
+ }
@@ -35,3 +35,9 @@ def toOption[A, E](r: Result[A, E]): Option[A] = r match {
35
35
  case Success(a) => Some(a)
36
36
  case Error(exc, msg) => None()
37
37
  }
38
+
39
+ def isError[A, E](r: Result[A, E]): Bool =
40
+ if (r is Error(_, _)) true else false
41
+
42
+ def isSuccess[A, E](r: Result[A, E]): Bool =
43
+ if (r is Success(_)) true else false
@@ -1,6 +1,5 @@
1
1
  module scanner
2
2
 
3
- import char
4
3
  import stream
5
4
 
6
5
  interface Scan[A] {
@@ -49,6 +48,15 @@ def readSome[A, B] { convert: A => Option[B] }: B / { Scan[A], stop } =
49
48
  do stop()
50
49
  }
51
50
 
51
+ /// like `readSome`, but for functions `A => B / Exception[WrongFormat]` such as `char::digitValue`
52
+ def tryRead[A, B] { convert: A => B / Exception[WrongFormat] }: B / { Scan[A], stop } = {
53
+ with on[WrongFormat].default { do stop() }
54
+
55
+ val t = convert(do peek())
56
+ do skip[A]()
57
+ return t
58
+ }
59
+
52
60
  /// Reads until the predicate does not hold for the next token.
53
61
  /// Emits the tokens read.
54
62
  def readWhile[A] { predicate: A => Bool }: Unit / { Scan[A], emit[A] } =
@@ -78,7 +86,7 @@ def readString(string: String): Unit / { Scan[Char], stop } =
78
86
 
79
87
  /// Check that the next character is a digit in base 10, and if so read and return it.
80
88
  def readDigit(): Int / { Scan[Char], stop } =
81
- readSome[Char, Int]{ char => digitValue(char) }
89
+ tryRead[Char, Int]{ char => digitValue(char) }
82
90
 
83
91
  /// Read a positive decimal number.
84
92
  def readDecimal(): Int / Scan[Char] = {
@@ -91,7 +99,7 @@ def readDecimal(): Int / Scan[Char] = {
91
99
 
92
100
  /// Check that the next character is a digit in base 16, and if so read and return it.
93
101
  def readHexDigit(): Int / { Scan[Char], stop } =
94
- readSome[Char, Int]{ char => hexDigitValue(char) }
102
+ tryRead[Char, Int]{ char => hexDigitValue(char) }
95
103
 
96
104
  /// Read a hexadecimal number.
97
105
  def readHexadecimal(): Int / Scan[Char] = {
@@ -173,10 +173,23 @@ def replicate[A](number: Int) { action: () => A }: Unit / emit[A] =
173
173
  replicate(number - 1) {action}
174
174
  }
175
175
 
176
+ /// Creates an infinite iterated stream given by an `initial` seed and a `step` function:
177
+ /// iterate(a){f} ~> a, f(a), f(f(a)), f(f(f(a))), ...
178
+ def iterate[A](initial: A) { step: A => A }: Unit / emit[A] = {
179
+ var current = initial
180
+ while (true) {
181
+ do emit(current)
182
+ current = step(current)
183
+ }
184
+ }
176
185
 
177
- def sum { stream : () => Unit / emit[Int] }: Int =
186
+
187
+ def sum { stream: () => Unit / emit[Int] }: Int =
178
188
  returning::sum[Unit]{stream}.second
179
189
 
190
+ def product { stream: () => Unit / emit[Int] }: Int =
191
+ returning::product[Unit]{stream}.second
192
+
180
193
  def collectList[A] { stream: () => Unit / emit[A] }: List[A] =
181
194
  returning::collectList[A, Unit]{stream}.second
182
195
 
@@ -409,6 +422,88 @@ def each(string: String): Unit / emit[Char] =
409
422
  def collectString { stream: () => Unit / emit[Char] }: String =
410
423
  returning::collectString[Unit]{stream}.second
411
424
 
425
+ namespace internal {
426
+ effect snapshot(): Unit
427
+ }
428
+
429
+ /// Use cons to handle prod, but also emit to the outside.
430
+ /// The outside controls iteration, i.e., if cons aborts, the rest of prod will still be emitted.
431
+ ///
432
+ /// Starts by executing cons. Will stop cons during execution if the outside stops consuming.
433
+ ///
434
+ /// Example, printing all values consumed by the outside:
435
+ ///
436
+ /// with teeing{ {s} => for{s}{ e => println(e) } }
437
+ /// prod() // some producer
438
+ ///
439
+ /// Laws-ish (hopefully):
440
+ /// - hnd{ prd() } === hnd{ teeing{t}{ prd() } } for all hnd and t that calls its argument at most once (and has no other captures)
441
+ def teeing[A]{ cons: { => Unit / emit[A] } => Unit }{ prod: => Unit / emit[A] }: Unit / emit[A] = region r {
442
+ var consDone = false
443
+ var k in r = box { prod() } // what still needs to run of prod after cons exits
444
+ try {
445
+ cons{
446
+ try {
447
+ prod()
448
+ k = box { () }
449
+ } with emit[A] { v =>
450
+ do internal::snapshot() // remember that we need to continue here if cons aborts
451
+ if(not(consDone)) do emit(v)
452
+ outer.emit(v)
453
+ resume(())
454
+ }
455
+ if(consDone) do stop() // cons already exited, so skip continuing there
456
+ } // end cons
457
+ consDone = true
458
+ }
459
+ with stop { () => () }
460
+ with outer: emit[A]{ v => resume(do emit(v)) }
461
+ with internal::snapshot{ () =>
462
+ k = box { resume(()) }
463
+ resume(())
464
+ }
465
+ k()
466
+ }
467
+
468
+ /// Binds a `teeing`-like function in the body, stopping the inner producer once all consumers in tees are done consuming.
469
+ ///
470
+ /// Example of use, equivalent to `tee[A]{cns1}{cns2}{prd}`:
471
+ ///
472
+ /// manyTee[A] { {tee} =>
473
+ /// with tee{cns1}
474
+ /// with tee{cns2}
475
+ /// prd()
476
+ /// }
477
+ ///
478
+ def manyTee[A]{ body: { { { => Unit / emit[A] } => Unit }{ => Unit / emit[A] } => Unit / emit[A] } => Unit / emit[A] }: Unit = {
479
+ var running = 0
480
+ try {
481
+ body{ {hnd}{prod} =>
482
+ running = running + 1
483
+ teeing[A]{ {b} => hnd{b}; running = running - 1 }{prod}
484
+ }
485
+ } with emit[A] { _ =>
486
+ if(running > 0) resume(())
487
+ }
488
+ }
489
+
490
+ /// Streams prod to both cons1 and cons2. Stops once both cons1 and cons2 stopped.
491
+ /// Only runs prod once, resuming at most once at each emit.
492
+ ///
493
+ /// var sumRes = 0
494
+ /// var productRes = 0
495
+ /// tee{ s => sumRes = sum{s} }{ s => productRes = product{s} }{ range(0, 10) }
496
+ /// assertEquals(sum{ range(0, 10) }, sumRes)
497
+ /// assertEquals(product{ range(0, 10) }, productRes)
498
+ ///
499
+ def tee[A]{ cons1: { => Unit / emit[A] } => Unit }{ cons2: { => Unit / emit[A] } => Unit }{ prod: => Unit / emit[A] }: Unit = {
500
+ manyTee[A]{ {tee} =>
501
+ with tee{cons1}
502
+ with tee{cons2}
503
+ prod()
504
+ }
505
+ }
506
+
412
507
  namespace returning {
413
508
 
414
509
  /// Canonical handler of push streams that performs `action` for every
@@ -461,7 +556,7 @@ def limit[A, R](number: Int) { stream: () => R / emit[A] }: R / { emit[A], stop
461
556
  }
462
557
  }
463
558
 
464
- def sum[R] { stream : () => R / emit[Int] }: (R, Int) = {
559
+ def sum[R] { stream: () => R / emit[Int] }: (R, Int) = {
465
560
  var s = 0;
466
561
  try {
467
562
  (stream(), s)
@@ -471,6 +566,16 @@ def sum[R] { stream : () => R / emit[Int] }: (R, Int) = {
471
566
  }
472
567
  }
473
568
 
569
+ def product[R] { stream: () => R / emit[Int] }: (R, Int) = {
570
+ var s = 1;
571
+ try {
572
+ (stream(), s)
573
+ } with emit[Int] { v =>
574
+ s = s * v;
575
+ resume(())
576
+ }
577
+ }
578
+
474
579
  def collectList[A, R] { stream: () => R / emit[A] }: (R, List[A]) =
475
580
  try {
476
581
  (stream(), Nil())
@@ -5,6 +5,7 @@ import option
5
5
  import list
6
6
  import exception
7
7
  import result
8
+ import char
8
9
 
9
10
  // TODO
10
11
  // - [ ] handle unicode codepoints (that can span two indices) correctly
@@ -61,12 +62,22 @@ def endsWith(str: String, suffix: String): Bool =
61
62
  /// TODO use a more efficient way of appending strings like a buffer
62
63
  def repeat(str: String, n: Int): String = {
63
64
  def go(n: Int, result: String): String = {
64
- if (n == 0) result
65
+ if (n <= 0) result
65
66
  else go(n - 1, result ++ str)
66
67
  }
67
68
  go(n, "")
68
69
  }
69
70
 
71
+ /// Left-pad the given string with spaces
72
+ def padLeft(str: String, n: Int): String = {
73
+ " ".repeat(n - str.length) ++ str
74
+ }
75
+
76
+ /// Right-pad the given string with spaces
77
+ def padRight(str: String, n: Int): String = {
78
+ str ++ " ".repeat(n - str.length)
79
+ }
80
+
70
81
  // TODO use .split() in JS
71
82
  def split(str: String, sep: String): List[String] = {
72
83
  val strLength = str.length
@@ -113,20 +124,18 @@ def toBool(s: String): Bool / Exception[WrongFormat] = s match {
113
124
 
114
125
  // TODO optimize (right now this will be horribly slow (compared to the native JS version)
115
126
  def toInt(str: String): Int / Exception[WrongFormat] = {
116
-
117
- val zero = '0'.toInt
118
-
119
127
  def go(index: Int, acc: Int): Int = {
120
- result[Char, OutOfBounds] { str.charAt(index) } match {
121
- case Success(c) and c >= '0' and c <= '9' =>
122
- go(index + 1, 10 * acc + (c.toInt - zero))
123
- case Success(c) => wrongFormat("Not a valid number: '" ++ str ++ "'")
124
- // wrong index means we are done parsing
125
- case Error(_, _) => acc
126
- }
128
+ // wrong index means we're done parsing
129
+ with on[OutOfBounds].default { () => acc };
130
+
131
+ val c = str.charAt(index)
132
+ val d = char::digitValue(c)
133
+ go(index + 1, 10 * acc + d)
127
134
  }
128
135
 
129
- with default[OutOfBounds, Int] { wrongFormat("Empty string is not a valid number") };
136
+ with on[OutOfBounds].default {
137
+ wrongFormat("Empty string is not a valid number")
138
+ }
130
139
 
131
140
  str.charAt(0) match {
132
141
  case '-' => 0 - go(1, 0)
@@ -136,38 +145,22 @@ def toInt(str: String): Int / Exception[WrongFormat] = {
136
145
 
137
146
  // TODO optimize (right now this will be horribly slow (compared to the native JS version)
138
147
  def toInt(str: String, base: Int): Int / Exception[WrongFormat] = {
139
-
140
- if( base > 36 || base < 1 ) {
148
+ if (base > 36 || base < 1) {
141
149
  wrongFormat("Invalid base: " ++ base.show)
142
150
  }
143
151
 
144
- val zero = '0'.toInt
145
- val l_a = 'a'.toInt
146
- val u_a = 'A'.toInt
147
-
148
- def parseDigit(c: Char): Option[Int] = {
149
- if( c >= '0' and c <= '9' and c.toInt - zero < base ) {
150
- Some(c.toInt - zero)
151
- } else if( c >= 'a' and c <= 'z' and c.toInt - l_a < base - 10 ) {
152
- Some(c.toInt - l_a + 10)
153
- } else if( c >= 'A' and c <= 'Z' and c.toInt - u_a < base - 10) {
154
- Some(c.toInt - u_a + 10)
155
- } else {
156
- None()
157
- }
158
- }
159
-
160
152
  def go(index: Int, acc: Int): Int = {
161
- result[Char, OutOfBounds] { str.charAt(index) } match {
162
- case Success(c) and parseDigit(c) is Some(d) =>
163
- go(index + 1, base * acc + d)
164
- case Success(c) => wrongFormat("Not a valid number: '" ++ str ++ "'")
165
- // wrong index means we are done parsing
166
- case Error(_, _) => acc
167
- }
153
+ // wrong index means we're done parsing
154
+ with on[OutOfBounds].default { () => acc };
155
+
156
+ val c = str.charAt(index)
157
+ val d = char::digitValue(c, base)
158
+ go(index + 1, base * acc + d)
168
159
  }
169
160
 
170
- with default[OutOfBounds, Int] { wrongFormat("Empty string is not a valid number") };
161
+ with on[OutOfBounds].default {
162
+ wrongFormat("Empty string is not a valid number")
163
+ }
171
164
 
172
165
  str.charAt(0) match {
173
166
  case '-' => 0 - go(1, 0)
@@ -222,104 +215,6 @@ def lastIndexOf(str: String, sub: String, from: Int): Option[Int] = {
222
215
  // extern pure def unsafeIndexOf(str: String, sub: String): Int =
223
216
  // js "${str}.indexOf(${sub})"
224
217
 
225
-
226
- // Characters
227
- // ----------
228
- //
229
- // JS: Int (Unicode codepoints)
230
- // Chez: ?
231
- // LLVM: i64 representing utf-8 (varying length 1-4 bytes)
232
-
233
- extern pure def toString(ch: Char): String =
234
- js "String.fromCodePoint(${ch})"
235
- chez "(string (integer->char ${ch}))"
236
- llvm """
237
- %z = call %Pos @c_bytearray_show_Char(%Int ${ch})
238
- ret %Pos %z
239
- """
240
-
241
- // Since we currently represent Char by integers in all backends, we could reuse comparison
242
- extern pure def toInt(ch: Char): Int =
243
- js "${ch}"
244
- chez "${ch}"
245
- llvm "ret %Int ${ch}"
246
- vm "string::toInt(Char)"
247
-
248
- extern pure def toChar(codepoint: Int): Char =
249
- js "${codepoint}"
250
- chez "${codepoint}"
251
- llvm "ret %Int ${codepoint}"
252
- vm "string::toChar(Int)"
253
-
254
-
255
- extern pure def infixLt(x: Char, y: Char): Bool =
256
- js "(${x} < ${y})"
257
- chez "(< ${x} ${y})"
258
- llvm """
259
- %z = icmp slt %Int ${x}, ${y}
260
- %fat_z = zext i1 %z to i64
261
- %adt_boolean = insertvalue %Pos zeroinitializer, i64 %fat_z, 0
262
- ret %Pos %adt_boolean
263
- """
264
- vm "string::infixLt(Char, Char)"
265
-
266
- extern pure def infixLte(x: Char, y: Char): Bool =
267
- js "(${x} <= ${y})"
268
- chez "(<= ${x} ${y})"
269
- llvm """
270
- %z = icmp sle %Int ${x}, ${y}
271
- %fat_z = zext i1 %z to i64
272
- %adt_boolean = insertvalue %Pos zeroinitializer, i64 %fat_z, 0
273
- ret %Pos %adt_boolean
274
- """
275
- vm "string::infixLte(Char, Char)"
276
-
277
- extern pure def infixGt(x: Char, y: Char): Bool =
278
- js "(${x} > ${y})"
279
- chez "(> ${x} ${y})"
280
- llvm """
281
- %z = icmp sgt %Int ${x}, ${y}
282
- %fat_z = zext i1 %z to i64
283
- %adt_boolean = insertvalue %Pos zeroinitializer, i64 %fat_z, 0
284
- ret %Pos %adt_boolean
285
- """
286
- vm "string::infixGt(Char, Char)"
287
-
288
- extern pure def infixGte(x: Char, y: Char): Bool =
289
- js "(${x} >= ${y})"
290
- chez "(>= ${x} ${y})"
291
- llvm """
292
- %z = icmp sge %Int ${x}, ${y}
293
- %fat_z = zext i1 %z to i64
294
- %adt_boolean = insertvalue %Pos zeroinitializer, i64 %fat_z, 0
295
- ret %Pos %adt_boolean
296
- """
297
- vm "string::infixGte(Char, Char)"
298
-
299
-
300
- /**
301
- * Determines the number of bytes needed by a codepoint
302
- *
303
- * Also see: https://en.wikipedia.org/wiki/UTF-8
304
- */
305
- def utf8ByteCount(codepoint: Char): Int = codepoint match {
306
- case c and c >= \u0000 and c <= \u007F => 1
307
- case c and c >= \u0080 and c <= \u07FF => 2
308
- case c and c >= \u0800 and c <= \uFFFF => 3
309
- case c and c >= \u10000 and c <= \u10FFFF => 4
310
- case c => panic("Not a valid code point")
311
- }
312
-
313
- def utf16UnitCount(codepoint: Char): Int = codepoint match {
314
- case c and c >= \u0000 and c <= \uFFFF => 1
315
- case c and c >= \u10000 and c <= \u10FFFF => 4
316
- case c => panic("Not a valid code point")
317
- }
318
-
319
- extern pure def charWidth(c: Char): Int =
320
- // JavaScript strings are UTF-16 where every unicode character after 0xffff takes two units
321
- js "(${c} > 0xffff) ? 2 : 1"
322
-
323
218
  def charAt(str: String, index: Int): Char / Exception[OutOfBounds] =
324
219
  if (index < 0 || index >= length(str))
325
220
  do raise(OutOfBounds(), "Index out of bounds: " ++ show(index) ++ " in string: '" ++ str ++ "'")
@@ -58,4 +58,4 @@ $effekt.unit = { __unit: true }
58
58
 
59
59
  $effekt.emptyMatch = function() { throw "empty match" }
60
60
 
61
- $effekt.hole = function() { throw "not implemented, yet" }
61
+ $effekt.hole = function() { throw "not implemented, yet" }