diff --git a/.github/workflows/acme.yml b/.github/workflows/acme.yml new file mode 100644 index 000000000..ff3286f44 --- /dev/null +++ b/.github/workflows/acme.yml @@ -0,0 +1,52 @@ +name: Check every stdlib module is compiled (acme) + +on: + pull_request: + paths: + - 'libraries/common/**' + +jobs: + check-stdlib-sync: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Generate acme and test for differences + id: generate-acme + shell: bash + run: | + # Function to convert file path to module import path + path_to_module() { + local filepath="$1" + # Remove libraries/common/ prefix and .effekt suffix + local module_path="${filepath#libraries/common/}" + module_path="${module_path%.effekt}" + echo "$module_path" + } + + # Find all .effekt files in libraries/common, excluding acme.effekt + MODULES=$(find libraries/common -type f -name "*.effekt" | sort | while read -r file; do + path_to_module "$file" + done) + + # Create the new acme.effekt content + { + echo "/// This module is auto-generated and checked in CI." + echo "/// It imports **every** stdlib module: ACME = All Common Modules in Effekt" + echo "module acme" + echo "" + for module in $MODULES; do + echo "import $module" + done + echo "" + echo "def main() = ()" + } > generated-acme.effekt + + # Compare files, ignoring whitespace, blank lines, and line ending differences + if ! diff -Bbq examples/stdlib/acme.effekt generated-acme.effekt; then + echo "::error::The stdlib import file (examples/stdlib/acme.effekt) is out of sync with the modules in libraries/common." + echo "Differences found:" + diff -Bu examples/stdlib/acme.effekt generated-acme.effekt + exit 1 + fi diff --git a/effekt/jvm/src/test/scala/effekt/StdlibTests.scala b/effekt/jvm/src/test/scala/effekt/StdlibTests.scala index 44fbe0be8..db36875d2 100644 --- a/effekt/jvm/src/test/scala/effekt/StdlibTests.scala +++ b/effekt/jvm/src/test/scala/effekt/StdlibTests.scala @@ -49,5 +49,9 @@ class StdlibLLVMTests extends StdlibTests { // String comparison using `<`, `<=`, `>`, `>=` is not implemented yet on LLVM examplesDir / "stdlib" / "string" / "compare.effekt", + + // Wrong codegen for negative types, see #801 + examplesDir / "stdlib" / "json.effekt", + examplesDir / "stdlib" / "buffer.effekt", ) } diff --git a/examples/stdlib/acme.check b/examples/stdlib/acme.check new file mode 100644 index 000000000..e69de29bb diff --git a/examples/stdlib/acme.effekt b/examples/stdlib/acme.effekt new file mode 100644 index 000000000..b8e209be7 --- /dev/null +++ b/examples/stdlib/acme.effekt @@ -0,0 +1,37 @@ +/// This module is auto-generated and checked in CI. +/// It imports **every** stdlib module: ACME = All Common Modules in Effekt +module acme + +import args +import array +import bench +import buffer +import bytearray +import char +import dequeue +import effekt +import exception +import io +import io/console +import io/error +import io/filesystem +import io/network +import io/time +import json +import list +import map +import option +import process +import queue +import ref +import regex +import result +import scanner +import seq +import set +import stream +import string +import test +import tty + +def main() = () diff --git a/examples/stdlib/buffer.check b/examples/stdlib/buffer.check new file mode 100644 index 000000000..5320d584f --- /dev/null +++ b/examples/stdlib/buffer.check @@ -0,0 +1,8 @@ +1 +false +Some(17) +Some(1) +Some(2) +Some(3) +Some(4) +Some(5) \ No newline at end of file diff --git a/examples/stdlib/buffer.effekt b/examples/stdlib/buffer.effekt new file mode 100644 index 000000000..4d87fdee9 --- /dev/null +++ b/examples/stdlib/buffer.effekt @@ -0,0 +1,3 @@ +import buffer + +def main() = buffer::examples::main() \ No newline at end of file diff --git a/examples/stdlib/json.check b/examples/stdlib/json.check new file mode 100644 index 000000000..186686a7f --- /dev/null +++ b/examples/stdlib/json.check @@ -0,0 +1,65 @@ + a + +a + +a +b +f + +{ +"x" +: +"Hallo" +, +"y" +: +null +, +"z" +: +12.3783 +} + +{ +"a" +: +null +, +"b" +: +[ +true +, +false +, +false +, +true +] +, +"f" +: +12.532 +} + +{ +"a" +: +null +, +"b" +: +[ +true +, +false +, +false +, +true +] +, +"f" +: +12.532 +} diff --git a/examples/stdlib/json.effekt b/examples/stdlib/json.effekt new file mode 100644 index 000000000..7f6438d9c --- /dev/null +++ b/examples/stdlib/json.effekt @@ -0,0 +1,3 @@ +import json + +def main() = json::test::main() \ No newline at end of file diff --git a/libraries/common/buffer.effekt b/libraries/common/buffer.effekt index e958897ab..2c9abab28 100644 --- a/libraries/common/buffer.effekt +++ b/libraries/common/buffer.effekt @@ -34,7 +34,7 @@ def emptyBuffer[T](capacity: Int): Buffer[T] at {global} = { } def arrayBuffer[T](initialCapacity: Int): Buffer[T] at {global} = { // TODO allocate buffer (and array) into a region r. - val contents = emptyArray[T](initialCapacity) + val contents = array::allocate[T](initialCapacity) var head in global = 0 var tail in global = 0 @@ -51,7 +51,7 @@ def arrayBuffer[T](initialCapacity: Int): Buffer[T] at {global} = { def read() = { if (buffer.empty?) None() else { - val result: T = contents.remove(head).getOrElse { <> }; + val result: T = contents.unsafeGet(head); head = mod(head + 1, initialCapacity) Some(result) } @@ -59,7 +59,7 @@ def arrayBuffer[T](initialCapacity: Int): Buffer[T] at {global} = { def write(el: T) = { if (buffer.full?) <> // raise(BufferOverflow()) - contents.put(tail, el) + contents.unsafeSet(tail, el) tail = mod(tail + 1, initialCapacity) } } diff --git a/libraries/common/io/network.effekt b/libraries/common/io/network.effekt index 4adcfed53..98baa9685 100644 --- a/libraries/common/io/network.effekt +++ b/libraries/common/io/network.effekt @@ -57,14 +57,17 @@ def server(host: String, port: Int, handler: () => Unit / Socket at {io, async, namespace examples { def helloWorldApp(): Unit / Socket = { - val request = do receive(); + val request = do receive().toString; - println("Received a request: " ++ request.toUTF8) + println("Received a request: " ++ request) - if (request.toUTF8.startsWith("GET /")) { - do send(fromUTF8("HTTP/1.1 200 OK\r\n\r\nHello from Effekt!")) + def respond(s: String): Unit / Socket = + do send(s.fromString) + + if (request.startsWith("GET /")) { + respond("HTTP/1.1 200 OK\r\n\r\nHello from Effekt!") } else { - do send("HTTP/1.1 400 Bad Request\r\n\r\n".fromUTF8) + respond("HTTP/1.1 400 Bad Request\r\n\r\n") } do end() } diff --git a/libraries/common/json.effekt b/libraries/common/json.effekt index eeb75ae5d..eb1bd096e 100644 --- a/libraries/common/json.effekt +++ b/libraries/common/json.effekt @@ -62,7 +62,7 @@ def encodeJsonObject[R]{ body: => R / JsonObjectBuilder }: R / emit[String] = { do emit("{") def c(k: String) = { if (not(first)) { do emit(",") } - do emit(escape(k)); do emit(":") + do emit(escape(k)); do emit(":") first = false } val r = encodeJson { @@ -76,7 +76,7 @@ def encodeJsonObject[R]{ body: => R / JsonObjectBuilder }: R / emit[String] = { /// Main entry point for encoding json. /// Emits individual tokens of the resulting json. def encodeJson[R]{ body: => R / JsonBuilder }: R / emit[String] = { - try body() with JsonBuilder { + try body() with JsonBuilder { def null() = { resume(do emit("null")) } def bool(v) = { resume(do emit( if(v){ "true" } else { "false" } )) } def number(n) = { resume(do emit(show(n))) } @@ -112,7 +112,7 @@ def readDouble(): Double / Scan[Char] = { if (optionally { readIf('.') }) { var b = 0.1 var r = pre.toDouble - while (optionally[Int] { readDigit() } is Some(d)) { + while (returning::optionally[Int] { readDigit() } is Some(d)) { r = r + b * d.toDouble b = b * 0.1 } @@ -126,14 +126,14 @@ def readDouble(): Double / Scan[Char] = { def expectString(string: String): Unit / { Scan[Char], Exception[WrongFormat] } = for[Char] { string.each } { char => - expect[Unit]("Expected " ++ string) { readIf(char) } + expect("Expected " ++ string) { readIf(char) } } /// Read and unescape a string in "" def readQuotedString(): Unit / { Scan[Char], emit[Char], Exception[WrongFormat] } = { try { skipWhitespace() - expect[Unit]("Expected \"") { readIf('"') } + expect("Expected \"") { readIf('"') } while(read[Char]() is c and c != '"') { c match { case '\\' => read[Char]() match { @@ -191,7 +191,7 @@ def decodeJsonObject(): Unit / {Scan[Char], JsonObjectBuilder, Exception[WrongFo } do skip[Char]() } -def decodeJsonList(): Unit / {Scan[Char], JsonBuilder, Exception[WrongFormat]} = { +def decodeJsonList(): Unit / {Scan[Char], JsonBuilder, Exception[WrongFormat]} = { var first = true expectString("[") with boundary @@ -278,7 +278,7 @@ def build[R](){ body: => R / JsonBuilder }: (R, JsonValue) = { } (x, r) } -def buildList[R](){ body: => R / JsonBuilder }: (R, List[JsonValue]) = collectList[JsonValue, R] { +def buildList[R](){ body: => R / JsonBuilder }: (R, List[JsonValue]) = returning::collectList[JsonValue, R] { try body() with JsonBuilder { def number(n) = { do emit(Number(n)); resume(()) } def bool(b) = { do emit(Bool(b)); resume(()) } @@ -296,7 +296,7 @@ def buildList[R](){ body: => R / JsonBuilder }: (R, List[JsonValue]) = collectLi } } } -def buildDict[R](){ body: => R / JsonObjectBuilder }: (R, List[(String, JsonValue)]) = collectList[(String, JsonValue), R] { +def buildDict[R](){ body: => R / JsonObjectBuilder }: (R, List[(String, JsonValue)]) = returning::collectList[(String, JsonValue), R] { try body() with JsonObjectBuilder { def field(k) = resume { {v} => val x = build{v} @@ -315,10 +315,12 @@ namespace test { // Read quoted string feed("\"\ta\n\ra\"") { - with scanner[Char, Unit] + with scanner[Char] println(collectString { readQuotedString() }) } + println("") + // Parse example feed("""{ "a": null, "b": [true,false,false,true], "f": 12.532 }"""){ with scanner[Char] @@ -336,8 +338,10 @@ namespace test { } handleJsonBuilder{d}{ decodeJson() } } - try{ - + + println("") + + try { // Encode example encodeJson { do dict{ @@ -352,13 +356,15 @@ namespace test { } } } - + + println("") + // format with intermediate value - val j = feed("""{ - "a": null, - "b": [true, false, false, true], "f": 12.532 + val j = feed[JsonValue]("""{ + "a": null, + "b": [true, false, false, true], "f": 12.532 } """){ - with scanner[Char, JsonValue] + with returning::scanner[Char, JsonValue] build { decodeJson() }.second @@ -366,18 +372,20 @@ namespace test { encodeJson{ unbuild(j) } - + + println("") + // format (minify) example encodeJson{ - feed("""{ - "a": null, - "b": [true, false, false, true], "f": 12.532 + feed("""{ + "a": null, + "b": [true, false, false, true], "f": 12.532 } """){ - with scanner[Char, Unit] + with scanner[Char] decodeJson() } } - + } with emit[String] { e => resume(println(e)) }