release: ship a private static runtime
f880f22 parent: ee111f4 added
.github/workflows/release.yml +105 -0 | new file mode 100644 | ||
| @@ -0,0 +1,105 @@ | ||
| 1 | +name: Release | |
| 2 | + | |
| 3 | +on: | |
| 4 | + push: | |
| 5 | + tags: | |
| 6 | + - "v*" | |
| 7 | + workflow_dispatch: | |
| 8 | + inputs: | |
| 9 | + tag: | |
| 10 | + description: Existing version tag to publish | |
| 11 | + required: true | |
| 12 | + type: string | |
| 13 | + | |
| 14 | +permissions: | |
| 15 | + contents: write | |
| 16 | + | |
| 17 | +concurrency: | |
| 18 | + group: release-${{ github.ref }} | |
| 19 | + cancel-in-progress: false | |
| 20 | + | |
| 21 | +jobs: | |
| 22 | + release: | |
| 23 | + name: Build and publish Linux x86_64 | |
| 24 | + runs-on: ubuntu-24.04 | |
| 25 | + | |
| 26 | + steps: | |
| 27 | + - name: Resolve release tag | |
| 28 | + id: release | |
| 29 | + env: | |
| 30 | + EVENT_NAME: ${{ github.event_name }} | |
| 31 | + INPUT_TAG: ${{ inputs.tag }} | |
| 32 | + REF_NAME: ${{ github.ref_name }} | |
| 33 | + run: | | |
| 34 | + if [[ "$EVENT_NAME" == "workflow_dispatch" ]]; then | |
| 35 | + tag="$INPUT_TAG" | |
| 36 | + else | |
| 37 | + tag="$REF_NAME" | |
| 38 | + fi | |
| 39 | + if [[ ! "$tag" =~ ^v[0-9] ]]; then | |
| 40 | + echo "Release tags must start with a version such as v0.1.0" >&2 | |
| 41 | + exit 1 | |
| 42 | + fi | |
| 43 | + echo "tag=$tag" >> "$GITHUB_OUTPUT" | |
| 44 | + | |
| 45 | + - name: Check out release tag | |
| 46 | + uses: actions/checkout@v4 | |
| 47 | + with: | |
| 48 | + ref: ${{ steps.release.outputs.tag }} | |
| 49 | + | |
| 50 | + - name: Install Nim | |
| 51 | + uses: jiro4989/setup-nim-action@v2 | |
| 52 | + with: | |
| 53 | + nim-version: "2.2.12" | |
| 54 | + repo-token: ${{ secrets.GITHUB_TOKEN }} | |
| 55 | + | |
| 56 | + - name: Run test suite | |
| 57 | + run: ./run-tests.sh | |
| 58 | + | |
| 59 | + - name: Build optimized compiler | |
| 60 | + run: | | |
| 61 | + mkdir -p lib | |
| 62 | + nim c --app:staticlib --nimMainPrefix:ClonimRuntime \ | |
| 63 | + --hints:off --warnings:off --nimcache:lib/nimcache-debug \ | |
| 64 | + -o:lib/libclonim_runtime_debug.a src/runtime_lib.nim | |
| 65 | + nim c -d:release --app:staticlib --nimMainPrefix:ClonimRuntime \ | |
| 66 | + --hints:off --warnings:off --nimcache:lib/nimcache-release \ | |
| 67 | + -o:lib/libclonim_runtime.a src/runtime_lib.nim | |
| 68 | + nim c -d:release --hints:off --warnings:off \ | |
| 69 | + -o:bin/clonim src/clonim.nim | |
| 70 | + | |
| 71 | + - name: Package release | |
| 72 | + env: | |
| 73 | + ARCHIVE_ROOT: clonim-linux-x86_64 | |
| 74 | + run: | | |
| 75 | + install -Dm755 bin/clonim "dist/$ARCHIVE_ROOT/bin/clonim" | |
| 76 | + install -Dm644 lib/libclonim_runtime.a \ | |
| 77 | + "dist/$ARCHIVE_ROOT/lib/libclonim_runtime.a" | |
| 78 | + install -Dm644 lib/libclonim_runtime_debug.a \ | |
| 79 | + "dist/$ARCHIVE_ROOT/lib/libclonim_runtime_debug.a" | |
| 80 | + install -Dm644 src/app_runtime.nim \ | |
| 81 | + "dist/$ARCHIVE_ROOT/src/app_runtime.nim" | |
| 82 | + install -Dm644 src/runtime_types.nim \ | |
| 83 | + "dist/$ARCHIVE_ROOT/src/runtime_types.nim" | |
| 84 | + cp -R stdlib "dist/$ARCHIVE_ROOT/stdlib" | |
| 85 | + cp README.md "dist/$ARCHIVE_ROOT/README.md" | |
| 86 | + tar -C dist -czf "dist/$ARCHIVE_ROOT.tar.gz" "$ARCHIVE_ROOT" | |
| 87 | + cd dist | |
| 88 | + sha256sum "$ARCHIVE_ROOT.tar.gz" > "$ARCHIVE_ROOT.tar.gz.sha256" | |
| 89 | + | |
| 90 | + - name: Publish GitHub release | |
| 91 | + env: | |
| 92 | + GH_TOKEN: ${{ github.token }} | |
| 93 | + TAG: ${{ steps.release.outputs.tag }} | |
| 94 | + run: | | |
| 95 | + assets=( | |
| 96 | + dist/clonim-linux-x86_64.tar.gz | |
| 97 | + dist/clonim-linux-x86_64.tar.gz.sha256 | |
| 98 | + ) | |
| 99 | + if gh release view "$TAG" >/dev/null 2>&1; then | |
| 100 | + gh release upload "$TAG" "${assets[@]}" --clobber | |
| 101 | + else | |
| 102 | + gh release create "$TAG" "${assets[@]}" \ | |
| 103 | + --verify-tag \ | |
| 104 | + --generate-notes \ | |
| 105 | + --title "$TAG" | |
| new file mode 100644 | |||
| @@ -0,0 +1,105 @@ | |||
| 1 | +name: Release | ||
| 2 | + | ||
| 3 | +on: | ||
| 4 | + push: | ||
| 5 | + tags: | ||
| 6 | + - "v*" | ||
| 7 | + workflow_dispatch: | ||
| 8 | + inputs: | ||
| 9 | + tag: | ||
| 10 | + description: Existing version tag to publish | ||
| 11 | + required: true | ||
| 12 | + type: string | ||
| 13 | + | ||
| 14 | +permissions: | ||
| 15 | + contents: write | ||
| 16 | + | ||
| 17 | +concurrency: | ||
| 18 | + group: release-${{ github.ref }} | ||
| 19 | + cancel-in-progress: false | ||
| 20 | + | ||
| 21 | +jobs: | ||
| 22 | + release: | ||
| 23 | + name: Build and publish Linux x86_64 | ||
| 24 | + runs-on: ubuntu-24.04 | ||
| 25 | + | ||
| 26 | + steps: | ||
| 27 | + - name: Resolve release tag | ||
| 28 | + id: release | ||
| 29 | + env: | ||
| 30 | + EVENT_NAME: ${{ github.event_name }} | ||
| 31 | + INPUT_TAG: ${{ inputs.tag }} | ||
| 32 | + REF_NAME: ${{ github.ref_name }} | ||
| 33 | + run: | | ||
| 34 | + if [[ "$EVENT_NAME" == "workflow_dispatch" ]]; then | ||
| 35 | + tag="$INPUT_TAG" | ||
| 36 | + else | ||
| 37 | + tag="$REF_NAME" | ||
| 38 | + fi | ||
| 39 | + if [[ ! "$tag" =~ ^v[0-9] ]]; then | ||
| 40 | + echo "Release tags must start with a version such as v0.1.0" >&2 | ||
| 41 | + exit 1 | ||
| 42 | + fi | ||
| 43 | + echo "tag=$tag" >> "$GITHUB_OUTPUT" | ||
| 44 | + | ||
| 45 | + - name: Check out release tag | ||
| 46 | + uses: actions/checkout@v4 | ||
| 47 | + with: | ||
| 48 | + ref: ${{ steps.release.outputs.tag }} | ||
| 49 | + | ||
| 50 | + - name: Install Nim | ||
| 51 | + uses: jiro4989/setup-nim-action@v2 | ||
| 52 | + with: | ||
| 53 | + nim-version: "2.2.12" | ||
| 54 | + repo-token: ${{ secrets.GITHUB_TOKEN }} | ||
| 55 | + | ||
| 56 | + - name: Run test suite | ||
| 57 | + run: ./run-tests.sh | ||
| 58 | + | ||
| 59 | + - name: Build optimized compiler | ||
| 60 | + run: | | ||
| 61 | + mkdir -p lib | ||
| 62 | + nim c --app:staticlib --nimMainPrefix:ClonimRuntime \ | ||
| 63 | + --hints:off --warnings:off --nimcache:lib/nimcache-debug \ | ||
| 64 | + -o:lib/libclonim_runtime_debug.a src/runtime_lib.nim | ||
| 65 | + nim c -d:release --app:staticlib --nimMainPrefix:ClonimRuntime \ | ||
| 66 | + --hints:off --warnings:off --nimcache:lib/nimcache-release \ | ||
| 67 | + -o:lib/libclonim_runtime.a src/runtime_lib.nim | ||
| 68 | + nim c -d:release --hints:off --warnings:off \ | ||
| 69 | + -o:bin/clonim src/clonim.nim | ||
| 70 | + | ||
| 71 | + - name: Package release | ||
| 72 | + env: | ||
| 73 | + ARCHIVE_ROOT: clonim-linux-x86_64 | ||
| 74 | + run: | | ||
| 75 | + install -Dm755 bin/clonim "dist/$ARCHIVE_ROOT/bin/clonim" | ||
| 76 | + install -Dm644 lib/libclonim_runtime.a \ | ||
| 77 | + "dist/$ARCHIVE_ROOT/lib/libclonim_runtime.a" | ||
| 78 | + install -Dm644 lib/libclonim_runtime_debug.a \ | ||
| 79 | + "dist/$ARCHIVE_ROOT/lib/libclonim_runtime_debug.a" | ||
| 80 | + install -Dm644 src/app_runtime.nim \ | ||
| 81 | + "dist/$ARCHIVE_ROOT/src/app_runtime.nim" | ||
| 82 | + install -Dm644 src/runtime_types.nim \ | ||
| 83 | + "dist/$ARCHIVE_ROOT/src/runtime_types.nim" | ||
| 84 | + cp -R stdlib "dist/$ARCHIVE_ROOT/stdlib" | ||
| 85 | + cp README.md "dist/$ARCHIVE_ROOT/README.md" | ||
| 86 | + tar -C dist -czf "dist/$ARCHIVE_ROOT.tar.gz" "$ARCHIVE_ROOT" | ||
| 87 | + cd dist | ||
| 88 | + sha256sum "$ARCHIVE_ROOT.tar.gz" > "$ARCHIVE_ROOT.tar.gz.sha256" | ||
| 89 | + | ||
| 90 | + - name: Publish GitHub release | ||
| 91 | + env: | ||
| 92 | + GH_TOKEN: ${{ github.token }} | ||
| 93 | + TAG: ${{ steps.release.outputs.tag }} | ||
| 94 | + run: | | ||
| 95 | + assets=( | ||
| 96 | + dist/clonim-linux-x86_64.tar.gz | ||
| 97 | + dist/clonim-linux-x86_64.tar.gz.sha256 | ||
| 98 | + ) | ||
| 99 | + if gh release view "$TAG" >/dev/null 2>&1; then | ||
| 100 | + gh release upload "$TAG" "${assets[@]}" --clobber | ||
| 101 | + else | ||
| 102 | + gh release create "$TAG" "${assets[@]}" \ | ||
| 103 | + --verify-tag \ | ||
| 104 | + --generate-notes \ | ||
| 105 | + --title "$TAG" | ||
modified
.gitignore +1 -0 | @@ -1,4 +1,5 @@ | ||
| 1 | 1 | bin/ |
| 2 | +lib/ | |
| 2 | 3 | nimcache/ |
| 3 | 4 | .clj-kondo/.cache/ |
| 4 | 5 | .lsp/ |
| @@ -1,4 +1,5 @@ | |||
| 1 | bin/ | 1 | bin/ |
| 2 | +lib/ | ||
| 2 | nimcache/ | 3 | nimcache/ |
| 3 | .clj-kondo/.cache/ | 4 | .clj-kondo/.cache/ |
| 4 | .lsp/ | 5 | .lsp/ |
modified
README.md +9 -1 | @@ -23,6 +23,14 @@ and flags slowdowns above 10% versus the latest compatible run. Requires Jolt | ||
| 23 | 23 | and a clean working tree. Use `just bench-store 15` to change the threshold; |
| 24 | 24 | see [benchmark history](bench/README.md) for metrics and exit codes. |
| 25 | 25 | |
| 26 | +`just release` also builds clonim's private static runtime archives. Generated | |
| 27 | +programs link the matching debug or release archive, so their finished binaries | |
| 28 | +are standalone. Version tags matching `v*` trigger the release workflow, which | |
| 29 | +runs the test suite and publishes a Linux x86_64 bundle plus its SHA-256 file to | |
| 30 | +GitHub Releases. The bundle contains the compiler, both internal runtime | |
| 31 | +archives, their small Nim interface, and the clonim source-level standard | |
| 32 | +library; users still need Nim installed as the native-code backend. | |
| 33 | + | |
| 26 | 34 | Source-level libraries are loaded explicitly. Use |
| 27 | 35 | `(require '[clonim.core :refer [now-ms]])` to load `stdlib/clonim/core.clj`. Host-dependent operations remain small runtime primitives; for |
| 28 | 36 | example, the stdlib `now-ms` function wraps the `*epoch-time-ms*` primitive. |
| @@ -62,7 +70,7 @@ Missing dependencies, missing vars, and dependency cycles produce errors. | ||
| 62 | 70 | |---|---|---| |
| 63 | 71 | | reader | `src/reader.nim` | text → data. Forms *are* runtime values (homoiconic), as in Clojure | |
| 64 | 72 | | analyzer + codegen | `src/compiler.nim` | expands the macro set to core special forms, emits Nim statements | |
| 65 | -| runtime | `src/runtime.nim` | the `Value` tagged union, the persistent vector/map, equality, printing, var cells, `call` | | |
| 73 | +| runtime | `src/runtime.nim` | the `Value` tagged union, the persistent vector/map, equality, printing, var cells, `call`; precompiled into a private static archive | | |
| 66 | 74 | | core | `src/core.nim` | ~140 `clojure.core` builtins as Nim closures | |
| 67 | 75 | | stdlib | `stdlib/clonim/core.clj` | source-level helpers loaded by explicit require | |
| 68 | 76 | | driver | `src/clonim.nim` | shells out to `nim c`, times each phase | |
| @@ -23,6 +23,14 @@ and flags slowdowns above 10% versus the latest compatible run. Requires Jolt | |||
| 23 | and a clean working tree. Use `just bench-store 15` to change the threshold; | 23 | and a clean working tree. Use `just bench-store 15` to change the threshold; |
| 24 | see [benchmark history](bench/README.md) for metrics and exit codes. | 24 | see [benchmark history](bench/README.md) for metrics and exit codes. |
| 25 | 25 | ||
| 26 | +`just release` also builds clonim's private static runtime archives. Generated | ||
| 27 | +programs link the matching debug or release archive, so their finished binaries | ||
| 28 | +are standalone. Version tags matching `v*` trigger the release workflow, which | ||
| 29 | +runs the test suite and publishes a Linux x86_64 bundle plus its SHA-256 file to | ||
| 30 | +GitHub Releases. The bundle contains the compiler, both internal runtime | ||
| 31 | +archives, their small Nim interface, and the clonim source-level standard | ||
| 32 | +library; users still need Nim installed as the native-code backend. | ||
| 33 | + | ||
| 26 | Source-level libraries are loaded explicitly. Use | 34 | Source-level libraries are loaded explicitly. Use |
| 27 | `(require '[clonim.core :refer [now-ms]])` to load `stdlib/clonim/core.clj`. Host-dependent operations remain small runtime primitives; for | 35 | `(require '[clonim.core :refer [now-ms]])` to load `stdlib/clonim/core.clj`. Host-dependent operations remain small runtime primitives; for |
| 28 | example, the stdlib `now-ms` function wraps the `*epoch-time-ms*` primitive. | 36 | example, the stdlib `now-ms` function wraps the `*epoch-time-ms*` primitive. |
| @@ -62,7 +70,7 @@ Missing dependencies, missing vars, and dependency cycles produce errors. | |||
| 62 | |---|---|---| | 70 | |---|---|---| |
| 63 | | reader | `src/reader.nim` | text → data. Forms *are* runtime values (homoiconic), as in Clojure | | 71 | | reader | `src/reader.nim` | text → data. Forms *are* runtime values (homoiconic), as in Clojure | |
| 64 | | analyzer + codegen | `src/compiler.nim` | expands the macro set to core special forms, emits Nim statements | | 72 | | analyzer + codegen | `src/compiler.nim` | expands the macro set to core special forms, emits Nim statements | |
| 65 | -| runtime | `src/runtime.nim` | the `Value` tagged union, the persistent vector/map, equality, printing, var cells, `call` | | 73 | +| runtime | `src/runtime.nim` | the `Value` tagged union, the persistent vector/map, equality, printing, var cells, `call`; precompiled into a private static archive | |
| 66 | | core | `src/core.nim` | ~140 `clojure.core` builtins as Nim closures | | 74 | | core | `src/core.nim` | ~140 `clojure.core` builtins as Nim closures | |
| 67 | | stdlib | `stdlib/clonim/core.clj` | source-level helpers loaded by explicit require | | 75 | | stdlib | `stdlib/clonim/core.clj` | source-level helpers loaded by explicit require | |
| 68 | | driver | `src/clonim.nim` | shells out to `nim c`, times each phase | | 76 | | driver | `src/clonim.nim` | shells out to `nim c`, times each phase | |
modified
justfile +7 -1 | @@ -6,8 +6,14 @@ bin := "bin/clonim" | ||
| 6 | 6 | build: |
| 7 | 7 | nim c --hints:off --warnings:off -o:{{bin}} src/clonim.nim |
| 8 | 8 | |
| 9 | +# Build the private static runtime in both modes used by the driver. | |
| 10 | +runtime-libs: | |
| 11 | + mkdir -p lib | |
| 12 | + nim c --app:staticlib --nimMainPrefix:ClonimRuntime --hints:off --warnings:off --nimcache:lib/nimcache-debug -o:lib/libclonim_runtime_debug.a src/runtime_lib.nim | |
| 13 | + nim c -d:release --app:staticlib --nimMainPrefix:ClonimRuntime --hints:off --warnings:off --nimcache:lib/nimcache-release -o:lib/libclonim_runtime.a src/runtime_lib.nim | |
| 14 | + | |
| 9 | 15 | # Build with optimisations on (compiler and, via -d:release, the programs it emits) |
| 10 | -release: | |
| 16 | +release: runtime-libs | |
| 11 | 17 | nim c -d:release --hints:off --warnings:off -o:{{bin}} src/clonim.nim |
| 12 | 18 | |
| 13 | 19 | # Run every example against tests/<name>.expected |
| @@ -6,8 +6,14 @@ bin := "bin/clonim" | |||
| 6 | build: | 6 | build: |
| 7 | nim c --hints:off --warnings:off -o:{{bin}} src/clonim.nim | 7 | nim c --hints:off --warnings:off -o:{{bin}} src/clonim.nim |
| 8 | 8 | ||
| 9 | +# Build the private static runtime in both modes used by the driver. | ||
| 10 | +runtime-libs: | ||
| 11 | + mkdir -p lib | ||
| 12 | + nim c --app:staticlib --nimMainPrefix:ClonimRuntime --hints:off --warnings:off --nimcache:lib/nimcache-debug -o:lib/libclonim_runtime_debug.a src/runtime_lib.nim | ||
| 13 | + nim c -d:release --app:staticlib --nimMainPrefix:ClonimRuntime --hints:off --warnings:off --nimcache:lib/nimcache-release -o:lib/libclonim_runtime.a src/runtime_lib.nim | ||
| 14 | + | ||
| 9 | # Build with optimisations on (compiler and, via -d:release, the programs it emits) | 15 | # Build with optimisations on (compiler and, via -d:release, the programs it emits) |
| 10 | -release: | 16 | +release: runtime-libs |
| 11 | nim c -d:release --hints:off --warnings:off -o:{{bin}} src/clonim.nim | 17 | nim c -d:release --hints:off --warnings:off -o:{{bin}} src/clonim.nim |
| 12 | 18 | ||
| 13 | # Run every example against tests/<name>.expected | 19 | # Run every example against tests/<name>.expected |
added
src/app_runtime.nim +58 -0 | new file mode 100644 | ||
| @@ -0,0 +1,58 @@ | ||
| 1 | +## Private interface to the precompiled clonim runtime. Generated programs | |
| 2 | +## import this module; implementations live in libclonim_runtime.a. | |
| 3 | +import runtime_types | |
| 4 | +export runtime_types | |
| 5 | + | |
| 6 | +proc mkFn*(name: string, | |
| 7 | + f: proc (args: openArray[Value]): Value {.closure.}): Value | |
| 8 | + {.cdecl, importc: "clonim_mk_fn".} | |
| 9 | + | |
| 10 | +{.push cdecl.} | |
| 11 | +proc initClonimRuntime*() {.importc: "ClonimRuntimeNimMain".} | |
| 12 | +proc registerCore*() {.importc: "clonim_register_core".} | |
| 13 | +proc registerNamespaceCore*() {.importc: "clonim_register_namespace_core".} | |
| 14 | + | |
| 15 | +proc err*(msg: string) {.noreturn, importc: "clonim_err".} | |
| 16 | +proc varCell*(name: string): VarCell {.importc: "clonim_var_cell".} | |
| 17 | +proc setVar*(name: string, v: Value): Value {.discardable, importc: "clonim_set_var".} | |
| 18 | +proc cellGet*(c: VarCell): Value {.importc: "clonim_cell_get".} | |
| 19 | +proc cellIs*(c: VarCell, v: Value): bool {.importc: "clonim_cell_is".} | |
| 20 | +proc getVar*(name: string): Value {.importc: "clonim_get_var".} | |
| 21 | +proc call*(f: Value, args: openArray[Value]): Value {.importc: "clonim_call".} | |
| 22 | +proc argAt*(args: openArray[Value], i: int): Value {.importc: "clonim_arg_at".} | |
| 23 | +proc restArgs*(args: openArray[Value], i: int): Value {.importc: "clonim_rest_args".} | |
| 24 | +proc seqDrop*(v: Value, n: int): Value {.importc: "clonim_seq_drop".} | |
| 25 | +proc toSeq*(v: Value): seq[Value] {.importc: "clonim_to_seq".} | |
| 26 | +proc truthy*(v: Value): bool {.importc: "clonim_truthy".} | |
| 27 | + | |
| 28 | +iterator elems*(v: Value): Value = | |
| 29 | + for x in toSeq(v): yield x | |
| 30 | + | |
| 31 | +proc mkInt*(x: int64): Value {.importc: "clonim_mk_int".} | |
| 32 | +proc mkFloat*(x: float64): Value {.importc: "clonim_mk_float".} | |
| 33 | +proc mkStr*(x: string): Value {.importc: "clonim_mk_str".} | |
| 34 | +proc mkKeyword*(x: string): Value {.importc: "clonim_mk_keyword".} | |
| 35 | +proc mkSymbol*(x: string): Value {.importc: "clonim_mk_symbol".} | |
| 36 | +proc mkList*(xs: openArray[Value]): Value {.importc: "clonim_mk_list".} | |
| 37 | +proc mkVector*(xs: openArray[Value]): Value {.importc: "clonim_mk_vector".} | |
| 38 | +proc mkSet*(xs: openArray[Value]): Value {.importc: "clonim_mk_set".} | |
| 39 | +proc mkMap*(ps: seq[(Value, Value)]): Value {.importc: "clonim_mk_map".} | |
| 40 | + | |
| 41 | +proc idiv*(a, b: int64): int64 {.importc: "clonim_idiv".} | |
| 42 | +proc irem*(a, b: int64): int64 {.importc: "clonim_irem".} | |
| 43 | +proc add2*(a, b: Value): Value {.importc: "clonim_add2".} | |
| 44 | +proc sub2*(a, b: Value): Value {.importc: "clonim_sub2".} | |
| 45 | +proc mul2*(a, b: Value): Value {.importc: "clonim_mul2".} | |
| 46 | +proc lt2*(a, b: Value): Value {.importc: "clonim_lt2".} | |
| 47 | +proc gt2*(a, b: Value): Value {.importc: "clonim_gt2".} | |
| 48 | +proc le2*(a, b: Value): Value {.importc: "clonim_le2".} | |
| 49 | +proc ge2*(a, b: Value): Value {.importc: "clonim_ge2".} | |
| 50 | +proc eq2*(a, b: Value): Value {.importc: "clonim_eq2".} | |
| 51 | +proc ne2*(a, b: Value): Value {.importc: "clonim_ne2".} | |
| 52 | +proc inc1*(a: Value): Value {.importc: "clonim_inc1".} | |
| 53 | +proc dec1*(a: Value): Value {.importc: "clonim_dec1".} | |
| 54 | +proc fusedReduce*(f, init: Value, hasInit: bool, base: Value, | |
| 55 | + ops: openArray[FusedOp]): Value {.importc: "clonim_fused_reduce".} | |
| 56 | +proc fusedCount*(base: Value, ops: openArray[FusedOp]): Value | |
| 57 | + {.importc: "clonim_fused_count".} | |
| 58 | +{.pop.} | |
| new file mode 100644 | |||
| @@ -0,0 +1,58 @@ | |||
| 1 | +## Private interface to the precompiled clonim runtime. Generated programs | ||
| 2 | +## import this module; implementations live in libclonim_runtime.a. | ||
| 3 | +import runtime_types | ||
| 4 | +export runtime_types | ||
| 5 | + | ||
| 6 | +proc mkFn*(name: string, | ||
| 7 | + f: proc (args: openArray[Value]): Value {.closure.}): Value | ||
| 8 | + {.cdecl, importc: "clonim_mk_fn".} | ||
| 9 | + | ||
| 10 | +{.push cdecl.} | ||
| 11 | +proc initClonimRuntime*() {.importc: "ClonimRuntimeNimMain".} | ||
| 12 | +proc registerCore*() {.importc: "clonim_register_core".} | ||
| 13 | +proc registerNamespaceCore*() {.importc: "clonim_register_namespace_core".} | ||
| 14 | + | ||
| 15 | +proc err*(msg: string) {.noreturn, importc: "clonim_err".} | ||
| 16 | +proc varCell*(name: string): VarCell {.importc: "clonim_var_cell".} | ||
| 17 | +proc setVar*(name: string, v: Value): Value {.discardable, importc: "clonim_set_var".} | ||
| 18 | +proc cellGet*(c: VarCell): Value {.importc: "clonim_cell_get".} | ||
| 19 | +proc cellIs*(c: VarCell, v: Value): bool {.importc: "clonim_cell_is".} | ||
| 20 | +proc getVar*(name: string): Value {.importc: "clonim_get_var".} | ||
| 21 | +proc call*(f: Value, args: openArray[Value]): Value {.importc: "clonim_call".} | ||
| 22 | +proc argAt*(args: openArray[Value], i: int): Value {.importc: "clonim_arg_at".} | ||
| 23 | +proc restArgs*(args: openArray[Value], i: int): Value {.importc: "clonim_rest_args".} | ||
| 24 | +proc seqDrop*(v: Value, n: int): Value {.importc: "clonim_seq_drop".} | ||
| 25 | +proc toSeq*(v: Value): seq[Value] {.importc: "clonim_to_seq".} | ||
| 26 | +proc truthy*(v: Value): bool {.importc: "clonim_truthy".} | ||
| 27 | + | ||
| 28 | +iterator elems*(v: Value): Value = | ||
| 29 | + for x in toSeq(v): yield x | ||
| 30 | + | ||
| 31 | +proc mkInt*(x: int64): Value {.importc: "clonim_mk_int".} | ||
| 32 | +proc mkFloat*(x: float64): Value {.importc: "clonim_mk_float".} | ||
| 33 | +proc mkStr*(x: string): Value {.importc: "clonim_mk_str".} | ||
| 34 | +proc mkKeyword*(x: string): Value {.importc: "clonim_mk_keyword".} | ||
| 35 | +proc mkSymbol*(x: string): Value {.importc: "clonim_mk_symbol".} | ||
| 36 | +proc mkList*(xs: openArray[Value]): Value {.importc: "clonim_mk_list".} | ||
| 37 | +proc mkVector*(xs: openArray[Value]): Value {.importc: "clonim_mk_vector".} | ||
| 38 | +proc mkSet*(xs: openArray[Value]): Value {.importc: "clonim_mk_set".} | ||
| 39 | +proc mkMap*(ps: seq[(Value, Value)]): Value {.importc: "clonim_mk_map".} | ||
| 40 | + | ||
| 41 | +proc idiv*(a, b: int64): int64 {.importc: "clonim_idiv".} | ||
| 42 | +proc irem*(a, b: int64): int64 {.importc: "clonim_irem".} | ||
| 43 | +proc add2*(a, b: Value): Value {.importc: "clonim_add2".} | ||
| 44 | +proc sub2*(a, b: Value): Value {.importc: "clonim_sub2".} | ||
| 45 | +proc mul2*(a, b: Value): Value {.importc: "clonim_mul2".} | ||
| 46 | +proc lt2*(a, b: Value): Value {.importc: "clonim_lt2".} | ||
| 47 | +proc gt2*(a, b: Value): Value {.importc: "clonim_gt2".} | ||
| 48 | +proc le2*(a, b: Value): Value {.importc: "clonim_le2".} | ||
| 49 | +proc ge2*(a, b: Value): Value {.importc: "clonim_ge2".} | ||
| 50 | +proc eq2*(a, b: Value): Value {.importc: "clonim_eq2".} | ||
| 51 | +proc ne2*(a, b: Value): Value {.importc: "clonim_ne2".} | ||
| 52 | +proc inc1*(a: Value): Value {.importc: "clonim_inc1".} | ||
| 53 | +proc dec1*(a: Value): Value {.importc: "clonim_dec1".} | ||
| 54 | +proc fusedReduce*(f, init: Value, hasInit: bool, base: Value, | ||
| 55 | + ops: openArray[FusedOp]): Value {.importc: "clonim_fused_reduce".} | ||
| 56 | +proc fusedCount*(base: Value, ops: openArray[FusedOp]): Value | ||
| 57 | + {.importc: "clonim_fused_count".} | ||
| 58 | +{.pop.} | ||
modified
src/clonim.nim +46 -9 | @@ -3,7 +3,7 @@ | ||
| 3 | 3 | ## clonim run foo.clj compile and run |
| 4 | 4 | ## clonim build foo.clj [-o bin] compile to a native binary |
| 5 | 5 | ## clonim emit foo.clj print the generated Nim |
| 6 | -import std/[hashes, os, osproc, strutils, times] | |
| 6 | +import std/[hashes, os, osproc, sequtils, strutils, times] | |
| 7 | 7 | import runtime, compiler |
| 8 | 8 | |
| 9 | 9 | proc usage() = |
| @@ -22,14 +22,43 @@ options: | ||
| 22 | 22 | quit(1) |
| 23 | 23 | |
| 24 | 24 | proc srcDir(): string = |
| 25 | - ## where runtime.nim / core.nim live, so generated code can import them | |
| 25 | + ## Where generated programs find the private runtime interface. | |
| 26 | 26 | for cand in [getAppDir(), getAppDir().parentDir / "src", |
| 27 | 27 | getAppDir().parentDir.parentDir / "src"]: |
| 28 | - if fileExists(cand / "runtime.nim"): return cand | |
| 28 | + if fileExists(cand / "app_runtime.nim"): return cand | |
| 29 | 29 | getAppDir() |
| 30 | 30 | |
| 31 | +proc runtimeLib(release: bool): string = | |
| 32 | + ## Release archives place the private runtime beside bin/. A source checkout | |
| 33 | + ## builds the same archive into lib/ on first use. | |
| 34 | + let libName = (if release: "libclonim_runtime.a" else: "libclonim_runtime_debug.a") | |
| 35 | + let installed = getAppDir().parentDir / "lib" / libName | |
| 36 | + let source = srcDir() / "runtime_lib.nim" | |
| 37 | + if fileExists(installed): | |
| 38 | + if not fileExists(source): return installed | |
| 39 | + var stale = false | |
| 40 | + for f in walkFiles(srcDir() / "*.nim"): | |
| 41 | + if getLastModificationTime(f) > getLastModificationTime(installed): | |
| 42 | + stale = true | |
| 43 | + break | |
| 44 | + if not stale: return installed | |
| 45 | + if not fileExists(source): | |
| 46 | + raise newException(IOError, "missing private runtime library: " & installed) | |
| 47 | + createDir(installed.parentDir) | |
| 48 | + var args = @["nim", "c", "--app:staticlib", "--nimMainPrefix:ClonimRuntime", | |
| 49 | + "--hints:off", "--warnings:off", "--path:" & srcDir(), | |
| 50 | + "--nimcache:" & installed.parentDir / | |
| 51 | + (if release: "nimcache-release" else: "nimcache-debug"), | |
| 52 | + "-o:" & installed] | |
| 53 | + if release: args.add "-d:release" | |
| 54 | + args.add source | |
| 55 | + let (output, code) = execCmdEx(args.mapIt(quoteShell(it)).join(" ")) | |
| 56 | + if code != 0: | |
| 57 | + raise newException(IOError, "failed to build private runtime library\n" & output) | |
| 58 | + installed | |
| 59 | + | |
| 31 | 60 | |
| 32 | -proc buildKey(nimSrc: string, release: bool): string = | |
| 61 | +proc buildKey(nimSrc: string, release: bool, rtLib: string): string = | |
| 33 | 62 | ## Identifies everything the produced binary depends on. Any change here |
| 34 | 63 | ## invalidates the cached binary for a source file. |
| 35 | 64 | var h: Hash = hash(nimSrc) !& hash(release) |
| @@ -39,6 +68,7 @@ proc buildKey(nimSrc: string, release: bool): string = | ||
| 39 | 68 | let nimExe = findExe("nim") |
| 40 | 69 | if nimExe.len > 0: |
| 41 | 70 | h = h !& hash($getLastModificationTime(nimExe)) |
| 71 | + h = h !& hash($getLastModificationTime(rtLib)) | |
| 42 | 72 | $(!$h) |
| 43 | 73 | |
| 44 | 74 | proc main() = |
| @@ -111,15 +141,22 @@ proc main() = | ||
| 111 | 141 | |
| 112 | 142 | # Even a warm nimcache costs a second or so of semantic checking and linking. |
| 113 | 143 | # `run` skips the backend entirely when nothing that feeds the binary has |
| 114 | - # changed: the generated Nim, the runtime/core sources it imports, the build | |
| 115 | - # flags, and the Nim compiler itself. | |
| 144 | + # changed: the generated Nim, private runtime archive/interface, build flags, | |
| 145 | + # and the Nim compiler itself. | |
| 116 | 146 | let stamp = work / "stamp" |
| 147 | + var rtLib = "" | |
| 148 | + try: | |
| 149 | + rtLib = runtimeLib(release) | |
| 150 | + except IOError, OSError: | |
| 151 | + stderr.writeLine("clonim: " & getCurrentExceptionMsg()) | |
| 152 | + quit(1) | |
| 117 | 153 | let cached = cmd == "run" and fileExists(outBin) and fileExists(stamp) and |
| 118 | - readFile(stamp) == buildKey(nimSrc, release) | |
| 154 | + readFile(stamp) == buildKey(nimSrc, release, rtLib) | |
| 119 | 155 | |
| 120 | 156 | var nimCmd = @["nim", "c", "--hints:off", "--warnings:off", |
| 121 | 157 | "--path:" & srcDir(), "--nimcache:" & (work / "cache"), |
| 122 | - "-o:" & outBin] | |
| 158 | + "--passL:-Wl,--allow-multiple-definition", "--passL:" & rtLib, | |
| 159 | + "--passL:-lm", "-o:" & outBin] | |
| 123 | 160 | if release: nimCmd.add "-d:release" |
| 124 | 161 | nimCmd.add nimFile |
| 125 | 162 | if verbose and not cached: echo "clonim: " & nimCmd.join(" ") |
| @@ -129,7 +166,7 @@ proc main() = | ||
| 129 | 166 | var code = 0 |
| 130 | 167 | if not cached: |
| 131 | 168 | (output, code) = execCmdEx(nimCmd.join(" ")) |
| 132 | - if code == 0: writeFile(stamp, buildKey(nimSrc, release)) | |
| 169 | + if code == 0: writeFile(stamp, buildKey(nimSrc, release, rtLib)) | |
| 133 | 170 | let tBuild = epochTime() - t1 |
| 134 | 171 | if code != 0: |
| 135 | 172 | stderr.writeLine("clonim: Nim backend failed\n" & output) |
| @@ -3,7 +3,7 @@ | |||
| 3 | ## clonim run foo.clj compile and run | 3 | ## clonim run foo.clj compile and run |
| 4 | ## clonim build foo.clj [-o bin] compile to a native binary | 4 | ## clonim build foo.clj [-o bin] compile to a native binary |
| 5 | ## clonim emit foo.clj print the generated Nim | 5 | ## clonim emit foo.clj print the generated Nim |
| 6 | -import std/[hashes, os, osproc, strutils, times] | 6 | +import std/[hashes, os, osproc, sequtils, strutils, times] |
| 7 | import runtime, compiler | 7 | import runtime, compiler |
| 8 | 8 | ||
| 9 | proc usage() = | 9 | proc usage() = |
| @@ -22,14 +22,43 @@ options: | |||
| 22 | quit(1) | 22 | quit(1) |
| 23 | 23 | ||
| 24 | proc srcDir(): string = | 24 | proc srcDir(): string = |
| 25 | - ## where runtime.nim / core.nim live, so generated code can import them | 25 | + ## Where generated programs find the private runtime interface. |
| 26 | for cand in [getAppDir(), getAppDir().parentDir / "src", | 26 | for cand in [getAppDir(), getAppDir().parentDir / "src", |
| 27 | getAppDir().parentDir.parentDir / "src"]: | 27 | getAppDir().parentDir.parentDir / "src"]: |
| 28 | - if fileExists(cand / "runtime.nim"): return cand | 28 | + if fileExists(cand / "app_runtime.nim"): return cand |
| 29 | getAppDir() | 29 | getAppDir() |
| 30 | 30 | ||
| 31 | +proc runtimeLib(release: bool): string = | ||
| 32 | + ## Release archives place the private runtime beside bin/. A source checkout | ||
| 33 | + ## builds the same archive into lib/ on first use. | ||
| 34 | + let libName = (if release: "libclonim_runtime.a" else: "libclonim_runtime_debug.a") | ||
| 35 | + let installed = getAppDir().parentDir / "lib" / libName | ||
| 36 | + let source = srcDir() / "runtime_lib.nim" | ||
| 37 | + if fileExists(installed): | ||
| 38 | + if not fileExists(source): return installed | ||
| 39 | + var stale = false | ||
| 40 | + for f in walkFiles(srcDir() / "*.nim"): | ||
| 41 | + if getLastModificationTime(f) > getLastModificationTime(installed): | ||
| 42 | + stale = true | ||
| 43 | + break | ||
| 44 | + if not stale: return installed | ||
| 45 | + if not fileExists(source): | ||
| 46 | + raise newException(IOError, "missing private runtime library: " & installed) | ||
| 47 | + createDir(installed.parentDir) | ||
| 48 | + var args = @["nim", "c", "--app:staticlib", "--nimMainPrefix:ClonimRuntime", | ||
| 49 | + "--hints:off", "--warnings:off", "--path:" & srcDir(), | ||
| 50 | + "--nimcache:" & installed.parentDir / | ||
| 51 | + (if release: "nimcache-release" else: "nimcache-debug"), | ||
| 52 | + "-o:" & installed] | ||
| 53 | + if release: args.add "-d:release" | ||
| 54 | + args.add source | ||
| 55 | + let (output, code) = execCmdEx(args.mapIt(quoteShell(it)).join(" ")) | ||
| 56 | + if code != 0: | ||
| 57 | + raise newException(IOError, "failed to build private runtime library\n" & output) | ||
| 58 | + installed | ||
| 59 | + | ||
| 31 | 60 | ||
| 32 | -proc buildKey(nimSrc: string, release: bool): string = | 61 | +proc buildKey(nimSrc: string, release: bool, rtLib: string): string = |
| 33 | ## Identifies everything the produced binary depends on. Any change here | 62 | ## Identifies everything the produced binary depends on. Any change here |
| 34 | ## invalidates the cached binary for a source file. | 63 | ## invalidates the cached binary for a source file. |
| 35 | var h: Hash = hash(nimSrc) !& hash(release) | 64 | var h: Hash = hash(nimSrc) !& hash(release) |
| @@ -39,6 +68,7 @@ proc buildKey(nimSrc: string, release: bool): string = | |||
| 39 | let nimExe = findExe("nim") | 68 | let nimExe = findExe("nim") |
| 40 | if nimExe.len > 0: | 69 | if nimExe.len > 0: |
| 41 | h = h !& hash($getLastModificationTime(nimExe)) | 70 | h = h !& hash($getLastModificationTime(nimExe)) |
| 71 | + h = h !& hash($getLastModificationTime(rtLib)) | ||
| 42 | $(!$h) | 72 | $(!$h) |
| 43 | 73 | ||
| 44 | proc main() = | 74 | proc main() = |
| @@ -111,15 +141,22 @@ proc main() = | |||
| 111 | 141 | ||
| 112 | # Even a warm nimcache costs a second or so of semantic checking and linking. | 142 | # Even a warm nimcache costs a second or so of semantic checking and linking. |
| 113 | # `run` skips the backend entirely when nothing that feeds the binary has | 143 | # `run` skips the backend entirely when nothing that feeds the binary has |
| 114 | - # changed: the generated Nim, the runtime/core sources it imports, the build | 144 | + # changed: the generated Nim, private runtime archive/interface, build flags, |
| 115 | - # flags, and the Nim compiler itself. | 145 | + # and the Nim compiler itself. |
| 116 | let stamp = work / "stamp" | 146 | let stamp = work / "stamp" |
| 147 | + var rtLib = "" | ||
| 148 | + try: | ||
| 149 | + rtLib = runtimeLib(release) | ||
| 150 | + except IOError, OSError: | ||
| 151 | + stderr.writeLine("clonim: " & getCurrentExceptionMsg()) | ||
| 152 | + quit(1) | ||
| 117 | let cached = cmd == "run" and fileExists(outBin) and fileExists(stamp) and | 153 | let cached = cmd == "run" and fileExists(outBin) and fileExists(stamp) and |
| 118 | - readFile(stamp) == buildKey(nimSrc, release) | 154 | + readFile(stamp) == buildKey(nimSrc, release, rtLib) |
| 119 | 155 | ||
| 120 | var nimCmd = @["nim", "c", "--hints:off", "--warnings:off", | 156 | var nimCmd = @["nim", "c", "--hints:off", "--warnings:off", |
| 121 | "--path:" & srcDir(), "--nimcache:" & (work / "cache"), | 157 | "--path:" & srcDir(), "--nimcache:" & (work / "cache"), |
| 122 | - "-o:" & outBin] | 158 | + "--passL:-Wl,--allow-multiple-definition", "--passL:" & rtLib, |
| 159 | + "--passL:-lm", "-o:" & outBin] | ||
| 123 | if release: nimCmd.add "-d:release" | 160 | if release: nimCmd.add "-d:release" |
| 124 | nimCmd.add nimFile | 161 | nimCmd.add nimFile |
| 125 | if verbose and not cached: echo "clonim: " & nimCmd.join(" ") | 162 | if verbose and not cached: echo "clonim: " & nimCmd.join(" ") |
| @@ -129,7 +166,7 @@ proc main() = | |||
| 129 | var code = 0 | 166 | var code = 0 |
| 130 | if not cached: | 167 | if not cached: |
| 131 | (output, code) = execCmdEx(nimCmd.join(" ")) | 168 | (output, code) = execCmdEx(nimCmd.join(" ")) |
| 132 | - if code == 0: writeFile(stamp, buildKey(nimSrc, release)) | 169 | + if code == 0: writeFile(stamp, buildKey(nimSrc, release, rtLib)) |
| 133 | let tBuild = epochTime() - t1 | 170 | let tBuild = epochTime() - t1 |
| 134 | if code != 0: | 171 | if code != 0: |
| 135 | stderr.writeLine("clonim: Nim backend failed\n" & output) | 172 | stderr.writeLine("clonim: Nim backend failed\n" & output) |
modified
src/compiler.nim +2 -2 | @@ -1134,7 +1134,7 @@ proc genInto(f: Value, dst: string, env: Env, c: Ctx) = | ||
| 1134 | 1134 | # ------------------------------------------------------------- entry point |
| 1135 | 1135 | const preamble = """ |
| 1136 | 1136 | ## Generated by clonim. Do not edit. |
| 1137 | -import runtime, core, namespaces | |
| 1137 | +import app_runtime | |
| 1138 | 1138 | |
| 1139 | 1139 | proc cljMain() = |
| 1140 | 1140 | """ |
| @@ -1165,7 +1165,7 @@ proc compileForms*(forms: seq[Value]): string = | ||
| 1165 | 1165 | c.line("var " & t & ": Value = NilV") |
| 1166 | 1166 | genInto(f, t, env, c) |
| 1167 | 1167 | c.line("discard " & t) |
| 1168 | - var src = preamble & " registerCore()\n registerNamespaceCore()\n" & c.prelude.join("\n") & "\n" & | |
| 1168 | + var src = preamble & " initClonimRuntime()\n registerCore()\n registerNamespaceCore()\n" & c.prelude.join("\n") & "\n" & | |
| 1169 | 1169 | c.body.join("\n") & "\n\n" |
| 1170 | 1170 | src &= """ |
| 1171 | 1171 | when isMainModule: |
| @@ -1134,7 +1134,7 @@ proc genInto(f: Value, dst: string, env: Env, c: Ctx) = | |||
| 1134 | # ------------------------------------------------------------- entry point | 1134 | # ------------------------------------------------------------- entry point |
| 1135 | const preamble = """ | 1135 | const preamble = """ |
| 1136 | ## Generated by clonim. Do not edit. | 1136 | ## Generated by clonim. Do not edit. |
| 1137 | -import runtime, core, namespaces | 1137 | +import app_runtime |
| 1138 | 1138 | ||
| 1139 | proc cljMain() = | 1139 | proc cljMain() = |
| 1140 | """ | 1140 | """ |
| @@ -1165,7 +1165,7 @@ proc compileForms*(forms: seq[Value]): string = | |||
| 1165 | c.line("var " & t & ": Value = NilV") | 1165 | c.line("var " & t & ": Value = NilV") |
| 1166 | genInto(f, t, env, c) | 1166 | genInto(f, t, env, c) |
| 1167 | c.line("discard " & t) | 1167 | c.line("discard " & t) |
| 1168 | - var src = preamble & " registerCore()\n registerNamespaceCore()\n" & c.prelude.join("\n") & "\n" & | 1168 | + var src = preamble & " initClonimRuntime()\n registerCore()\n registerNamespaceCore()\n" & c.prelude.join("\n") & "\n" & |
| 1169 | c.body.join("\n") & "\n\n" | 1169 | c.body.join("\n") & "\n\n" |
| 1170 | src &= """ | 1170 | src &= """ |
| 1171 | when isMainModule: | 1171 | when isMainModule: |
modified
src/core.nim +0 -5 | @@ -348,11 +348,6 @@ proc def(name: string, f: proc (args: openArray[Value]): Value {.closure.}) = | ||
| 348 | 348 | ## Each base element is pushed through the stages and whatever survives is |
| 349 | 349 | ## folded, so no chunk, cursor or intermediate seq is built between stages. |
| 350 | 350 | |
| 351 | -type FusedOp* = object | |
| 352 | - isMap*: bool ## map when true, filter when false | |
| 353 | - fn*: Value | |
| 354 | - keep*: bool ## filter: keep matches, or drop them | |
| 355 | - | |
| 356 | 351 | template pushThrough(ops: openArray[FusedOp], x0: Value, emit: untyped) = |
| 357 | 352 | ## Stages arrive outermost-first, so they apply in reverse. |
| 358 | 353 | var it {.inject.} = x0 |
| @@ -348,11 +348,6 @@ proc def(name: string, f: proc (args: openArray[Value]): Value {.closure.}) = | |||
| 348 | ## Each base element is pushed through the stages and whatever survives is | 348 | ## Each base element is pushed through the stages and whatever survives is |
| 349 | ## folded, so no chunk, cursor or intermediate seq is built between stages. | 349 | ## folded, so no chunk, cursor or intermediate seq is built between stages. |
| 350 | 350 | ||
| 351 | -type FusedOp* = object | ||
| 352 | - isMap*: bool ## map when true, filter when false | ||
| 353 | - fn*: Value | ||
| 354 | - keep*: bool ## filter: keep matches, or drop them | ||
| 355 | - | ||
| 356 | template pushThrough(ops: openArray[FusedOp], x0: Value, emit: untyped) = | 351 | template pushThrough(ops: openArray[FusedOp], x0: Value, emit: untyped) = |
| 357 | ## Stages arrive outermost-first, so they apply in reverse. | 352 | ## Stages arrive outermost-first, so they apply in reverse. |
| 358 | var it {.inject.} = x0 | 353 | var it {.inject.} = x0 |
modified
src/runtime.nim +2 -175 | @@ -7,6 +7,8 @@ | ||
| 7 | 7 | ## entry carries an `ord` stamp and iteration sorts by it — so printing and |
| 8 | 8 | ## `keys`/`vals` stay predictable the way Clojure's small array-maps are. |
| 9 | 9 | import std/[tables, strutils, hashes, bitops, algorithm] |
| 10 | +import runtime_types | |
| 11 | +export runtime_types | |
| 10 | 12 | |
| 11 | 13 | const |
| 12 | 14 | Bits = 5 |
| @@ -14,174 +16,6 @@ const | ||
| 14 | 16 | Mask = Width - 1 |
| 15 | 17 | MaxShift = 30 # beyond this a HAMT runs out of hash bits |
| 16 | 18 | |
| 17 | -type | |
| 18 | - Kind* = enum | |
| 19 | - kNil, kBool, kInt, kFloat, kStr, kKeyword, kSymbol, | |
| 20 | - kList, kVector, kMap, kSet, kCons, kChunk, kLazy, kFn | |
| 21 | - | |
| 22 | - VNode* = ref object | |
| 23 | - ## A trie node: leaves hold values, internal nodes hold children. | |
| 24 | - case leaf*: bool | |
| 25 | - of true: vals*: seq[Value] | |
| 26 | - of false: kids*: seq[VNode] | |
| 27 | - | |
| 28 | - PVec* = object | |
| 29 | - cnt*: int ## total element count | |
| 30 | - shift*: int ## bit offset of the root level | |
| 31 | - root*: VNode ## internal node (never nil) | |
| 32 | - tail*: seq[Value] ## up to 32 trailing elements, not yet in the trie | |
| 33 | - | |
| 34 | - MEntry* = object | |
| 35 | - key*, val*: Value | |
| 36 | - ord*: int ## insertion stamp, for stable iteration order | |
| 37 | - | |
| 38 | - MSlotKind* = enum msEntry, msNode | |
| 39 | - MSlot* = object | |
| 40 | - case sk*: MSlotKind | |
| 41 | - of msEntry: e*: MEntry | |
| 42 | - of msNode: node*: MNode | |
| 43 | - | |
| 44 | - MNode* = ref object | |
| 45 | - ## Bitmap-indexed node, or — once the hash bits run out — a linear | |
| 46 | - ## collision bucket. | |
| 47 | - case collision*: bool | |
| 48 | - of false: | |
| 49 | - bitmap*: uint32 | |
| 50 | - slots*: seq[MSlot] | |
| 51 | - of true: | |
| 52 | - kvs*: seq[MEntry] | |
| 53 | - | |
| 54 | - PMap* = object | |
| 55 | - root*: MNode ## nil when empty | |
| 56 | - cnt*: int | |
| 57 | - nextOrd*: int | |
| 58 | - | |
| 59 | - RecipeKind* = enum rkRange, rkMap, rkFilter | |
| 60 | - | |
| 61 | - Recipe* = ref object | |
| 62 | - ## What a lazy seq would produce, described rather than built. A consumer | |
| 63 | - ## that is going to realize everything anyway (reduce, count) can read this | |
| 64 | - ## and run the whole pipeline as a single loop, allocating no intermediate | |
| 65 | - ## chunks at all. Consumers that stop early ignore it and take the ordinary | |
| 66 | - ## lazy path, so laziness is unaffected. | |
| 67 | - case rk*: RecipeKind | |
| 68 | - of rkRange: | |
| 69 | - lo*, hi*, step*: int64 | |
| 70 | - bounded*: bool | |
| 71 | - of rkMap, rkFilter: | |
| 72 | - rfn*: Value | |
| 73 | - keep*: bool ## rkFilter: keep matches, or drop them | |
| 74 | - src*: Value | |
| 75 | - | |
| 76 | - Obj* = ref ValueObj | |
| 77 | - ## Value itself is not a ref: nil, bool, int and float live entirely in the | |
| 78 | - ## word below, so arithmetic and a numeric sequence never touch the heap. Only | |
| 79 | - ## the kinds with a real payload allocate a ValueObj, which is also what keeps | |
| 80 | - ## a lazy seq's memoisation working -- `force` mutates through `obj`, which is | |
| 81 | - ## still shared by every Value that names it. | |
| 82 | - ValueObj* = object | |
| 83 | - case kind*: Kind | |
| 84 | - of kNil, kBool, kInt, kFloat: discard | |
| 85 | - of kStr, kKeyword, kSymbol: s*: string | |
| 86 | - of kList: xs*: seq[Value] | |
| 87 | - of kVector: vec*: PVec | |
| 88 | - of kMap, kSet: m*: PMap | |
| 89 | - of kCons: | |
| 90 | - head*: Value | |
| 91 | - tl*: Value ## rest of the seq: a cons, a lazy seq, a coll, or nil | |
| 92 | - of kChunk: | |
| 93 | - ## A run of elements already realized together, plus the rest of the seq. | |
| 94 | - ## One of these per 32 elements replaces 32 cons cells and 32 thunks. | |
| 95 | - chunk*: seq[Value] | |
| 96 | - coff*: int ## index of the first element not yet consumed | |
| 97 | - ctl*: Value ## rest of the seq beyond this run | |
| 98 | - of kLazy: | |
| 99 | - thunk*: proc (): Value {.closure.} | |
| 100 | - cached*: Value | |
| 101 | - forced*: bool | |
| 102 | - rec*: Recipe ## nil unless this seq is a describable pipeline stage | |
| 103 | - of kFn: | |
| 104 | - fn*: proc (args: openArray[Value]): Value {.closure.} | |
| 105 | - name*: string | |
| 106 | - | |
| 107 | - Value* = object | |
| 108 | - kind*: Kind | |
| 109 | - raw*: int64 ## int payload; a float is bit-cast into it; bool is 0/1 | |
| 110 | - obj*: Obj ## nil for the scalar kinds | |
| 111 | - | |
| 112 | - CljError* = object of CatchableError | |
| 113 | - | |
| 114 | -# Field accessors, named so the rest of the codebase keeps reading `v.i`, | |
| 115 | -# `v.xs`, `v.head` exactly as it did when Value was a ref to the variant. | |
| 116 | -proc i*(v: Value): int64 {.inline.} = v.raw | |
| 117 | -proc b*(v: Value): bool {.inline.} = v.raw != 0 | |
| 118 | -proc f*(v: Value): float64 {.inline.} = cast[float64](v.raw) | |
| 119 | -proc s*(v: Value): lent string {.inline.} = v.obj.s | |
| 120 | -proc xs*(v: Value): lent seq[Value] {.inline.} = v.obj.xs | |
| 121 | -proc vec*(v: Value): lent PVec {.inline.} = v.obj.vec | |
| 122 | -proc m*(v: Value): lent PMap {.inline.} = v.obj.m | |
| 123 | -proc head*(v: Value): lent Value {.inline.} = v.obj.head | |
| 124 | -proc tl*(v: Value): lent Value {.inline.} = v.obj.tl | |
| 125 | -proc chunk*(v: Value): lent seq[Value] {.inline.} = v.obj.chunk | |
| 126 | -proc coff*(v: Value): int {.inline.} = v.obj.coff | |
| 127 | -proc ctl*(v: Value): lent Value {.inline.} = v.obj.ctl | |
| 128 | -proc cached*(v: Value): lent Value {.inline.} = v.obj.cached | |
| 129 | -proc forced*(v: Value): bool {.inline.} = v.obj.forced | |
| 130 | -proc thunk*(v: Value): auto {.inline.} = v.obj.thunk | |
| 131 | -proc fn*(v: Value): auto {.inline.} = v.obj.fn | |
| 132 | -proc name*(v: Value): lent string {.inline.} = v.obj.name | |
| 133 | - | |
| 134 | -## A Value is never a nil pointer now, but every `v.isNil` in the codebase | |
| 135 | -## meant "is this the nil value", so keep the spelling working. | |
| 136 | -proc isNil*(v: Value): bool {.inline.} = v.kind == kNil | |
| 137 | - | |
| 138 | -# ------------------------------------------------------------ teardown | |
| 139 | -## A realized lazy seq is a chain of `cons -> lazy -> cons -> …` refs, and ARC | |
| 140 | -## frees a chain by recursing into it — a million-element seq means a million | |
| 141 | -## destructor frames, i.e. a segfault at scope exit. So `kCons`/`kLazy` hand | |
| 142 | -## their tail to a worklist instead of letting the field drop inline, and the | |
| 143 | -## outermost destructor drains it in a loop. Nothing shared is mutated: a node | |
| 144 | -## another seq still holds simply survives with its refcount intact. | |
| 145 | -## | |
| 146 | -## Defining `=destroy` means the compiler stops generating field teardown for | |
| 147 | -## `ValueObj`, so every branch below has to release its own fields. | |
| 148 | - | |
| 149 | -var pendingFree: seq[Value] = @[] | |
| 150 | -var draining = false | |
| 151 | - | |
| 152 | -proc `=destroy`*(x: var ValueObj) = | |
| 153 | - case x.kind | |
| 154 | - of kStr, kKeyword, kSymbol: `=destroy`(x.s) | |
| 155 | - of kList: `=destroy`(x.xs) | |
| 156 | - of kVector: `=destroy`(x.vec) | |
| 157 | - of kMap, kSet: `=destroy`(x.m) | |
| 158 | - of kFn: | |
| 159 | - `=destroy`(x.fn) | |
| 160 | - `=destroy`(x.name) | |
| 161 | - of kCons: | |
| 162 | - `=destroy`(x.head) | |
| 163 | - if not x.tl.isNil: pendingFree.add x.tl # +1, outlives the release below | |
| 164 | - `=destroy`(x.tl) | |
| 165 | - of kChunk: | |
| 166 | - `=destroy`(x.chunk) | |
| 167 | - if not x.ctl.isNil: pendingFree.add x.ctl | |
| 168 | - `=destroy`(x.ctl) | |
| 169 | - of kLazy: | |
| 170 | - `=destroy`(x.thunk) | |
| 171 | - if not x.cached.isNil: pendingFree.add x.cached | |
| 172 | - `=destroy`(x.cached) | |
| 173 | - of kNil, kBool, kInt, kFloat: discard | |
| 174 | - if draining: return | |
| 175 | - draining = true | |
| 176 | - while pendingFree.len > 0: | |
| 177 | - let v = pendingFree.pop() | |
| 178 | - discard v # dies here: its own tail is queued, not recursed into | |
| 179 | - draining = false | |
| 180 | - | |
| 181 | -let NilV* = Value(kind: kNil) | |
| 182 | -let TrueV* = Value(kind: kBool, raw: 1) | |
| 183 | -let FalseV* = Value(kind: kBool, raw: 0) | |
| 184 | - | |
| 185 | 19 | proc err*(msg: string) {.noreturn.} = raise newException(CljError, msg) |
| 186 | 20 | |
| 187 | 21 | proc equals*(a, b: Value): bool |
| @@ -769,11 +603,6 @@ proc str*(v: Value): string = toStr(v, false) | ||
| 769 | 603 | ## Vars are cells, as in Clojure: compiled code resolves the cell once and |
| 770 | 604 | ## reads through it, so a call site costs one pointer deref, not a hash lookup, |
| 771 | 605 | ## while `def` can still rebind the var later. |
| 772 | -type VarCell* = ref object | |
| 773 | - name*: string | |
| 774 | - bound*: bool | |
| 775 | - v*: Value | |
| 776 | - | |
| 777 | 606 | var globals*: Table[string, VarCell] = initTable[string, VarCell]() |
| 778 | 607 | |
| 779 | 608 | proc varCell*(name: string): VarCell = |
| @@ -843,8 +672,6 @@ proc toSeq*(v: Value): seq[Value] = | ||
| 843 | 672 | r |
| 844 | 673 | else: err("Don't know how to create seq from: " & prStr(v)) |
| 845 | 674 | |
| 846 | -let emptyArgs*: array[0, Value] = [] | |
| 847 | - | |
| 848 | 675 | ## True while a var still holds the exact fn a call site was compiled against. |
| 849 | 676 | ## Call sites that bind a known-arity fn or an inlined primitive directly guard |
| 850 | 677 | ## on this, so a later `def` that rebinds the name still takes effect. |
| @@ -7,6 +7,8 @@ | |||
| 7 | ## entry carries an `ord` stamp and iteration sorts by it — so printing and | 7 | ## entry carries an `ord` stamp and iteration sorts by it — so printing and |
| 8 | ## `keys`/`vals` stay predictable the way Clojure's small array-maps are. | 8 | ## `keys`/`vals` stay predictable the way Clojure's small array-maps are. |
| 9 | import std/[tables, strutils, hashes, bitops, algorithm] | 9 | import std/[tables, strutils, hashes, bitops, algorithm] |
| 10 | +import runtime_types | ||
| 11 | +export runtime_types | ||
| 10 | 12 | ||
| 11 | const | 13 | const |
| 12 | Bits = 5 | 14 | Bits = 5 |
| @@ -14,174 +16,6 @@ const | |||
| 14 | Mask = Width - 1 | 16 | Mask = Width - 1 |
| 15 | MaxShift = 30 # beyond this a HAMT runs out of hash bits | 17 | MaxShift = 30 # beyond this a HAMT runs out of hash bits |
| 16 | 18 | ||
| 17 | -type | ||
| 18 | - Kind* = enum | ||
| 19 | - kNil, kBool, kInt, kFloat, kStr, kKeyword, kSymbol, | ||
| 20 | - kList, kVector, kMap, kSet, kCons, kChunk, kLazy, kFn | ||
| 21 | - | ||
| 22 | - VNode* = ref object | ||
| 23 | - ## A trie node: leaves hold values, internal nodes hold children. | ||
| 24 | - case leaf*: bool | ||
| 25 | - of true: vals*: seq[Value] | ||
| 26 | - of false: kids*: seq[VNode] | ||
| 27 | - | ||
| 28 | - PVec* = object | ||
| 29 | - cnt*: int ## total element count | ||
| 30 | - shift*: int ## bit offset of the root level | ||
| 31 | - root*: VNode ## internal node (never nil) | ||
| 32 | - tail*: seq[Value] ## up to 32 trailing elements, not yet in the trie | ||
| 33 | - | ||
| 34 | - MEntry* = object | ||
| 35 | - key*, val*: Value | ||
| 36 | - ord*: int ## insertion stamp, for stable iteration order | ||
| 37 | - | ||
| 38 | - MSlotKind* = enum msEntry, msNode | ||
| 39 | - MSlot* = object | ||
| 40 | - case sk*: MSlotKind | ||
| 41 | - of msEntry: e*: MEntry | ||
| 42 | - of msNode: node*: MNode | ||
| 43 | - | ||
| 44 | - MNode* = ref object | ||
| 45 | - ## Bitmap-indexed node, or — once the hash bits run out — a linear | ||
| 46 | - ## collision bucket. | ||
| 47 | - case collision*: bool | ||
| 48 | - of false: | ||
| 49 | - bitmap*: uint32 | ||
| 50 | - slots*: seq[MSlot] | ||
| 51 | - of true: | ||
| 52 | - kvs*: seq[MEntry] | ||
| 53 | - | ||
| 54 | - PMap* = object | ||
| 55 | - root*: MNode ## nil when empty | ||
| 56 | - cnt*: int | ||
| 57 | - nextOrd*: int | ||
| 58 | - | ||
| 59 | - RecipeKind* = enum rkRange, rkMap, rkFilter | ||
| 60 | - | ||
| 61 | - Recipe* = ref object | ||
| 62 | - ## What a lazy seq would produce, described rather than built. A consumer | ||
| 63 | - ## that is going to realize everything anyway (reduce, count) can read this | ||
| 64 | - ## and run the whole pipeline as a single loop, allocating no intermediate | ||
| 65 | - ## chunks at all. Consumers that stop early ignore it and take the ordinary | ||
| 66 | - ## lazy path, so laziness is unaffected. | ||
| 67 | - case rk*: RecipeKind | ||
| 68 | - of rkRange: | ||
| 69 | - lo*, hi*, step*: int64 | ||
| 70 | - bounded*: bool | ||
| 71 | - of rkMap, rkFilter: | ||
| 72 | - rfn*: Value | ||
| 73 | - keep*: bool ## rkFilter: keep matches, or drop them | ||
| 74 | - src*: Value | ||
| 75 | - | ||
| 76 | - Obj* = ref ValueObj | ||
| 77 | - ## Value itself is not a ref: nil, bool, int and float live entirely in the | ||
| 78 | - ## word below, so arithmetic and a numeric sequence never touch the heap. Only | ||
| 79 | - ## the kinds with a real payload allocate a ValueObj, which is also what keeps | ||
| 80 | - ## a lazy seq's memoisation working -- `force` mutates through `obj`, which is | ||
| 81 | - ## still shared by every Value that names it. | ||
| 82 | - ValueObj* = object | ||
| 83 | - case kind*: Kind | ||
| 84 | - of kNil, kBool, kInt, kFloat: discard | ||
| 85 | - of kStr, kKeyword, kSymbol: s*: string | ||
| 86 | - of kList: xs*: seq[Value] | ||
| 87 | - of kVector: vec*: PVec | ||
| 88 | - of kMap, kSet: m*: PMap | ||
| 89 | - of kCons: | ||
| 90 | - head*: Value | ||
| 91 | - tl*: Value ## rest of the seq: a cons, a lazy seq, a coll, or nil | ||
| 92 | - of kChunk: | ||
| 93 | - ## A run of elements already realized together, plus the rest of the seq. | ||
| 94 | - ## One of these per 32 elements replaces 32 cons cells and 32 thunks. | ||
| 95 | - chunk*: seq[Value] | ||
| 96 | - coff*: int ## index of the first element not yet consumed | ||
| 97 | - ctl*: Value ## rest of the seq beyond this run | ||
| 98 | - of kLazy: | ||
| 99 | - thunk*: proc (): Value {.closure.} | ||
| 100 | - cached*: Value | ||
| 101 | - forced*: bool | ||
| 102 | - rec*: Recipe ## nil unless this seq is a describable pipeline stage | ||
| 103 | - of kFn: | ||
| 104 | - fn*: proc (args: openArray[Value]): Value {.closure.} | ||
| 105 | - name*: string | ||
| 106 | - | ||
| 107 | - Value* = object | ||
| 108 | - kind*: Kind | ||
| 109 | - raw*: int64 ## int payload; a float is bit-cast into it; bool is 0/1 | ||
| 110 | - obj*: Obj ## nil for the scalar kinds | ||
| 111 | - | ||
| 112 | - CljError* = object of CatchableError | ||
| 113 | - | ||
| 114 | -# Field accessors, named so the rest of the codebase keeps reading `v.i`, | ||
| 115 | -# `v.xs`, `v.head` exactly as it did when Value was a ref to the variant. | ||
| 116 | -proc i*(v: Value): int64 {.inline.} = v.raw | ||
| 117 | -proc b*(v: Value): bool {.inline.} = v.raw != 0 | ||
| 118 | -proc f*(v: Value): float64 {.inline.} = cast[float64](v.raw) | ||
| 119 | -proc s*(v: Value): lent string {.inline.} = v.obj.s | ||
| 120 | -proc xs*(v: Value): lent seq[Value] {.inline.} = v.obj.xs | ||
| 121 | -proc vec*(v: Value): lent PVec {.inline.} = v.obj.vec | ||
| 122 | -proc m*(v: Value): lent PMap {.inline.} = v.obj.m | ||
| 123 | -proc head*(v: Value): lent Value {.inline.} = v.obj.head | ||
| 124 | -proc tl*(v: Value): lent Value {.inline.} = v.obj.tl | ||
| 125 | -proc chunk*(v: Value): lent seq[Value] {.inline.} = v.obj.chunk | ||
| 126 | -proc coff*(v: Value): int {.inline.} = v.obj.coff | ||
| 127 | -proc ctl*(v: Value): lent Value {.inline.} = v.obj.ctl | ||
| 128 | -proc cached*(v: Value): lent Value {.inline.} = v.obj.cached | ||
| 129 | -proc forced*(v: Value): bool {.inline.} = v.obj.forced | ||
| 130 | -proc thunk*(v: Value): auto {.inline.} = v.obj.thunk | ||
| 131 | -proc fn*(v: Value): auto {.inline.} = v.obj.fn | ||
| 132 | -proc name*(v: Value): lent string {.inline.} = v.obj.name | ||
| 133 | - | ||
| 134 | -## A Value is never a nil pointer now, but every `v.isNil` in the codebase | ||
| 135 | -## meant "is this the nil value", so keep the spelling working. | ||
| 136 | -proc isNil*(v: Value): bool {.inline.} = v.kind == kNil | ||
| 137 | - | ||
| 138 | -# ------------------------------------------------------------ teardown | ||
| 139 | -## A realized lazy seq is a chain of `cons -> lazy -> cons -> …` refs, and ARC | ||
| 140 | -## frees a chain by recursing into it — a million-element seq means a million | ||
| 141 | -## destructor frames, i.e. a segfault at scope exit. So `kCons`/`kLazy` hand | ||
| 142 | -## their tail to a worklist instead of letting the field drop inline, and the | ||
| 143 | -## outermost destructor drains it in a loop. Nothing shared is mutated: a node | ||
| 144 | -## another seq still holds simply survives with its refcount intact. | ||
| 145 | -## | ||
| 146 | -## Defining `=destroy` means the compiler stops generating field teardown for | ||
| 147 | -## `ValueObj`, so every branch below has to release its own fields. | ||
| 148 | - | ||
| 149 | -var pendingFree: seq[Value] = @[] | ||
| 150 | -var draining = false | ||
| 151 | - | ||
| 152 | -proc `=destroy`*(x: var ValueObj) = | ||
| 153 | - case x.kind | ||
| 154 | - of kStr, kKeyword, kSymbol: `=destroy`(x.s) | ||
| 155 | - of kList: `=destroy`(x.xs) | ||
| 156 | - of kVector: `=destroy`(x.vec) | ||
| 157 | - of kMap, kSet: `=destroy`(x.m) | ||
| 158 | - of kFn: | ||
| 159 | - `=destroy`(x.fn) | ||
| 160 | - `=destroy`(x.name) | ||
| 161 | - of kCons: | ||
| 162 | - `=destroy`(x.head) | ||
| 163 | - if not x.tl.isNil: pendingFree.add x.tl # +1, outlives the release below | ||
| 164 | - `=destroy`(x.tl) | ||
| 165 | - of kChunk: | ||
| 166 | - `=destroy`(x.chunk) | ||
| 167 | - if not x.ctl.isNil: pendingFree.add x.ctl | ||
| 168 | - `=destroy`(x.ctl) | ||
| 169 | - of kLazy: | ||
| 170 | - `=destroy`(x.thunk) | ||
| 171 | - if not x.cached.isNil: pendingFree.add x.cached | ||
| 172 | - `=destroy`(x.cached) | ||
| 173 | - of kNil, kBool, kInt, kFloat: discard | ||
| 174 | - if draining: return | ||
| 175 | - draining = true | ||
| 176 | - while pendingFree.len > 0: | ||
| 177 | - let v = pendingFree.pop() | ||
| 178 | - discard v # dies here: its own tail is queued, not recursed into | ||
| 179 | - draining = false | ||
| 180 | - | ||
| 181 | -let NilV* = Value(kind: kNil) | ||
| 182 | -let TrueV* = Value(kind: kBool, raw: 1) | ||
| 183 | -let FalseV* = Value(kind: kBool, raw: 0) | ||
| 184 | - | ||
| 185 | proc err*(msg: string) {.noreturn.} = raise newException(CljError, msg) | 19 | proc err*(msg: string) {.noreturn.} = raise newException(CljError, msg) |
| 186 | 20 | ||
| 187 | proc equals*(a, b: Value): bool | 21 | proc equals*(a, b: Value): bool |
| @@ -769,11 +603,6 @@ proc str*(v: Value): string = toStr(v, false) | |||
| 769 | ## Vars are cells, as in Clojure: compiled code resolves the cell once and | 603 | ## Vars are cells, as in Clojure: compiled code resolves the cell once and |
| 770 | ## reads through it, so a call site costs one pointer deref, not a hash lookup, | 604 | ## reads through it, so a call site costs one pointer deref, not a hash lookup, |
| 771 | ## while `def` can still rebind the var later. | 605 | ## while `def` can still rebind the var later. |
| 772 | -type VarCell* = ref object | ||
| 773 | - name*: string | ||
| 774 | - bound*: bool | ||
| 775 | - v*: Value | ||
| 776 | - | ||
| 777 | var globals*: Table[string, VarCell] = initTable[string, VarCell]() | 606 | var globals*: Table[string, VarCell] = initTable[string, VarCell]() |
| 778 | 607 | ||
| 779 | proc varCell*(name: string): VarCell = | 608 | proc varCell*(name: string): VarCell = |
| @@ -843,8 +672,6 @@ proc toSeq*(v: Value): seq[Value] = | |||
| 843 | r | 672 | r |
| 844 | else: err("Don't know how to create seq from: " & prStr(v)) | 673 | else: err("Don't know how to create seq from: " & prStr(v)) |
| 845 | 674 | ||
| 846 | -let emptyArgs*: array[0, Value] = [] | ||
| 847 | - | ||
| 848 | ## True while a var still holds the exact fn a call site was compiled against. | 675 | ## True while a var still holds the exact fn a call site was compiled against. |
| 849 | ## Call sites that bind a known-arity fn or an inlined primitive directly guard | 676 | ## Call sites that bind a known-arity fn or an inlined primitive directly guard |
| 850 | ## on this, so a later `def` that rebinds the name still takes effect. | 677 | ## on this, so a later `def` that rebinds the name still takes effect. |
added
src/runtime_lib.nim +53 -0 | new file mode 100644 | ||
| @@ -0,0 +1,53 @@ | ||
| 1 | +## C-named entry points for clonim's private static runtime archive. | |
| 2 | +import runtime, core, namespaces | |
| 3 | + | |
| 4 | +proc clonim_mk_fn(name: string, | |
| 5 | + f: proc (args: openArray[Value]): Value {.closure.}): Value | |
| 6 | + {.cdecl, exportc.} = | |
| 7 | + mkFn(name, f) | |
| 8 | + | |
| 9 | +{.push cdecl, exportc.} | |
| 10 | +proc clonim_register_core() = registerCore() | |
| 11 | +proc clonim_register_namespace_core() = registerNamespaceCore() | |
| 12 | +proc clonim_err(msg: string) {.noreturn.} = err(msg) | |
| 13 | +proc clonim_var_cell(name: string): VarCell = varCell(name) | |
| 14 | +proc clonim_set_var(name: string, v: Value): Value = setVar(name, v) | |
| 15 | +proc clonim_cell_get(c: VarCell): Value = cellGet(c) | |
| 16 | +proc clonim_cell_is(c: VarCell, v: Value): bool = cellIs(c, v) | |
| 17 | +proc clonim_get_var(name: string): Value = getVar(name) | |
| 18 | +proc clonim_call(f: Value, args: openArray[Value]): Value = call(f, args) | |
| 19 | +proc clonim_arg_at(args: openArray[Value], i: int): Value = argAt(args, i) | |
| 20 | +proc clonim_rest_args(args: openArray[Value], i: int): Value = restArgs(args, i) | |
| 21 | +proc clonim_seq_drop(v: Value, n: int): Value = seqDrop(v, n) | |
| 22 | +proc clonim_to_seq(v: Value): seq[Value] = toSeq(v) | |
| 23 | +proc clonim_truthy(v: Value): bool = truthy(v) | |
| 24 | + | |
| 25 | +proc clonim_mk_int(x: int64): Value = mkInt(x) | |
| 26 | +proc clonim_mk_float(x: float64): Value = mkFloat(x) | |
| 27 | +proc clonim_mk_str(x: string): Value = mkStr(x) | |
| 28 | +proc clonim_mk_keyword(x: string): Value = mkKeyword(x) | |
| 29 | +proc clonim_mk_symbol(x: string): Value = mkSymbol(x) | |
| 30 | +proc clonim_mk_list(xs: openArray[Value]): Value = mkList(xs) | |
| 31 | +proc clonim_mk_vector(xs: openArray[Value]): Value = mkVector(xs) | |
| 32 | +proc clonim_mk_set(xs: openArray[Value]): Value = mkSet(xs) | |
| 33 | +proc clonim_mk_map(ps: seq[(Value, Value)]): Value = mkMap(ps) | |
| 34 | + | |
| 35 | +proc clonim_idiv(a, b: int64): int64 = idiv(a, b) | |
| 36 | +proc clonim_irem(a, b: int64): int64 = irem(a, b) | |
| 37 | +proc clonim_add2(a, b: Value): Value = add2(a, b) | |
| 38 | +proc clonim_sub2(a, b: Value): Value = sub2(a, b) | |
| 39 | +proc clonim_mul2(a, b: Value): Value = mul2(a, b) | |
| 40 | +proc clonim_lt2(a, b: Value): Value = lt2(a, b) | |
| 41 | +proc clonim_gt2(a, b: Value): Value = gt2(a, b) | |
| 42 | +proc clonim_le2(a, b: Value): Value = le2(a, b) | |
| 43 | +proc clonim_ge2(a, b: Value): Value = ge2(a, b) | |
| 44 | +proc clonim_eq2(a, b: Value): Value = eq2(a, b) | |
| 45 | +proc clonim_ne2(a, b: Value): Value = ne2(a, b) | |
| 46 | +proc clonim_inc1(a: Value): Value = inc1(a) | |
| 47 | +proc clonim_dec1(a: Value): Value = dec1(a) | |
| 48 | +proc clonim_fused_reduce(f, init: Value, hasInit: bool, base: Value, | |
| 49 | + ops: openArray[FusedOp]): Value = | |
| 50 | + fusedReduce(f, init, hasInit, base, ops) | |
| 51 | +proc clonim_fused_count(base: Value, ops: openArray[FusedOp]): Value = | |
| 52 | + fusedCount(base, ops) | |
| 53 | +{.pop.} | |
| new file mode 100644 | |||
| @@ -0,0 +1,53 @@ | |||
| 1 | +## C-named entry points for clonim's private static runtime archive. | ||
| 2 | +import runtime, core, namespaces | ||
| 3 | + | ||
| 4 | +proc clonim_mk_fn(name: string, | ||
| 5 | + f: proc (args: openArray[Value]): Value {.closure.}): Value | ||
| 6 | + {.cdecl, exportc.} = | ||
| 7 | + mkFn(name, f) | ||
| 8 | + | ||
| 9 | +{.push cdecl, exportc.} | ||
| 10 | +proc clonim_register_core() = registerCore() | ||
| 11 | +proc clonim_register_namespace_core() = registerNamespaceCore() | ||
| 12 | +proc clonim_err(msg: string) {.noreturn.} = err(msg) | ||
| 13 | +proc clonim_var_cell(name: string): VarCell = varCell(name) | ||
| 14 | +proc clonim_set_var(name: string, v: Value): Value = setVar(name, v) | ||
| 15 | +proc clonim_cell_get(c: VarCell): Value = cellGet(c) | ||
| 16 | +proc clonim_cell_is(c: VarCell, v: Value): bool = cellIs(c, v) | ||
| 17 | +proc clonim_get_var(name: string): Value = getVar(name) | ||
| 18 | +proc clonim_call(f: Value, args: openArray[Value]): Value = call(f, args) | ||
| 19 | +proc clonim_arg_at(args: openArray[Value], i: int): Value = argAt(args, i) | ||
| 20 | +proc clonim_rest_args(args: openArray[Value], i: int): Value = restArgs(args, i) | ||
| 21 | +proc clonim_seq_drop(v: Value, n: int): Value = seqDrop(v, n) | ||
| 22 | +proc clonim_to_seq(v: Value): seq[Value] = toSeq(v) | ||
| 23 | +proc clonim_truthy(v: Value): bool = truthy(v) | ||
| 24 | + | ||
| 25 | +proc clonim_mk_int(x: int64): Value = mkInt(x) | ||
| 26 | +proc clonim_mk_float(x: float64): Value = mkFloat(x) | ||
| 27 | +proc clonim_mk_str(x: string): Value = mkStr(x) | ||
| 28 | +proc clonim_mk_keyword(x: string): Value = mkKeyword(x) | ||
| 29 | +proc clonim_mk_symbol(x: string): Value = mkSymbol(x) | ||
| 30 | +proc clonim_mk_list(xs: openArray[Value]): Value = mkList(xs) | ||
| 31 | +proc clonim_mk_vector(xs: openArray[Value]): Value = mkVector(xs) | ||
| 32 | +proc clonim_mk_set(xs: openArray[Value]): Value = mkSet(xs) | ||
| 33 | +proc clonim_mk_map(ps: seq[(Value, Value)]): Value = mkMap(ps) | ||
| 34 | + | ||
| 35 | +proc clonim_idiv(a, b: int64): int64 = idiv(a, b) | ||
| 36 | +proc clonim_irem(a, b: int64): int64 = irem(a, b) | ||
| 37 | +proc clonim_add2(a, b: Value): Value = add2(a, b) | ||
| 38 | +proc clonim_sub2(a, b: Value): Value = sub2(a, b) | ||
| 39 | +proc clonim_mul2(a, b: Value): Value = mul2(a, b) | ||
| 40 | +proc clonim_lt2(a, b: Value): Value = lt2(a, b) | ||
| 41 | +proc clonim_gt2(a, b: Value): Value = gt2(a, b) | ||
| 42 | +proc clonim_le2(a, b: Value): Value = le2(a, b) | ||
| 43 | +proc clonim_ge2(a, b: Value): Value = ge2(a, b) | ||
| 44 | +proc clonim_eq2(a, b: Value): Value = eq2(a, b) | ||
| 45 | +proc clonim_ne2(a, b: Value): Value = ne2(a, b) | ||
| 46 | +proc clonim_inc1(a: Value): Value = inc1(a) | ||
| 47 | +proc clonim_dec1(a: Value): Value = dec1(a) | ||
| 48 | +proc clonim_fused_reduce(f, init: Value, hasInit: bool, base: Value, | ||
| 49 | + ops: openArray[FusedOp]): Value = | ||
| 50 | + fusedReduce(f, init, hasInit, base, ops) | ||
| 51 | +proc clonim_fused_count(base: Value, ops: openArray[FusedOp]): Value = | ||
| 52 | + fusedCount(base, ops) | ||
| 53 | +{.pop.} | ||
added
src/runtime_types.nim +147 -0 | new file mode 100644 | ||
| @@ -0,0 +1,147 @@ | ||
| 1 | +## ABI shared by generated clonim programs and the private static runtime. | |
| 2 | +## This is not a public interface: it is built and shipped as one versioned | |
| 3 | +## unit with the matching compiler and runtime archive. | |
| 4 | + | |
| 5 | +type | |
| 6 | + Kind* = enum | |
| 7 | + kNil, kBool, kInt, kFloat, kStr, kKeyword, kSymbol, | |
| 8 | + kList, kVector, kMap, kSet, kCons, kChunk, kLazy, kFn | |
| 9 | + | |
| 10 | + VNode* = ref object | |
| 11 | + case leaf*: bool | |
| 12 | + of true: vals*: seq[Value] | |
| 13 | + of false: kids*: seq[VNode] | |
| 14 | + | |
| 15 | + PVec* = object | |
| 16 | + cnt*, shift*: int | |
| 17 | + root*: VNode | |
| 18 | + tail*: seq[Value] | |
| 19 | + | |
| 20 | + MEntry* = object | |
| 21 | + key*, val*: Value | |
| 22 | + ord*: int | |
| 23 | + | |
| 24 | + MSlotKind* = enum msEntry, msNode | |
| 25 | + MSlot* = object | |
| 26 | + case sk*: MSlotKind | |
| 27 | + of msEntry: e*: MEntry | |
| 28 | + of msNode: node*: MNode | |
| 29 | + | |
| 30 | + MNode* = ref object | |
| 31 | + case collision*: bool | |
| 32 | + of false: | |
| 33 | + bitmap*: uint32 | |
| 34 | + slots*: seq[MSlot] | |
| 35 | + of true: | |
| 36 | + kvs*: seq[MEntry] | |
| 37 | + | |
| 38 | + PMap* = object | |
| 39 | + root*: MNode | |
| 40 | + cnt*, nextOrd*: int | |
| 41 | + | |
| 42 | + RecipeKind* = enum rkRange, rkMap, rkFilter | |
| 43 | + Recipe* = ref object | |
| 44 | + case rk*: RecipeKind | |
| 45 | + of rkRange: | |
| 46 | + lo*, hi*, step*: int64 | |
| 47 | + bounded*: bool | |
| 48 | + of rkMap, rkFilter: | |
| 49 | + rfn*: Value | |
| 50 | + keep*: bool | |
| 51 | + src*: Value | |
| 52 | + | |
| 53 | + Obj* = ref ValueObj | |
| 54 | + ValueObj* = object | |
| 55 | + case kind*: Kind | |
| 56 | + of kNil, kBool, kInt, kFloat: discard | |
| 57 | + of kStr, kKeyword, kSymbol: s*: string | |
| 58 | + of kList: xs*: seq[Value] | |
| 59 | + of kVector: vec*: PVec | |
| 60 | + of kMap, kSet: m*: PMap | |
| 61 | + of kCons: | |
| 62 | + head*, tl*: Value | |
| 63 | + of kChunk: | |
| 64 | + chunk*: seq[Value] | |
| 65 | + coff*: int | |
| 66 | + ctl*: Value | |
| 67 | + of kLazy: | |
| 68 | + thunk*: proc (): Value {.closure.} | |
| 69 | + cached*: Value | |
| 70 | + forced*: bool | |
| 71 | + rec*: Recipe | |
| 72 | + of kFn: | |
| 73 | + fn*: proc (args: openArray[Value]): Value {.closure.} | |
| 74 | + name*: string | |
| 75 | + | |
| 76 | + Value* = object | |
| 77 | + kind*: Kind | |
| 78 | + raw*: int64 | |
| 79 | + obj*: Obj | |
| 80 | + | |
| 81 | + CljError* = object of CatchableError | |
| 82 | + | |
| 83 | + VarCell* = ref object | |
| 84 | + name*: string | |
| 85 | + bound*: bool | |
| 86 | + v*: Value | |
| 87 | + | |
| 88 | + FusedOp* = object | |
| 89 | + isMap*: bool | |
| 90 | + fn*: Value | |
| 91 | + keep*: bool | |
| 92 | + | |
| 93 | +proc i*(v: Value): int64 {.inline.} = v.raw | |
| 94 | +proc b*(v: Value): bool {.inline.} = v.raw != 0 | |
| 95 | +proc f*(v: Value): float64 {.inline.} = cast[float64](v.raw) | |
| 96 | +proc s*(v: Value): lent string {.inline.} = v.obj.s | |
| 97 | +proc xs*(v: Value): lent seq[Value] {.inline.} = v.obj.xs | |
| 98 | +proc vec*(v: Value): lent PVec {.inline.} = v.obj.vec | |
| 99 | +proc m*(v: Value): lent PMap {.inline.} = v.obj.m | |
| 100 | +proc head*(v: Value): lent Value {.inline.} = v.obj.head | |
| 101 | +proc tl*(v: Value): lent Value {.inline.} = v.obj.tl | |
| 102 | +proc chunk*(v: Value): lent seq[Value] {.inline.} = v.obj.chunk | |
| 103 | +proc coff*(v: Value): int {.inline.} = v.obj.coff | |
| 104 | +proc ctl*(v: Value): lent Value {.inline.} = v.obj.ctl | |
| 105 | +proc cached*(v: Value): lent Value {.inline.} = v.obj.cached | |
| 106 | +proc forced*(v: Value): bool {.inline.} = v.obj.forced | |
| 107 | +proc thunk*(v: Value): auto {.inline.} = v.obj.thunk | |
| 108 | +proc fn*(v: Value): auto {.inline.} = v.obj.fn | |
| 109 | +proc name*(v: Value): lent string {.inline.} = v.obj.name | |
| 110 | +proc isNil*(v: Value): bool {.inline.} = v.kind == kNil | |
| 111 | + | |
| 112 | +var pendingFree: seq[Value] = @[] | |
| 113 | +var draining = false | |
| 114 | + | |
| 115 | +proc `=destroy`*(x: var ValueObj) = | |
| 116 | + case x.kind | |
| 117 | + of kStr, kKeyword, kSymbol: `=destroy`(x.s) | |
| 118 | + of kList: `=destroy`(x.xs) | |
| 119 | + of kVector: `=destroy`(x.vec) | |
| 120 | + of kMap, kSet: `=destroy`(x.m) | |
| 121 | + of kFn: | |
| 122 | + `=destroy`(x.fn) | |
| 123 | + `=destroy`(x.name) | |
| 124 | + of kCons: | |
| 125 | + `=destroy`(x.head) | |
| 126 | + if not x.tl.isNil: pendingFree.add x.tl | |
| 127 | + `=destroy`(x.tl) | |
| 128 | + of kChunk: | |
| 129 | + `=destroy`(x.chunk) | |
| 130 | + if not x.ctl.isNil: pendingFree.add x.ctl | |
| 131 | + `=destroy`(x.ctl) | |
| 132 | + of kLazy: | |
| 133 | + `=destroy`(x.thunk) | |
| 134 | + if not x.cached.isNil: pendingFree.add x.cached | |
| 135 | + `=destroy`(x.cached) | |
| 136 | + of kNil, kBool, kInt, kFloat: discard | |
| 137 | + if draining: return | |
| 138 | + draining = true | |
| 139 | + while pendingFree.len > 0: | |
| 140 | + let v = pendingFree.pop() | |
| 141 | + discard v | |
| 142 | + draining = false | |
| 143 | + | |
| 144 | +let NilV* = Value(kind: kNil) | |
| 145 | +let TrueV* = Value(kind: kBool, raw: 1) | |
| 146 | +let FalseV* = Value(kind: kBool, raw: 0) | |
| 147 | +let emptyArgs*: array[0, Value] = [] | |
| new file mode 100644 | |||
| @@ -0,0 +1,147 @@ | |||
| 1 | +## ABI shared by generated clonim programs and the private static runtime. | ||
| 2 | +## This is not a public interface: it is built and shipped as one versioned | ||
| 3 | +## unit with the matching compiler and runtime archive. | ||
| 4 | + | ||
| 5 | +type | ||
| 6 | + Kind* = enum | ||
| 7 | + kNil, kBool, kInt, kFloat, kStr, kKeyword, kSymbol, | ||
| 8 | + kList, kVector, kMap, kSet, kCons, kChunk, kLazy, kFn | ||
| 9 | + | ||
| 10 | + VNode* = ref object | ||
| 11 | + case leaf*: bool | ||
| 12 | + of true: vals*: seq[Value] | ||
| 13 | + of false: kids*: seq[VNode] | ||
| 14 | + | ||
| 15 | + PVec* = object | ||
| 16 | + cnt*, shift*: int | ||
| 17 | + root*: VNode | ||
| 18 | + tail*: seq[Value] | ||
| 19 | + | ||
| 20 | + MEntry* = object | ||
| 21 | + key*, val*: Value | ||
| 22 | + ord*: int | ||
| 23 | + | ||
| 24 | + MSlotKind* = enum msEntry, msNode | ||
| 25 | + MSlot* = object | ||
| 26 | + case sk*: MSlotKind | ||
| 27 | + of msEntry: e*: MEntry | ||
| 28 | + of msNode: node*: MNode | ||
| 29 | + | ||
| 30 | + MNode* = ref object | ||
| 31 | + case collision*: bool | ||
| 32 | + of false: | ||
| 33 | + bitmap*: uint32 | ||
| 34 | + slots*: seq[MSlot] | ||
| 35 | + of true: | ||
| 36 | + kvs*: seq[MEntry] | ||
| 37 | + | ||
| 38 | + PMap* = object | ||
| 39 | + root*: MNode | ||
| 40 | + cnt*, nextOrd*: int | ||
| 41 | + | ||
| 42 | + RecipeKind* = enum rkRange, rkMap, rkFilter | ||
| 43 | + Recipe* = ref object | ||
| 44 | + case rk*: RecipeKind | ||
| 45 | + of rkRange: | ||
| 46 | + lo*, hi*, step*: int64 | ||
| 47 | + bounded*: bool | ||
| 48 | + of rkMap, rkFilter: | ||
| 49 | + rfn*: Value | ||
| 50 | + keep*: bool | ||
| 51 | + src*: Value | ||
| 52 | + | ||
| 53 | + Obj* = ref ValueObj | ||
| 54 | + ValueObj* = object | ||
| 55 | + case kind*: Kind | ||
| 56 | + of kNil, kBool, kInt, kFloat: discard | ||
| 57 | + of kStr, kKeyword, kSymbol: s*: string | ||
| 58 | + of kList: xs*: seq[Value] | ||
| 59 | + of kVector: vec*: PVec | ||
| 60 | + of kMap, kSet: m*: PMap | ||
| 61 | + of kCons: | ||
| 62 | + head*, tl*: Value | ||
| 63 | + of kChunk: | ||
| 64 | + chunk*: seq[Value] | ||
| 65 | + coff*: int | ||
| 66 | + ctl*: Value | ||
| 67 | + of kLazy: | ||
| 68 | + thunk*: proc (): Value {.closure.} | ||
| 69 | + cached*: Value | ||
| 70 | + forced*: bool | ||
| 71 | + rec*: Recipe | ||
| 72 | + of kFn: | ||
| 73 | + fn*: proc (args: openArray[Value]): Value {.closure.} | ||
| 74 | + name*: string | ||
| 75 | + | ||
| 76 | + Value* = object | ||
| 77 | + kind*: Kind | ||
| 78 | + raw*: int64 | ||
| 79 | + obj*: Obj | ||
| 80 | + | ||
| 81 | + CljError* = object of CatchableError | ||
| 82 | + | ||
| 83 | + VarCell* = ref object | ||
| 84 | + name*: string | ||
| 85 | + bound*: bool | ||
| 86 | + v*: Value | ||
| 87 | + | ||
| 88 | + FusedOp* = object | ||
| 89 | + isMap*: bool | ||
| 90 | + fn*: Value | ||
| 91 | + keep*: bool | ||
| 92 | + | ||
| 93 | +proc i*(v: Value): int64 {.inline.} = v.raw | ||
| 94 | +proc b*(v: Value): bool {.inline.} = v.raw != 0 | ||
| 95 | +proc f*(v: Value): float64 {.inline.} = cast[float64](v.raw) | ||
| 96 | +proc s*(v: Value): lent string {.inline.} = v.obj.s | ||
| 97 | +proc xs*(v: Value): lent seq[Value] {.inline.} = v.obj.xs | ||
| 98 | +proc vec*(v: Value): lent PVec {.inline.} = v.obj.vec | ||
| 99 | +proc m*(v: Value): lent PMap {.inline.} = v.obj.m | ||
| 100 | +proc head*(v: Value): lent Value {.inline.} = v.obj.head | ||
| 101 | +proc tl*(v: Value): lent Value {.inline.} = v.obj.tl | ||
| 102 | +proc chunk*(v: Value): lent seq[Value] {.inline.} = v.obj.chunk | ||
| 103 | +proc coff*(v: Value): int {.inline.} = v.obj.coff | ||
| 104 | +proc ctl*(v: Value): lent Value {.inline.} = v.obj.ctl | ||
| 105 | +proc cached*(v: Value): lent Value {.inline.} = v.obj.cached | ||
| 106 | +proc forced*(v: Value): bool {.inline.} = v.obj.forced | ||
| 107 | +proc thunk*(v: Value): auto {.inline.} = v.obj.thunk | ||
| 108 | +proc fn*(v: Value): auto {.inline.} = v.obj.fn | ||
| 109 | +proc name*(v: Value): lent string {.inline.} = v.obj.name | ||
| 110 | +proc isNil*(v: Value): bool {.inline.} = v.kind == kNil | ||
| 111 | + | ||
| 112 | +var pendingFree: seq[Value] = @[] | ||
| 113 | +var draining = false | ||
| 114 | + | ||
| 115 | +proc `=destroy`*(x: var ValueObj) = | ||
| 116 | + case x.kind | ||
| 117 | + of kStr, kKeyword, kSymbol: `=destroy`(x.s) | ||
| 118 | + of kList: `=destroy`(x.xs) | ||
| 119 | + of kVector: `=destroy`(x.vec) | ||
| 120 | + of kMap, kSet: `=destroy`(x.m) | ||
| 121 | + of kFn: | ||
| 122 | + `=destroy`(x.fn) | ||
| 123 | + `=destroy`(x.name) | ||
| 124 | + of kCons: | ||
| 125 | + `=destroy`(x.head) | ||
| 126 | + if not x.tl.isNil: pendingFree.add x.tl | ||
| 127 | + `=destroy`(x.tl) | ||
| 128 | + of kChunk: | ||
| 129 | + `=destroy`(x.chunk) | ||
| 130 | + if not x.ctl.isNil: pendingFree.add x.ctl | ||
| 131 | + `=destroy`(x.ctl) | ||
| 132 | + of kLazy: | ||
| 133 | + `=destroy`(x.thunk) | ||
| 134 | + if not x.cached.isNil: pendingFree.add x.cached | ||
| 135 | + `=destroy`(x.cached) | ||
| 136 | + of kNil, kBool, kInt, kFloat: discard | ||
| 137 | + if draining: return | ||
| 138 | + draining = true | ||
| 139 | + while pendingFree.len > 0: | ||
| 140 | + let v = pendingFree.pop() | ||
| 141 | + discard v | ||
| 142 | + draining = false | ||
| 143 | + | ||
| 144 | +let NilV* = Value(kind: kNil) | ||
| 145 | +let TrueV* = Value(kind: kBool, raw: 1) | ||
| 146 | +let FalseV* = Value(kind: kBool, raw: 0) | ||
| 147 | +let emptyArgs*: array[0, Value] = [] | ||