diff --git a/effekt/shared/src/main/scala/effekt/core/vm/Builtin.scala b/effekt/shared/src/main/scala/effekt/core/vm/Builtin.scala index 1b3209fa3..be2a2818b 100644 --- a/effekt/shared/src/main/scala/effekt/core/vm/Builtin.scala +++ b/effekt/shared/src/main/scala/effekt/core/vm/Builtin.scala @@ -150,6 +150,9 @@ lazy val integers: Builtins = Map( builtin("effekt::bitwiseXor(Int, Int)") { case As.Int(x) :: As.Int(y) :: Nil => Value.Int(x ^ y) }, + builtin("effekt::bitwiseNot(Int)") { + case As.Int(x) :: Nil => Value.Int(~x) + }, // Comparison // ---------- diff --git a/examples/stdlib/acme.effekt b/examples/stdlib/acme.effekt index cffcd9178..f1afdadba 100644 --- a/examples/stdlib/acme.effekt +++ b/examples/stdlib/acme.effekt @@ -5,6 +5,7 @@ module acme import args import array import bench +import binstream import buffer import bytearray import char diff --git a/examples/stdlib/binstream.check b/examples/stdlib/binstream.check new file mode 100644 index 000000000..2f0bc23f8 --- /dev/null +++ b/examples/stdlib/binstream.check @@ -0,0 +1,20 @@ +Binstream +✓ literal hex 10 +✓ literal hex ff +✓ literal char a +✓ literal string ba +✓ int back-and-forth (17) +✓ int back-and-forth (17), explicit BE +✓ int back-and-forth (17), explicit LE +✓ byte 00101010 +✓ to bits and back LE bitorder +✓ to bits and back BE bitorder +✓ append 0 means *2 +✓ pow agrees with double one +✓ LE 2s-complement +✓ BE 2s-complement + + 14 pass + 0 fail + 14 tests total + \ No newline at end of file diff --git a/examples/stdlib/binstream.effekt b/examples/stdlib/binstream.effekt new file mode 100644 index 000000000..14d08dca6 --- /dev/null +++ b/examples/stdlib/binstream.effekt @@ -0,0 +1,49 @@ +import binstream +import stream +import test + +def main() = { + suite("Binstream", false){ + test("literal hex 10"){ assert(x"10${()}", 16) } + test("literal hex ff"){ assert(x"ff${()}", 255) } + test("literal char a"){ assert(x"${'a'}", x"61${()}") } + test("literal string ba"){ assert(x"${"ba"}", x"62${()}" * 256 + x"61${()}") } + test("int back-and-forth (17)"){ assert(x"${17}", 17)} + test("int back-and-forth (17), explicit BE"){ assert(x"${17.BE}", 17) } + test("int back-and-forth (17), explicit LE"){ assert(x"${17.LE}", 17 * 256 * 256 * 256) } + test("byte 00101010"){ + with on[MissingValue].assertNotThrown + assert(first[Byte]{groupBytesBE{ bit"00101010${()}" }}.toInt, 42) + } + test("to bits and back LE bitorder"){ + with on[MissingValue].assertNotThrown + [42.toByte, 12.toByte, 113.toByte, 0.toByte, 255.toByte].foreach{ v => + assert(first[Byte]{ groupBytesLE{ bitsLE(v) } }, v) + } + } + test("to bits and back BE bitorder"){ + with on[MissingValue].assertNotThrown + [42.toByte, 12.toByte, 113.toByte, 0.toByte, 255.toByte].foreach{ v => + assert(first[Byte]{ groupBytesBE{ bitsBE(v) } }, v) + } + } + test("append 0 means *2"){ + with on[MissingValue].assertNotThrown + [42.toByte, 12.toByte, 127.toByte].foreach{ v => + assert(nth[Byte](1){ groupBytesBE{ repeat(7){ do emit(B0()) }; bitsBE(v); do emit(B0()) } }, (v.toInt * 2).toByte) + } + } + test("pow agrees with double one"){ + assert(pow(2,5), pow(2.0,5).toInt) + } + test("LE 2s-complement"){ + with on[MissingValue].assertNotThrown + assert(first[Byte]{ groupBytesLE{ twoscomplementLE{ bitsLE(6.toByte) } } }, 250.toByte) + } + test("BE 2s-complement"){ + with on[MissingValue].assertNotThrown + assert(first[Byte]{ groupBytesBE{ bit"${-6.Signed.BE.OfWidth(8)}" } }, 250.toByte) + } + } + () +} \ No newline at end of file diff --git a/examples/stdlib/test/exceptions.check b/examples/stdlib/test/exceptions.check new file mode 100644 index 000000000..c39838f74 --- /dev/null +++ b/examples/stdlib/test/exceptions.check @@ -0,0 +1,17 @@ +Test test suite on Exceptions +✓ Test test assertNotThrown pass +✕ Test test assertNotThrown fail + Unexpected Exception +✕ Test test assertThrown fail + Expected Exception, but exited normally +✓ Test test assertThrows pass +✓ Test test assertNoThrow pass +✕ Test test assertNoThrow fail + Unexpected Exception +✕ Test test assertThrows fail + Expected Exception, but exited normally +✓ Test test assertThrows pass + + 4 pass + 4 fail + 8 tests total diff --git a/examples/stdlib/test/exceptions.effekt b/examples/stdlib/test/exceptions.effekt new file mode 100644 index 000000000..224840897 --- /dev/null +++ b/examples/stdlib/test/exceptions.effekt @@ -0,0 +1,44 @@ +import test + +def main() = { + // Don't print out times in CI. + suite("Test test suite on Exceptions", false) { + // on[E] variant + // ------------- + test("Test test assertNotThrown pass") { + with on[MissingValue].assertNotThrown + assert(Some(12).value, 12) + } + test("Test test assertNotThrown fail") { + with on[MissingValue].assertNotThrown + assert(None().value, 12) + } + test("Test test assertThrown fail") { + with on[MissingValue].assertThrown + assert(Some(12).value, 12) + } + test("Test test assertThrows pass") { + with on[MissingValue].assertThrown[MissingValue] + assert(None().value, 12) + } + // direct variant + // -------------- + test("Test test assertNoThrow pass") { + with assertNoThrow[MissingValue] + assert(Some(12).value, 12) + } + test("Test test assertNoThrow fail") { + with assertNoThrow[MissingValue] + assert(None().value, 12) + } + test("Test test assertThrows fail") { + with assertThrows[MissingValue] + assert(Some(12).value, 12) + } + test("Test test assertThrows pass") { + with assertThrows[MissingValue] + assert(None().value, 12) + } + }; + () +} diff --git a/libraries/common/binstream.effekt b/libraries/common/binstream.effekt new file mode 100644 index 000000000..cf8250231 --- /dev/null +++ b/libraries/common/binstream.effekt @@ -0,0 +1,336 @@ +import option +import stringbuffer +import stream +import char +import io/error +import bytearray + +// assumes by default: +// BE byteorder, BE bitorder, unsigned + +// Wrappers +// -------- +/// A with explicit big-endian order +record BE[A](raw: A) +/// A with explicit little-endian order +record LE[A](raw: A) +/// A with explicit width in current unit +/// (bits for bitstreams, bytes for bytestreams) +record OfWidth[A](raw: A, width: Int) +/// explicitly signed A +record Signed[A](raw: A) + +/// Bits +type Bit { B0(); B1() } + +/// not on Bits +def not(b: Bit): Bit = b match { + case B0() => B1() + case B1() => B0() +} + +/// Splices allowed in hex/byte stream literals +effect HexSplices = { + splice[Char], splice[String], + splice[Unit], + splice[Int], + splice[Byte], + splice[BE[Int]], splice[LE[Int]], + splice[LE[Signed[Int]]], splice[OfWidth[LE[Int]]], splice[OfWidth[LE[Signed[Int]]]], + splice[BE[Signed[Int]]], splice[OfWidth[BE[Int]]], splice[OfWidth[BE[Signed[Int]]]], + splice[ByteArray] +} + +// Splitting +// --------- + +/// emit bytes of the given int as a w bytes in little-endian byte order +def bytesLE(int: Int, w: Int): Unit / emit[Byte] = { + var c = int + repeat(w){ + do emit(mod(c, 256).toByte) + c = c / 256 + } +} + +/// emit bytes of the given int as a 4 bytes in little-endian byte order +def bytesLE(int: Int): Unit / emit[Byte] = bytesLE(int, 4) + +/// emit bytes of the given int as a w bytes in big-endian byte order +def bytesBE(n: Int, width: Int): Unit / emit[Byte] = { + var pos = pow(256, width - 1) + repeat(width){ + do emit((bitwiseAnd(n, pos * 255) / pos).toByte) + pos = pos / 256 + } +} + +/// emit bytes of the given int as a 4 bytes in big-endian byte order +def bytesBE(n: Int): Unit / emit[Byte] = bytesBE(n, 4) + +/// emit bytes of the given int as width bytes (in 2s-complement) in little-endian byte order +def signedBytesLE(int: Int, width: Int): Unit / emit[Byte] = + if (int < 0) { + bytesLE(bitwiseNot(neg(int)) + 1, width) + } else { + bytesLE(int, width) + } +/// emit bytes of the given int as width bytes (in 2s-complement) in big-endian byte order +def signedBytesBE(int: Int, width: Int): Unit / emit[Byte] = { + if (int < 0) { + bytesBE(bitwiseNot(neg(int)) + 1, width) + } else { + bytesBE(int, width) + } +} + +/// emit bytes of the given int as 4 bytes (in 2s-complement) in little-endian byte order +def signedBytesLE(int: Int): Unit / emit[Byte] = signedBytesLE(int, 4) + +/// emit bytes of the given int as 4 bytes (in 2s-complement) in big-endian byte order +def signedBytesBE(int: Int): Unit / emit[Byte] = signedBytesBE(int, 4) + + + +// Splicers +// -------- + +/// Splicer to emit the bytes in hex notation given, plus eventual splices +/// Ignores whitespace +def hex{ body: => Unit / { literal, HexSplices } }: Unit / emit[Byte] = { + try { + try { + body() + } + with splice[String] { s => + feed(s){ exhaustively{ do splice[Char](do read[Char]()) } } + resume(()) + } + with splice[ByteArray] { ba => + ba.foreach{ b => do splice[Byte](b) } + resume(()) + } + } + with literal { s => + feed(s){ + exhaustively { + with on[MissingValue].default{ () } + val upper: Int = hexDigitValue(do read[Char]()).value + val lower: Int = hexDigitValue(do read[Char]()).value + do emit[Byte]((16 * upper + lower).toByte) + } + } + resume(()) + } + with splice[Char] { c => do emit[Byte](c.toInt.toByte); resume(()) } + with splice[Byte] { b => do emit(b); resume(()) } + with splice[Unit] { u => resume(()) } + with splice[Int] { n => bytesBE(n); resume(()) } + with splice[LE[Int]] { w => bytesLE(w.raw); resume(()) } + with splice[BE[Int]] { v => bytesBE(v.raw); resume(()) } + with splice[LE[Signed[Int]]] { w => signedBytesLE(w.raw.raw); resume(()) } + with splice[OfWidth[LE[Int]]] { w => bytesLE(w.raw.raw, w.width); resume(()) } + with splice[OfWidth[LE[Signed[Int]]]] { w => signedBytesLE(w.raw.raw.raw, w.width); resume(()) } + with splice[BE[Signed[Int]]] { w => signedBytesBE(w.raw.raw); resume(()) } + with splice[OfWidth[BE[Int]]] { w => bytesBE(w.raw.raw, w.width); resume(()) } + with splice[OfWidth[BE[Signed[Int]]]] { w => signedBytesBE(w.raw.raw.raw, w.width); resume(()) } +} + +/// convert the given hex notation to an integer (big-endian) +def x{ body: => Unit / { literal, HexSplices } }: Int = { + var res = 0 + for[Byte]{ hex{body} }{ v => res = res * 256 + v.toInt } + res +} + + +// Sub-Byte +// ======== + +// From/to Bytes +// ------------- +/// emit bits of the given Byte as 8 Bits in little-endian bit order +def bitsLE(byte: Byte): Unit / emit[Bit] = { + val v = byte.toInt + var mask = 1 + repeat(8){ + bitwiseAnd(v, mask) match { + case 0 => do emit(B0()) + case _ => do emit(B1()) + } + mask = mask * 2 + } +} + +/// emit bits of the given Byte as 8 Bits in big-endian bit order +def bitsBE(byte: Byte): Unit / emit[Bit] = { + val v = byte.toInt + var mask = 128 + repeat(8){ + bitwiseAnd(v, mask) match { + case 0 => do emit(B0()) + case _ => do emit(B1()) + } + mask = mask / 2 + } +} + +/// emit bits of the given Byte as width Bits in little-endian bit order +def bitsLE(v: Int, width: Int): Unit / emit[Bit] = { + var mask = 1 + repeat(width){ + bitwiseAnd(v, mask) match { + case 0 => do emit(B0()) + case _ => do emit(B1()) + } + mask = mask * 2 + } +} + +/// emit bits of the given int as width Bits in big-endian bit order +def bitsBE(v: Int, width: Int): Unit / emit[Bit] = { + var mask = pow(2, width - 1) + repeat(width){ + bitwiseAnd(v, mask) match { + case 0 => do emit(B0()) + case _ => do emit(B1()) + } + mask = mask / 2 + } +} + +/// emit bits of the given int as 32 Bits in big-endian bit order +def bitsBE(int: Int): Unit / emit[Bit] = bitsBE(int, 32) + +/// collect bits in big-endian bit order into an Int +def collectBitsBE{ body: => Unit / emit[Bit] }: Int = { + var res = 0 + try body() with emit[Bit] { b => + res = b match { + case B0() => res * 2 + case B1() => res * 2 + 1 + } + resume(()) + } + res +} + +/// split emitted bytes and emit the individual bits in big-endian bit order +def ungroupBytesBE{ body: => Unit / emit[Byte] }: Unit / emit[Bit] = + for[Byte]{body}{ b => bitsBE(b) } + +/// streaming negation in 2s-complement for little-endian bitstreams +def twoscomplementLE{ body: => Unit / emit[Bit] }: Unit / emit[Bit] = { + var carry = true + try body() + with emit[Bit] { + case B0() => if(carry) { do emit(B0()) } else { do emit(B1()) }; resume(()) + case B1() => if(carry) { do emit(B1()); carry = false } else { do emit(B0()) }; resume(()) + } +} + +/// group 8 bits into a byte each, big-endian bit order. +/// NOTE: The remainder is dropped. +def groupBytesBE{ body: => Unit / emit[Bit] }: Unit / emit[Byte] = { + var next = 0 + var p = 128 + for[Bit]{body}{ b => + b match { + case B0() => () + case B1() => next = next + p + } + p = p / 2 + if(p == 0) { + do emit(next.toByte) + next = 0 + p = 128 + } + } +} + +/// group 8 bits into a byte each, little-endian bit order. +/// NOTE: The remainder is dropped. +def groupBytesLE{ body: => Unit / emit[Bit] }: Unit / emit[Byte] = { + var next = 0 + var p = 1 + for[Bit]{body}{ b => + b match { + case B0() => () + case B1() => next = next + p + } + p = p * 2 + if(p == 256) { + do emit(next.toByte) + next = 0 + p = 1 + } + } +} + +// Literals/splices +// ---------------- +/// Splices allowed in bit stream literals +effect BitSplices = { + splice[Unit], splice[Bit], + splice[Byte], + splice[LE[Int]], splice[BE[Int]], + splice[LE[Signed[Int]]], splice[BE[Signed[Int]]], + splice[OfWidth[LE[Int]]], splice[OfWidth[BE[Int]]], + splice[OfWidth[LE[Signed[Int]]]], splice[OfWidth[BE[Signed[Int]]]] +} +/// Splicer to emit the bits in binary notation given, plus evenutal splices +/// Ignores whitespace +def bit{ body: => Unit / { literal, BitSplices } }: Unit / emit[Bit] = { + try { + ungroupBytesBE{ + try { + body() + } + with splice[LE[Int]] { i => bytesLE(i.raw); resume(()) } + with splice[BE[Int]] { i => bytesBE(i.raw); resume(()) } + with splice[LE[Signed[Int]]] { i => signedBytesLE(i.raw.raw); resume(()) } + with splice[BE[Signed[Int]]] { i => signedBytesBE(i.raw.raw); resume(()) } + } + } + with literal { s => + feed(s){ + exhaustively { + do read[Char]() match { + case '0' => do emit(B0()) + case '1' => do emit(B1()) + case _ => () + } + } + } + resume(()) + } + with splice[Unit] { _ => resume(()) } + with splice[Bit] { b => do emit(b); resume(()) } + with splice[Byte] { b => bitsBE(b); resume(()) } + with splice[OfWidth[LE[Int]]] { i => + bitsLE(i.raw.raw, i.width) + resume(()) + } + with splice[OfWidth[LE[Signed[Int]]]] { i => + if(i.raw.raw.raw < 0){ + twoscomplementLE{ bitsLE(0 - i.raw.raw.raw, i.width) } + } else { + bitsLE(i.raw.raw.raw, i.width) + } + resume(()) + } + with splice[OfWidth[BE[Int]]] { i => + bitsBE(i.raw.raw, i.width) + resume(()) + } + with splice[OfWidth[BE[Signed[Int]]]] { i => + collectList[Bit]{ + if(i.raw.raw.raw < 0){ + twoscomplementLE{ bitsLE(0 - i.raw.raw.raw, i.width) } + } else { + bitsLE(i.raw.raw.raw, i.width) + } + }.reverse.each + resume(()) + } +} \ No newline at end of file diff --git a/libraries/common/effekt.effekt b/libraries/common/effekt.effekt index 0fe82a777..f6db00052 100644 --- a/libraries/common/effekt.effekt +++ b/libraries/common/effekt.effekt @@ -90,7 +90,7 @@ extern pure def show(value: Char): String = extern pure def show(value: Byte): String = js "'' + ${value}" - chez "(string ${value})" + chez "(show-number ${value})" llvm """ %z = call %Pos @c_bytearray_show_Byte(i8 ${value}) ret %Pos %z @@ -234,6 +234,10 @@ extern pure def infixNeq(x: Int, y: Int): Bool = """ vm "effekt::infixNeq(Int, Int)" +def infixEq(x: Byte, y: Byte): Bool = { + x.toInt == y.toInt +} + extern pure def infixEq(x: Char, y: Char): Bool = js "${x} === ${y}" chez "(equal? ${x} ${y})" @@ -419,6 +423,22 @@ def pow(base: Double, exponent: Int): Double = { } } +/// Computes n^exp for integers. +/// +/// Precondition: exp >= 0 +def pow(n: Int, exp: Int): Int = { + def go(n: Int, exp: Int, acc: Int): Int = { + if (exp == 0) { + acc + } else if (mod(exp, 2) == 0) { + go(n * n, exp / 2, acc) + } else { + go(n * n, exp / 2, acc * n) + } + } + go(n, exp, 1) +} + extern pure def pow(base: Double, exponent: Double): Double = js "Math.pow(${base}, ${exponent})" chez "(expt ${base} ${exponent})" @@ -649,6 +669,12 @@ extern pure def bitwiseXor(x: Int, y: Int): Int = llvm "%z = xor %Int ${x}, ${y} ret %Int %z" vm "effekt::bitwiseXor(Int, Int)" +extern pure def bitwiseNot(x: Int): Int = + js "(~${x})" + chez "(lognot ${x})" + llvm "%z = xor %Int ${x}, -1 ret %Int %z" + vm "effekt::bitwiseNot(Int)" + // Byte operations // =============== diff --git a/libraries/common/stream.effekt b/libraries/common/stream.effekt index 4f3f1382a..c1d5c8560 100644 --- a/libraries/common/stream.effekt +++ b/libraries/common/stream.effekt @@ -504,6 +504,61 @@ def tee[A]{ cons1: { => Unit / emit[A] } => Unit }{ cons2: { => Unit / emit[A] } } } +// Counting and padding +// -------------------- + +/// get current position in stream +effect getPos(): Int + +/// Track position in stream, starting with init +/// Handles getPos +def tracking[A](init: Int){ body: => Unit / { emit[A], getPos } }: Unit / emit[A] = { + var n = init + try body() + with emit[A] { b => n = n + 1; resume(do emit[A](b)) } + with getPos{ resume(n) } +} + +/// Pad to a multiple of fac emitted values, by emitting gen repeatedly +def pad[A](fac: Int){ gen: => A }: Unit / {getPos, emit[A]} = { + while(mod(do getPos(), fac) != 0) { + do emit[A](gen()) + } +} + +/// Track how many bytes were emitted (in the body) +/// Handles getPos +def tracking[A]{ body: => Unit / { emit[A], getPos } }: Unit / emit[A] = + tracking[A](0){body} + +/// Return just the nth element of the given stream. +/// Raises MissingValue if not enough elements are emitted +def nth[A](n: Int){ body: => Unit / emit[A] }: A / Exception[MissingValue] = { + var m = n + try { + body() + val r: A = do raise[MissingValue](MissingValue(), "code in first did not emit any values") + r + } with emit[A] { a => + if (m == 0) { + a + } else { + m = m - 1 + resume(()) + } + } +} + +/// Return just the first element of the given stream. +/// Raises MissingValue if no elements are emitted +def first[A]{ body: => Unit / emit[A] }: A / Exception[MissingValue] = { + try { + body() + val r: A = do raise[MissingValue](MissingValue(), "code in first did not emit any values") + r + } with emit[A] { a => a } +} + namespace returning { /// Canonical handler of push streams that performs `action` for every diff --git a/libraries/common/test.effekt b/libraries/common/test.effekt index d5cafc894..a393b296c 100644 --- a/libraries/common/test.effekt +++ b/libraries/common/test.effekt @@ -33,6 +33,9 @@ def assert(obtained: String, expected: String, msg: String): Unit / Assertion = def assert(obtained: Int, expected: Int): Unit / { Assertion, Formatted } = assertEqual(obtained, expected) { (x, y) => x == y } { x => show(x) } +def assert(obtained: Byte, expected: Byte): Unit / { Assertion, Formatted } = + assertEqual(obtained, expected) { (x, y) => x == y } { x => show(x) } + def assert(obtained: Bool, expected: Bool): Unit / { Assertion, Formatted } = assertEqual(obtained, expected) { (x, y) => x == y } { x => show(x) } @@ -56,6 +59,18 @@ def assertEqual[A](obtained: A, expected: A) { equals: (A, A) => Bool } { show: // NOTE: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // Here's an accidental capture! Can we prevent this somehow nicely? +def assertNotThrown[E](ex: on[E]){ body: => Unit / Exception[E] }: Unit / Assertion = { + ex.default{ do assert(false, "Unexpected Exception") }{body} +} +def assertNoThrow[E]{ body: => Unit / Exception[E] }: Unit / Assertion = { + on[E].default{ do assert(false, "Unexpected Exception") }{body} +} +def assertThrown[E](ex: on[E]){ body: => Unit / Exception[E] }: Unit / Assertion = { + do assert(ex.default{ true }{ body(); false }, "Expected Exception, but exited normally") +} +def assertThrows[E]{body: => Unit / Exception[E] }: Unit / Assertion = { + do assert(on[E].default{ true }{ body(); false }, "Expected Exception, but exited normally") +} interface Test { def success(name: String, duration: Int): Unit