commit 19b0454b3f280a5fe59e925a90c8f4517f5d21c5 Author: sanjin Date: Fri May 22 08:38:43 2026 +0200 Import Slovo 1.0.0-beta monorepo diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..79cf6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +# Rust +/target/ +compiler/target/ + +# Build outputs +/out/ +/build/ +*.ll +!tests/*.ll +*.bc +*.o +*.s + +# Benchmark and local runtime artifacts +benchmarks/**/build/ +compiler/glagol-std-layout-local-fs-alpha.txt +__pycache__/ +*.pyc + +# Editor and OS files +.DS_Store +.vscode/ +.idea/ +*.swp +*.swo diff --git a/.llm/MONOREPO_1_0_0_BETA.md b/.llm/MONOREPO_1_0_0_BETA.md new file mode 100644 index 0000000..d532bf1 --- /dev/null +++ b/.llm/MONOREPO_1_0_0_BETA.md @@ -0,0 +1,39 @@ +# Slovo Monorepo 1.0.0-beta + +Date: 2026-05-22 + +Status: public monorepo baseline prepared from the paired Slovo design and +Glagol compiler beta repositories. + +## Purpose + +Make `Hermeticum/slovo` the canonical repository for: + +- the Slovo language contract +- Glagol compiler implementation +- source-authored standard library +- runtime, examples, benchmarks, and publication documents + +## Layout Contract + +- language design docs: `docs/language/` +- compiler docs: `docs/compiler/` +- whitepapers and generated PDFs: `docs/papers/` +- standard library source: `lib/std/` +- compiler source and tests: `compiler/` +- hosted runtime: `runtime/` +- examples and benchmark harnesses: `examples/` and `benchmarks/` + +## Beta Boundary + +`1.0.0-beta` is a real general-purpose beta, not a stable release. It supports +ordinary local command-line tools and libraries inside the documented beta +surface. It does not claim stable ABI/layout, generic collections, remote +registries, networking/async, LSP/watch/debug-adapter support, or frozen stable +standard-library APIs. + +## Publication Rule + +The public repository must not contain machine-local paths, private checkout +names, or generated PDFs that drift after regeneration. The release gate must +fail if tracked PDFs change after `scripts/render-doc-pdfs.sh`. diff --git a/.llm/ROADMAP_TO_STABLE.md b/.llm/ROADMAP_TO_STABLE.md new file mode 100644 index 0000000..dde53ed --- /dev/null +++ b/.llm/ROADMAP_TO_STABLE.md @@ -0,0 +1,23 @@ +# Roadmap From 1.0.0-beta To Stable + +## Stable Criteria + +Slovo reaches stable `1.0.0` only after: + +- compatibility rules are documented and exercised across multiple beta releases +- formatter and diagnostics schemas are frozen for promoted features +- `lib/std` has clear API stability tiers +- package/workspace behavior has deterministic version and dependency policy +- conformance tests cover normal user projects, not only narrow fixtures +- benchmark publication remains repeatable without becoming a performance claim + +## Recommended Sequence + +1. Harden monorepo release tooling, clean docs, and public install/build flow. +2. Stabilize `lib/std` module boundaries and document beta-vs-stable APIs. +3. Broaden numeric completeness with explicit `f32` and additional integer + policy where needed. +4. Improve collection and string breadth without exposing unstable ABI details. +5. Expand package/workspace discipline before remote registry work. +6. Add editor-facing diagnostics and generated documentation improvements. +7. Freeze migration/deprecation policy and cut stable only after beta feedback. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..bf07c02 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,31 @@ +# Contributing + +Slovo is currently in `1.0.0-beta`. Changes should preserve the beta support +boundary: a feature is supported only when the language docs, compiler, +formatter, diagnostics, examples, tests, and release notes agree. + +## Local Checks + +From the repository root: + +```bash +cargo fmt --manifest-path compiler/Cargo.toml --check +cargo test --manifest-path compiler/Cargo.toml +./scripts/release-gate.sh +``` + +`./scripts/release-gate.sh` regenerates publication PDFs and fails if tracked +PDFs drift after regeneration. + +## Standard Library + +Source-authored standard-library modules live in `lib/std`. Keep imports +explicit and keep compiler/runtime support aligned with the promoted language +contract. + +## Release Discipline + +- `exp-*` labels are historical experimental milestones. +- `1.0.0-beta` is the first real general-purpose beta. +- `1.0.0` should wait for compatibility hardening, conformance coverage, and + stable standard-library policy. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6f949d7 --- /dev/null +++ b/LICENSE @@ -0,0 +1,10 @@ +Slovo is distributed under the terms of both the MIT license and the Apache +License (Version 2.0). + +Copyright (c) 2026 Sanjin Gumbarevic. + +You may use this project under either license, at your option. + +SPDX-License-Identifier: MIT OR Apache-2.0 + +See LICENSE-MIT and LICENSE-APACHE for full license text. diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..13736e9 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,186 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the +copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other +entities that control, are controlled by, or are under common control with +that entity. For the purposes of this definition, "control" means (i) the +power, direct or indirect, to cause the direction or management of such +entity, whether by contract or otherwise, or (ii) ownership of fifty percent +(50%) or more of the outstanding shares, or (iii) beneficial ownership of +such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising +permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, +including but not limited to software source code, documentation source, and +configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object +code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, +made available under the License, as indicated by a copyright notice that is +included in or attached to the work (an example is provided in the Appendix +below). + +"Derivative Works" shall mean any work, whether in Source or Object form, +that is based on (or derived from) the Work and for which the editorial +revisions, annotations, elaborations, or other modifications represent, as a +whole, an original work of authorship. For the purposes of this License, +Derivative Works shall not include works that remain separable from, or +merely link (or bind by name) to the interfaces of, the Work and Derivative +Works thereof. + +"Contribution" shall mean any work of authorship, including the original +version of the Work and any modifications or additions to that Work or +Derivative Works thereof, that is intentionally submitted to Licensor for +inclusion in the Work by the copyright owner or by an individual or Legal +Entity authorized to submit on behalf of the copyright owner. For the +purposes of this definition, "submitted" means any form of electronic, verbal, +or written communication sent to the Licensor or its representatives, +including but not limited to communication on electronic mailing lists, source +code control systems, and issue tracking systems that are managed by, or on +behalf of, the Licensor for the purpose of discussing and improving the Work, +but excluding communication that is conspicuously marked or otherwise +designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on +behalf of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this +License, each Contributor hereby grants to You a perpetual, worldwide, +non-exclusive, no-charge, royalty-free, irrevocable copyright license to +reproduce, prepare Derivative Works of, publicly display, publicly perform, +sublicense, and distribute the Work and such Derivative Works in Source or +Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this +License, each Contributor hereby grants to You a perpetual, worldwide, +non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this +section) patent license to make, have made, use, offer to sell, sell, import, +and otherwise transfer the Work, where such license applies only to those +patent claims licensable by such Contributor that are necessarily infringed by +their Contribution(s) alone or by combination of their Contribution(s) with +the Work to which such Contribution(s) was submitted. If You institute patent +litigation against any entity (including a cross-claim or counterclaim in a +lawsuit) alleging that the Work or a Contribution incorporated within the Work +constitutes direct or contributory patent infringement, then any patent +licenses granted to You under this License for that Work shall terminate as +of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or +Derivative Works thereof in any medium, with or without modifications, and in +Source or Object form, provided that You meet the following conditions: + +(a) You must give any other recipients of the Work or Derivative Works a copy +of this License; and + +(b) You must cause any modified files to carry prominent notices stating that +You changed the files; and + +(c) You must retain, in the Source form of any Derivative Works that You +distribute, all copyright, patent, trademark, and attribution notices from the +Source form of the Work, excluding those notices that do not pertain to any +part of the Derivative Works; and + +(d) If the Work includes a "NOTICE" text file as part of its distribution, +then any Derivative Works that You distribute must include a readable copy of +the attribution notices contained within such NOTICE file, excluding those +notices that do not pertain to any part of the Derivative Works, in at least +one of the following places: within a NOTICE text file distributed as part of +the Derivative Works; within the Source form or documentation, if provided +along with the Derivative Works; or, within a display generated by the +Derivative Works, if and wherever such third-party notices normally appear. +The contents of the NOTICE file are for informational purposes only and do not +modify the License. You may add Your own attribution notices within +Derivative Works that You distribute, alongside or as an addendum to the +NOTICE text from the Work, provided that such additional attribution notices +cannot be construed as modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a +whole, provided Your use, reproduction, and distribution of the Work otherwise +complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any +Contribution intentionally submitted for inclusion in the Work by You to the +Licensor shall be under the terms and conditions of this License, without any +additional terms or conditions. Notwithstanding the above, nothing herein +shall supersede or modify the terms of any separate license agreement you may +have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, +trademarks, service marks, or product names of the Licensor, except as +required for reasonable and customary use in describing the origin of the Work +and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in +writing, Licensor provides the Work (and each Contributor provides its +Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied, including, without limitation, any warranties +or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A +PARTICULAR PURPOSE. You are solely responsible for determining the +appropriateness of using or redistributing the Work and assume any risks +associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in +tort (including negligence), contract, or otherwise, unless required by +applicable law (such as deliberate and grossly negligent acts) or agreed to in +writing, shall any Contributor be liable to You for damages, including any +direct, indirect, special, incidental, or consequential damages of any +character arising as a result of this License or out of the use or inability +to use the Work (including but not limited to damages for loss of goodwill, +work stoppage, computer failure or malfunction, or any and all other +commercial damages or losses), even if such Contributor has been advised of +the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work +or Derivative Works thereof, You may choose to offer, and charge a fee for, +acceptance of support, warranty, indemnity, or other liability obligations +and/or rights consistent with this License. However, in accepting such +obligations, You may act only on Your own behalf and on Your sole +responsibility, not on behalf of any other Contributor, and only if You agree +to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets "[]" replaced with your own +identifying information. Do not include the brackets. The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same "printed page" as the copyright notice for easier identification +within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..add2edd --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Sanjin Gumbarevic + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..36feabe --- /dev/null +++ b/README.md @@ -0,0 +1,88 @@ +# Slovo + +Slovo (ⰔⰎⰑⰂⰑ) is a typed structural programming language and toolchain. + +This repository is the canonical public monorepo for the language design, +standard library source, compiler, runtime, examples, benchmarks, and technical +documents. + +Current release: `1.0.0-beta`. + +## Repository Layout + +```text +compiler/ Glagol, the first Slovo compiler +runtime/ C runtime used by hosted native builds +lib/std/ source-authored Slovo standard-library facades +examples/ compiler-supported Slovo examples and projects +benchmarks/ local benchmark comparison harnesses +docs/language/ language manifest, specs, roadmap, and release notes +docs/compiler/ compiler manifest, roadmap, and release notes +docs/papers/ whitepapers and generated publication PDFs +scripts/ local release and document tooling +``` + +## Beta Scope + +`1.0.0-beta` supports practical local command-line programs and libraries with: + +- modules, explicit imports, packages, and local workspaces +- `new`, `check`, `fmt`, `test`, `doc`, and `build` +- `i32`, `i64`, `u32`, `u64`, `f64`, `bool`, `string`, and internal `unit` +- structs, enums, fixed arrays, concrete vectors, option/result families, and + current `match` +- explicit `std/*.slo` imports from `lib/std`, installed `share/slovo/std`, or + `SLOVO_STD_PATH` +- hosted native builds through LLVM IR, Clang, and `runtime/runtime.c` + +Still deferred before stable: generics, maps/sets, broad package registry +semantics, networking/async, LSP/watch/debug-adapter guarantees, stable ABI and +layout, and a stable standard-library compatibility freeze. + +## Build And Test + +```bash +cargo test --manifest-path compiler/Cargo.toml +``` + +Run the full local release gate: + +```bash +./scripts/release-gate.sh +``` + +Build the compiler binary: + +```bash +cargo build --manifest-path compiler/Cargo.toml +./compiler/target/debug/glagol --version +``` + +Create and check a project: + +```bash +./compiler/target/debug/glagol new hello +SLOVO_STD_PATH="$PWD/lib/std" ./compiler/target/debug/glagol check hello +SLOVO_STD_PATH="$PWD/lib/std" ./compiler/target/debug/glagol test hello +``` + +Build a native executable when Clang is available: + +```bash +SLOVO_STD_PATH="$PWD/lib/std" ./compiler/target/debug/glagol build hello -o hello/bin +``` + +## Documentation + +- [Language Manifest](docs/language/MANIFEST.md) +- [Language Specification](docs/language/SPEC-v1.md) +- [Compiler Manifest](docs/compiler/GLAGOL_COMPILER_MANIFEST.md) +- [Slovo Whitepaper](docs/papers/SLOVO_WHITEPAPER.md) +- [Glagol Whitepaper](docs/papers/GLAGOL_WHITEPAPER.md) + +Generated PDFs live beside their Markdown sources in `docs/papers/`. + +## License + +Slovo is licensed under either the MIT License or the Apache License, Version +2.0, at your option. diff --git a/benchmarks/array-index-loop/.gitignore b/benchmarks/array-index-loop/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/benchmarks/array-index-loop/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/benchmarks/array-index-loop/README.md b/benchmarks/array-index-loop/README.md new file mode 100644 index 0000000..4d9c171 --- /dev/null +++ b/benchmarks/array-index-loop/README.md @@ -0,0 +1,72 @@ +# Array Index Loop Benchmark Scaffold + +Release: `exp-119`. + +This benchmark compares immutable fixed-array indexing plus scalar accumulation +across Slovo, C, Rust, Python, Clojure, and Common Lisp/SBCL on the same machine. + +It is not a published benchmark result, performance threshold, optimizer +claim, or cross-machine comparison. + +## Files + +- `src/main.slo`: Slovo project benchmark fixture +- `c/array_index_loop.c`: C comparison implementation +- `rust/array_index_loop.rs`: Rust comparison implementation +- `python/array_index_loop.py`: Python comparison implementation +- `clojure/array_index_loop.clj`: Clojure comparison implementation +- `common-lisp/array_index_loop.lisp`: Common Lisp/SBCL comparison implementation +- `run.py`: build/run/timing harness + +All implementations print checksum `3875007` for loop count `1000000`. +Hot-loop mode uses loop count `10000000` and checksum `38750007`. The runner +supplies the loop count at runtime so native compilers cannot fold the loop +into a constant answer. + +## Commands + +Run from the Glagol repository root: + +```bash +python3 benchmarks/array-index-loop/run.py --list +python3 benchmarks/array-index-loop/run.py --dry-run +python3 benchmarks/array-index-loop/run.py --only python --repeats 3 --warmups 1 +python3 benchmarks/array-index-loop/run.py --mode hot-loop --only slovo --only c --only rust +``` + +To include Slovo, build or point at a Glagol binary and make sure host Clang is +available: + +```bash +cargo build --manifest-path compiler/Cargo.toml --bin glagol +python3 benchmarks/array-index-loop/run.py --glagol compiler/target/debug/glagol +``` + +The runner skips missing C/Rust/Slovo/Clojure/SBCL toolchains where possible. +Use `--only` multiple times to select implementations: + +```bash +python3 benchmarks/array-index-loop/run.py --only slovo --only c --only rust --only clojure --only common_lisp +``` + +Clojure is detected with `clojure` on PATH, `CLOJURE`, or `CLOJURE_JAR`. +Common Lisp is detected with `sbcl` on PATH, `SBCL`, or `--sbcl`. + +## Comparison Method + +- The runner builds each implementation once before timing. The reported + numbers measure execution only, not compile time. +- Slovo timings use `glagol build`, which currently lowers to LLVM and then + invokes host `clang -O2` with `runtime/runtime.c`. +- C timings use `clang -O2 -std=c11`. +- Rust timings use `rustc -C opt-level=3 -C debuginfo=0`. +- The measured loop keeps one fixed immutable `i32` array in local scope, + indexes it with a loop-carried `% 8` position, and accumulates the selected + value into an `i32` checksum. + +Timing is cold-process local-machine evidence only. Clojure timings include +JVM and Clojure startup, while Common Lisp timings include SBCL script +startup. + +Hot-loop mode is startup-amortized local evidence. It runs a larger loop count +and reports total time plus normalized time for the base `1000000` loop count. diff --git a/benchmarks/array-index-loop/benchmark.json b/benchmarks/array-index-loop/benchmark.json new file mode 100644 index 0000000..a8ff589 --- /dev/null +++ b/benchmarks/array-index-loop/benchmark.json @@ -0,0 +1,10 @@ +{ + "benchmark": "array-index-loop", + "source_stem": "array_index_loop", + "loop_count": 1000000, + "expected_checksum": "3875007", + "stdin": "1000000\n", + "hot_loop_count": 10000000, + "hot_expected_checksum": "38750007", + "hot_stdin": "10000000\n" +} diff --git a/benchmarks/array-index-loop/c/array_index_loop.c b/benchmarks/array-index-loop/c/array_index_loop.c new file mode 100644 index 0000000..0f7050c --- /dev/null +++ b/benchmarks/array-index-loop/c/array_index_loop.c @@ -0,0 +1,33 @@ +#include +#include + +#define LOOP_COUNT 1000000 +#define EXPECTED_CHECKSUM 3875007 + +static int32_t configured_loop_count(void) { + int32_t value = LOOP_COUNT; + if (scanf("%d", &value) != 1 || value <= 0) { + return LOOP_COUNT; + } + return value; +} + +static int32_t array_index_loop(int32_t limit) { + static const int32_t digits[8] = {3, 1, 4, 1, 5, 9, 2, 6}; + int32_t i = 0; + int32_t acc = 7; + + while (i < limit) { + acc = acc + digits[i % 8]; + acc = acc > 1000000000 ? acc - 1000000000 : acc; + i = i + 1; + } + + return acc; +} + +int main(void) { + int32_t result = array_index_loop(configured_loop_count()); + printf("%d\n", result); + return result == EXPECTED_CHECKSUM ? 0 : 1; +} diff --git a/benchmarks/array-index-loop/clojure/array_index_loop.clj b/benchmarks/array-index-loop/clojure/array_index_loop.clj new file mode 100644 index 0000000..09f9fb4 --- /dev/null +++ b/benchmarks/array-index-loop/clojure/array_index_loop.clj @@ -0,0 +1,29 @@ +(set! *warn-on-reflection* true) +(set! *unchecked-math* :warn-on-boxed) + +(def loop-count 1000000) +(def expected-checksum 3875007) +(def digits [3 1 4 1 5 9 2 6]) + +(defn configured-loop-count [] + (try + (let [line (read-line) + value (Integer/parseInt (.trim ^String line))] + (if (pos? value) value loop-count)) + (catch Exception _ + loop-count))) + +(defn array-index-loop [limit] + (loop [i 0 + acc 7] + (if (< i limit) + (let [next (+ acc (nth digits (rem i 8))) + bounded (if (> next 1000000000) + (- next 1000000000) + next)] + (recur (inc i) bounded)) + acc))) + +(let [result (array-index-loop (configured-loop-count))] + (println result) + (System/exit (if (= result expected-checksum) 0 1))) diff --git a/benchmarks/array-index-loop/common-lisp/array_index_loop.lisp b/benchmarks/array-index-loop/common-lisp/array_index_loop.lisp new file mode 100644 index 0000000..02965e5 --- /dev/null +++ b/benchmarks/array-index-loop/common-lisp/array_index_loop.lisp @@ -0,0 +1,34 @@ +(declaim (optimize (speed 3) (safety 0) (debug 0))) + +(defconstant +loop-count+ 1000000) +(defconstant +expected-checksum+ 3875007) +(defparameter +digits+ #(3 1 4 1 5 9 2 6)) + +(declaim (ftype (function () fixnum) configured-loop-count)) +(defun configured-loop-count () + (handler-case + (let ((line (read-line *standard-input* nil nil))) + (if line + (let ((value (parse-integer line :junk-allowed t))) + (if (> value 0) value +loop-count+)) + +loop-count+)) + (error () +loop-count+))) + +(declaim (ftype (function (fixnum) fixnum) array-index-loop)) +(defun array-index-loop (limit) + (declare (type fixnum limit)) + (loop with i of-type fixnum = 0 + with acc of-type fixnum = 7 + while (< i limit) + do (let* ((next (+ acc (svref +digits+ (rem i 8)))) + (bounded (if (> next 1000000000) + (- next 1000000000) + next))) + (declare (type fixnum next bounded)) + (setf acc bounded + i (+ i 1))) + finally (return acc))) + +(let ((result (array-index-loop (configured-loop-count)))) + (format t "~D~%" result) + (sb-ext:exit :code (if (= result +expected-checksum+) 0 1))) diff --git a/benchmarks/array-index-loop/python/array_index_loop.py b/benchmarks/array-index-loop/python/array_index_loop.py new file mode 100644 index 0000000..a634f85 --- /dev/null +++ b/benchmarks/array-index-loop/python/array_index_loop.py @@ -0,0 +1,34 @@ +LOOP_COUNT = 1_000_000 +EXPECTED_CHECKSUM = 3_875_007 +DIGITS = [3, 1, 4, 1, 5, 9, 2, 6] + + +def configured_loop_count() -> int: + try: + value = int(input().strip()) + except (EOFError, ValueError): + return LOOP_COUNT + + return value if value > 0 else LOOP_COUNT + + +def array_index_loop(limit: int) -> int: + i = 0 + acc = 7 + + while i < limit: + acc += DIGITS[i % 8] + acc = acc - 1_000_000_000 if acc > 1_000_000_000 else acc + i += 1 + + return acc + + +def main() -> int: + result = array_index_loop(configured_loop_count()) + print(result) + return 0 if result == EXPECTED_CHECKSUM else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/benchmarks/array-index-loop/run.py b/benchmarks/array-index-loop/run.py new file mode 100644 index 0000000..cda86d6 --- /dev/null +++ b/benchmarks/array-index-loop/run.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +"""Run the local array-index-loop benchmark scaffold.""" + +from pathlib import Path +import sys + +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from runner import main + + +if __name__ == "__main__": + raise SystemExit(main(Path(__file__).resolve().parent, sys.argv[1:])) diff --git a/benchmarks/array-index-loop/rust/array_index_loop.rs b/benchmarks/array-index-loop/rust/array_index_loop.rs new file mode 100644 index 0000000..36842f5 --- /dev/null +++ b/benchmarks/array-index-loop/rust/array_index_loop.rs @@ -0,0 +1,40 @@ +const LOOP_COUNT: i32 = 1_000_000; +const EXPECTED_CHECKSUM: i32 = 3_875_007; +const DIGITS: [i32; 8] = [3, 1, 4, 1, 5, 9, 2, 6]; + +fn configured_loop_count() -> i32 { + let mut input = String::new(); + if std::io::stdin().read_line(&mut input).is_err() { + return LOOP_COUNT; + } + + input + .trim() + .parse::() + .ok() + .filter(|value| *value > 0) + .unwrap_or(LOOP_COUNT) +} + +fn array_index_loop(limit: i32) -> i32 { + let mut i = 0; + let mut acc = 7; + + while i < limit { + acc += DIGITS[(i % 8) as usize]; + acc = if acc > 1_000_000_000 { + acc - 1_000_000_000 + } else { + acc + }; + i += 1; + } + + acc +} + +fn main() { + let result = array_index_loop(configured_loop_count()); + println!("{}", result); + std::process::exit(if result == EXPECTED_CHECKSUM { 0 } else { 1 }); +} diff --git a/benchmarks/array-index-loop/slovo.toml b/benchmarks/array-index-loop/slovo.toml new file mode 100644 index 0000000..a0ea995 --- /dev/null +++ b/benchmarks/array-index-loop/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "array-index-loop" +source_root = "src" +entry = "main" diff --git a/benchmarks/array-index-loop/src/main.slo b/benchmarks/array-index-loop/src/main.slo new file mode 100644 index 0000000..4d810d4 --- /dev/null +++ b/benchmarks/array-index-loop/src/main.slo @@ -0,0 +1,60 @@ +; Benchmark scaffold fixture for local-machine array-index timing comparisons only. +; Keep LOOP_COUNT and EXPECTED_CHECKSUM aligned with the C/Rust/Python fixtures. +; The runner supplies the loop count through stdin or argv so native compilers +; cannot fold the benchmark loop into a constant answer. + +(module main) + +(fn loop_count () -> i32 + 1000000) + +(fn expected_checksum () -> i32 + 3875007) + +(fn parse_stdin_loop_count () -> (result i32 i32) + (let input (result string i32) (std.io.read_stdin_result)) + (match input + ((ok text) + (std.string.parse_i32_result text)) + ((err code) + (err i32 i32 code)))) + +(fn parse_arg_loop_count () -> (result i32 i32) + (std.string.parse_i32_result (std.process.arg 1))) + +(fn configured_stdin_loop_count () -> i32 + (let parsed_stdin (result i32 i32) (parse_stdin_loop_count)) + (if (is_ok parsed_stdin) + (unwrap_ok parsed_stdin) + (loop_count))) + +(fn configured_loop_count () -> i32 + (let parsed_arg (result i32 i32) (parse_arg_loop_count)) + (if (is_ok parsed_arg) + (unwrap_ok parsed_arg) + (configured_stdin_loop_count))) + +(fn values () -> (array i32 8) + (array i32 3 1 4 1 5 9 2 6)) + +(fn array_index_loop ((limit i32)) -> i32 + (let digits (array i32 8) (values)) + (var i i32 0) + (var acc i32 7) + (while (< i limit) + (set acc (+ acc (index digits (% i 8)))) + (set acc (if (> acc 1000000000) + (- acc 1000000000) + acc)) + (set i (+ i 1))) + acc) + +(fn main () -> i32 + (let result i32 (array_index_loop (configured_loop_count))) + (std.io.print_i32 result) + (if (= result (expected_checksum)) + 0 + 1)) + +(test "array index loop checksum is deterministic" + (= (array_index_loop (loop_count)) (expected_checksum))) diff --git a/benchmarks/array-struct-field-loop/.gitignore b/benchmarks/array-struct-field-loop/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/benchmarks/array-struct-field-loop/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/benchmarks/array-struct-field-loop/README.md b/benchmarks/array-struct-field-loop/README.md new file mode 100644 index 0000000..58378bb --- /dev/null +++ b/benchmarks/array-struct-field-loop/README.md @@ -0,0 +1,73 @@ +# Array Struct Field Loop Benchmark Scaffold + +Release: `exp-122`. + +This benchmark compares immutable fixed-array struct-field access plus scalar +accumulation across Slovo, C, Rust, Python, Clojure, and Common Lisp/SBCL on +the same machine. + +It is not a published benchmark result, performance threshold, optimizer +claim, or cross-machine comparison. + +## Files + +- `src/main.slo`: Slovo project benchmark fixture +- `c/array_struct_field_loop.c`: C comparison implementation +- `rust/array_struct_field_loop.rs`: Rust comparison implementation +- `python/array_struct_field_loop.py`: Python comparison implementation +- `clojure/array_struct_field_loop.clj`: Clojure comparison implementation +- `common-lisp/array_struct_field_loop.lisp`: Common Lisp/SBCL comparison implementation +- `run.py`: build/run/timing harness + +All implementations print checksum `3875011` for loop count `1000000`. +Hot-loop mode uses loop count `10000000` and checksum `38750011`. The runner +supplies the loop count at runtime so native compilers cannot fold the loop +into a constant answer. + +## Commands + +Run from the Glagol repository root: + +```bash +python3 benchmarks/array-struct-field-loop/run.py --list +python3 benchmarks/array-struct-field-loop/run.py --dry-run +python3 benchmarks/array-struct-field-loop/run.py --only python --repeats 3 --warmups 1 +python3 benchmarks/array-struct-field-loop/run.py --mode hot-loop --only slovo --only c --only rust +``` + +To include Slovo, build or point at a Glagol binary and make sure host Clang is +available: + +```bash +cargo build --manifest-path compiler/Cargo.toml --bin glagol +python3 benchmarks/array-struct-field-loop/run.py --glagol compiler/target/debug/glagol +``` + +The runner skips missing C/Rust/Slovo/Clojure/SBCL toolchains where possible. +Use `--only` multiple times to select implementations: + +```bash +python3 benchmarks/array-struct-field-loop/run.py --only slovo --only c --only rust --only clojure --only common_lisp +``` + +Clojure is detected with `clojure` on PATH, `CLOJURE`, or `CLOJURE_JAR`. +Common Lisp is detected with `sbcl` on PATH, `SBCL`, or `--sbcl`. + +## Comparison Method + +- The runner builds each implementation once before timing. The reported + numbers measure execution only, not compile time. +- Slovo timings use `glagol build`, which currently lowers to LLVM and then + invokes host `clang -O2` with `runtime/runtime.c`. +- C timings use `clang -O2 -std=c11`. +- Rust timings use `rustc -C opt-level=3 -C debuginfo=0`. +- The measured loop keeps one immutable struct value in local scope, indexes + the struct's fixed `i32` array field with a loop-carried `% 8` position, and + accumulates the selected value into an `i32` checksum. + +Timing is cold-process local-machine evidence only. Clojure timings include +JVM and Clojure startup, while Common Lisp timings include SBCL script +startup. + +Hot-loop mode is startup-amortized local evidence. It runs a larger loop count +and reports total time plus normalized time for the base `1000000` loop count. diff --git a/benchmarks/array-struct-field-loop/benchmark.json b/benchmarks/array-struct-field-loop/benchmark.json new file mode 100644 index 0000000..9e218cb --- /dev/null +++ b/benchmarks/array-struct-field-loop/benchmark.json @@ -0,0 +1,10 @@ +{ + "benchmark": "array-struct-field-loop", + "source_stem": "array_struct_field_loop", + "loop_count": 1000000, + "expected_checksum": "3875011", + "stdin": "1000000\n", + "hot_loop_count": 10000000, + "hot_expected_checksum": "38750011", + "hot_stdin": "10000000\n" +} diff --git a/benchmarks/array-struct-field-loop/c/array_struct_field_loop.c b/benchmarks/array-struct-field-loop/c/array_struct_field_loop.c new file mode 100644 index 0000000..adc0f2d --- /dev/null +++ b/benchmarks/array-struct-field-loop/c/array_struct_field_loop.c @@ -0,0 +1,41 @@ +#include +#include + +#define LOOP_COUNT 1000000 +#define EXPECTED_CHECKSUM 3875011 + +struct array_record { + int32_t digits[8]; +}; + +static int32_t configured_loop_count(void) { + int32_t value = LOOP_COUNT; + if (scanf("%d", &value) != 1 || value <= 0) { + return LOOP_COUNT; + } + return value; +} + +static struct array_record make_record(void) { + return (struct array_record){.digits = {3, 1, 4, 1, 5, 9, 2, 6}}; +} + +static int32_t array_struct_field_loop(int32_t limit) { + struct array_record record = make_record(); + int32_t i = 0; + int32_t acc = 11; + + while (i < limit) { + acc = acc + record.digits[i % 8]; + acc = acc > 1000000000 ? acc - 1000000000 : acc; + i = i + 1; + } + + return acc; +} + +int main(void) { + int32_t result = array_struct_field_loop(configured_loop_count()); + printf("%d\n", result); + return result == EXPECTED_CHECKSUM ? 0 : 1; +} diff --git a/benchmarks/array-struct-field-loop/clojure/array_struct_field_loop.clj b/benchmarks/array-struct-field-loop/clojure/array_struct_field_loop.clj new file mode 100644 index 0000000..35475be --- /dev/null +++ b/benchmarks/array-struct-field-loop/clojure/array_struct_field_loop.clj @@ -0,0 +1,29 @@ +(set! *warn-on-reflection* true) +(set! *unchecked-math* :warn-on-boxed) + +(def loop-count 1000000) +(def expected-checksum 3875011) +(def record {:digits [3 1 4 1 5 9 2 6]}) + +(defn configured-loop-count [] + (try + (let [line (read-line) + value (Integer/parseInt (.trim ^String line))] + (if (pos? value) value loop-count)) + (catch Exception _ + loop-count))) + +(defn array-struct-field-loop [limit] + (loop [i 0 + acc 11] + (if (< i limit) + (let [next (+ acc (nth (:digits record) (rem i 8))) + bounded (if (> next 1000000000) + (- next 1000000000) + next)] + (recur (inc i) bounded)) + acc))) + +(let [result (array-struct-field-loop (configured-loop-count))] + (println result) + (System/exit (if (= result expected-checksum) 0 1))) diff --git a/benchmarks/array-struct-field-loop/common-lisp/array_struct_field_loop.lisp b/benchmarks/array-struct-field-loop/common-lisp/array_struct_field_loop.lisp new file mode 100644 index 0000000..852dfff --- /dev/null +++ b/benchmarks/array-struct-field-loop/common-lisp/array_struct_field_loop.lisp @@ -0,0 +1,39 @@ +(declaim (optimize (speed 3) (safety 0) (debug 0))) + +(defconstant +loop-count+ 1000000) +(defconstant +expected-checksum+ 3875011) + +(defstruct array-record + (digits #(3 1 4 1 5 9 2 6) :type simple-vector)) + +(declaim (ftype (function () fixnum) configured-loop-count)) +(defun configured-loop-count () + (handler-case + (let ((line (read-line *standard-input* nil nil))) + (if line + (let ((value (parse-integer line :junk-allowed t))) + (if (> value 0) value +loop-count+)) + +loop-count+)) + (error () +loop-count+))) + +(declaim (ftype (function (fixnum) fixnum) array-struct-field-loop)) +(defun array-struct-field-loop (limit) + (declare (type fixnum limit)) + (let ((record (make-array-record))) + (loop with i of-type fixnum = 0 + with acc of-type fixnum = 11 + while (< i limit) + do (let* ((digits (array-record-digits record)) + (next (+ acc (svref digits (rem i 8)))) + (bounded (if (> next 1000000000) + (- next 1000000000) + next))) + (declare (type simple-vector digits) + (type fixnum next bounded)) + (setf acc bounded + i (+ i 1))) + finally (return acc)))) + +(let ((result (array-struct-field-loop (configured-loop-count)))) + (format t "~D~%" result) + (sb-ext:exit :code (if (= result +expected-checksum+) 0 1))) diff --git a/benchmarks/array-struct-field-loop/python/array_struct_field_loop.py b/benchmarks/array-struct-field-loop/python/array_struct_field_loop.py new file mode 100644 index 0000000..eb6b316 --- /dev/null +++ b/benchmarks/array-struct-field-loop/python/array_struct_field_loop.py @@ -0,0 +1,38 @@ +LOOP_COUNT = 1_000_000 +EXPECTED_CHECKSUM = 3_875_011 + + +def configured_loop_count() -> int: + try: + value = int(input().strip()) + except (EOFError, ValueError): + return LOOP_COUNT + + return value if value > 0 else LOOP_COUNT + + +def make_record() -> dict[str, list[int]]: + return {"digits": [3, 1, 4, 1, 5, 9, 2, 6]} + + +def array_struct_field_loop(limit: int) -> int: + record = make_record() + i = 0 + acc = 11 + + while i < limit: + acc += record["digits"][i % 8] + acc = acc - 1_000_000_000 if acc > 1_000_000_000 else acc + i += 1 + + return acc + + +def main() -> int: + result = array_struct_field_loop(configured_loop_count()) + print(result) + return 0 if result == EXPECTED_CHECKSUM else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/benchmarks/array-struct-field-loop/run.py b/benchmarks/array-struct-field-loop/run.py new file mode 100644 index 0000000..5becb5c --- /dev/null +++ b/benchmarks/array-struct-field-loop/run.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +"""Run the local array-struct-field-loop benchmark scaffold.""" + +from pathlib import Path +import sys + +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from runner import main + + +if __name__ == "__main__": + raise SystemExit(main(Path(__file__).resolve().parent, sys.argv[1:])) diff --git a/benchmarks/array-struct-field-loop/rust/array_struct_field_loop.rs b/benchmarks/array-struct-field-loop/rust/array_struct_field_loop.rs new file mode 100644 index 0000000..00337d1 --- /dev/null +++ b/benchmarks/array-struct-field-loop/rust/array_struct_field_loop.rs @@ -0,0 +1,50 @@ +const LOOP_COUNT: i32 = 1_000_000; +const EXPECTED_CHECKSUM: i32 = 3_875_011; + +struct ArrayRecord { + digits: [i32; 8], +} + +fn configured_loop_count() -> i32 { + let mut input = String::new(); + if std::io::stdin().read_line(&mut input).is_err() { + return LOOP_COUNT; + } + + input + .trim() + .parse::() + .ok() + .filter(|value| *value > 0) + .unwrap_or(LOOP_COUNT) +} + +fn make_record() -> ArrayRecord { + ArrayRecord { + digits: [3, 1, 4, 1, 5, 9, 2, 6], + } +} + +fn array_struct_field_loop(limit: i32) -> i32 { + let record = make_record(); + let mut i = 0; + let mut acc = 11; + + while i < limit { + acc += record.digits[(i % 8) as usize]; + acc = if acc > 1_000_000_000 { + acc - 1_000_000_000 + } else { + acc + }; + i += 1; + } + + acc +} + +fn main() { + let result = array_struct_field_loop(configured_loop_count()); + println!("{}", result); + std::process::exit(if result == EXPECTED_CHECKSUM { 0 } else { 1 }); +} diff --git a/benchmarks/array-struct-field-loop/slovo.toml b/benchmarks/array-struct-field-loop/slovo.toml new file mode 100644 index 0000000..ec1cf1f --- /dev/null +++ b/benchmarks/array-struct-field-loop/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "array-struct-field-loop" +source_root = "src" +entry = "main" diff --git a/benchmarks/array-struct-field-loop/src/main.slo b/benchmarks/array-struct-field-loop/src/main.slo new file mode 100644 index 0000000..d57389a --- /dev/null +++ b/benchmarks/array-struct-field-loop/src/main.slo @@ -0,0 +1,71 @@ +; Benchmark scaffold fixture for local-machine array-struct-field timing +; comparisons only. Keep LOOP_COUNT and EXPECTED_CHECKSUM aligned with the +; C/Rust/Python fixtures. The runner supplies the loop count through stdin or +; argv so native compilers cannot fold the benchmark loop into a constant +; answer. + +(module main) + +(struct ArrayRecord + (digits (array i32 8)) + (wides (array i64 2)) + (ratios (array f64 3)) + (flags (array bool 3)) + (words (array string 3))) + +(fn loop_count () -> i32 + 1000000) + +(fn expected_checksum () -> i32 + 3875011) + +(fn parse_stdin_loop_count () -> (result i32 i32) + (let input (result string i32) (std.io.read_stdin_result)) + (match input + ((ok text) + (std.string.parse_i32_result text)) + ((err code) + (err i32 i32 code)))) + +(fn parse_arg_loop_count () -> (result i32 i32) + (std.string.parse_i32_result (std.process.arg 1))) + +(fn configured_stdin_loop_count () -> i32 + (let parsed_stdin (result i32 i32) (parse_stdin_loop_count)) + (if (is_ok parsed_stdin) + (unwrap_ok parsed_stdin) + (loop_count))) + +(fn configured_loop_count () -> i32 + (let parsed_arg (result i32 i32) (parse_arg_loop_count)) + (if (is_ok parsed_arg) + (unwrap_ok parsed_arg) + (configured_stdin_loop_count))) + +(fn make_record () -> ArrayRecord + (ArrayRecord (digits (array i32 3 1 4 1 5 9 2 6)) (wides (array i64 40i64 41i64)) (ratios (array f64 1.5 2.5 3.5)) (flags (array bool false true true)) (words (array string "sun" "moon" "star")))) + +(fn int_at ((record ArrayRecord) (i i32)) -> i32 + (index (. record digits) i)) + +(fn array_struct_field_loop ((limit i32)) -> i32 + (let record ArrayRecord (make_record)) + (var i i32 0) + (var acc i32 11) + (while (< i limit) + (set acc (+ acc (int_at record (% i 8)))) + (set acc (if (> acc 1000000000) + (- acc 1000000000) + acc)) + (set i (+ i 1))) + acc) + +(fn main () -> i32 + (let result i32 (array_struct_field_loop (configured_loop_count))) + (std.io.print_i32 result) + (if (= result (expected_checksum)) + 0 + 1)) + +(test "array struct field loop checksum is deterministic" + (= (array_struct_field_loop (loop_count)) (expected_checksum))) diff --git a/benchmarks/branch-loop/.gitignore b/benchmarks/branch-loop/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/benchmarks/branch-loop/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/benchmarks/branch-loop/README.md b/benchmarks/branch-loop/README.md new file mode 100644 index 0000000..63def70 --- /dev/null +++ b/benchmarks/branch-loop/README.md @@ -0,0 +1,30 @@ +# Branch Loop Benchmark Scaffold + +Release: `exp-40`; Common Lisp/SBCL comparison added by `exp-41`; hot-loop +mode added by `exp-42`. + +This benchmark compares a deterministic branch-heavy integer loop across +Slovo, C, Rust, Python, Clojure, and Common Lisp/SBCL on the same machine. + +It is not a published benchmark result, performance threshold, optimizer +claim, or cross-machine comparison. + +All implementations print checksum `1185071` for loop count `1000000`. +Hot-loop mode uses loop count `10000000` and checksum `220775`. The runner +supplies the loop count at runtime. + +## Comparison Method + +- The runner builds each implementation once before timing. The reported + numbers measure execution only, not compile time. +- Slovo timings use `glagol build`, which currently lowers to LLVM and then + invokes host `clang -O2` with `runtime/runtime.c`. +- C timings use `clang -O2 -std=c11`. +- Rust timings use `rustc -C opt-level=3 -C debuginfo=0`. + +Timing is cold-process local-machine evidence only. Clojure timings include +JVM and Clojure startup, while Common Lisp timings include SBCL script +startup. + +Hot-loop mode is startup-amortized local evidence. It reports total time plus +normalized time for the base `1000000` loop count. diff --git a/benchmarks/branch-loop/benchmark.json b/benchmarks/branch-loop/benchmark.json new file mode 100644 index 0000000..c420fc3 --- /dev/null +++ b/benchmarks/branch-loop/benchmark.json @@ -0,0 +1,10 @@ +{ + "benchmark": "branch-loop", + "source_stem": "branch_loop", + "loop_count": 1000000, + "expected_checksum": "1185071", + "stdin": "1000000\n", + "hot_loop_count": 10000000, + "hot_expected_checksum": "220775", + "hot_stdin": "10000000\n" +} diff --git a/benchmarks/branch-loop/c/branch_loop.c b/benchmarks/branch-loop/c/branch_loop.c new file mode 100644 index 0000000..1e50d74 --- /dev/null +++ b/benchmarks/branch-loop/c/branch_loop.c @@ -0,0 +1,33 @@ +#include +#include + +#define LOOP_COUNT 1000000 +#define EXPECTED_CHECKSUM 1185071 + +static int32_t configured_loop_count(void) { + int32_t value = LOOP_COUNT; + if (scanf("%d", &value) != 1 || value <= 0) { + return LOOP_COUNT; + } + return value; +} + +static int32_t branch_loop(int32_t limit) { + int32_t i = 0; + int32_t acc = 7; + + while (i < limit) { + acc = acc > 1000000000 ? acc - 1000000000 : acc; + acc = i < 500000 ? acc + 3 : acc + 7; + acc = acc > 1234567 ? acc - 1234567 : acc + 11; + i = i + 1; + } + + return acc; +} + +int main(void) { + int32_t result = branch_loop(configured_loop_count()); + printf("%d\n", result); + return result == EXPECTED_CHECKSUM ? 0 : 1; +} diff --git a/benchmarks/branch-loop/clojure/branch_loop.clj b/benchmarks/branch-loop/clojure/branch_loop.clj new file mode 100644 index 0000000..e665ad5 --- /dev/null +++ b/benchmarks/branch-loop/clojure/branch_loop.clj @@ -0,0 +1,33 @@ +(set! *warn-on-reflection* true) +(set! *unchecked-math* :warn-on-boxed) + +(def loop-count 1000000) +(def expected-checksum 1185071) + +(defn configured-loop-count [] + (try + (let [line (read-line) + value (Integer/parseInt (.trim ^String line))] + (if (pos? value) value loop-count)) + (catch Exception _ + loop-count))) + +(defn branch-loop [limit] + (loop [i 0 + acc 7] + (if (< i limit) + (let [bounded (if (> acc 1000000000) + (- acc 1000000000) + acc) + branched (if (< i 500000) + (+ bounded 3) + (+ bounded 7)) + next (if (> branched 1234567) + (- branched 1234567) + (+ branched 11))] + (recur (inc i) next)) + acc))) + +(let [result (branch-loop (configured-loop-count))] + (println result) + (System/exit (if (= result expected-checksum) 0 1))) diff --git a/benchmarks/branch-loop/common-lisp/branch_loop.lisp b/benchmarks/branch-loop/common-lisp/branch_loop.lisp new file mode 100644 index 0000000..af7e8d8 --- /dev/null +++ b/benchmarks/branch-loop/common-lisp/branch_loop.lisp @@ -0,0 +1,38 @@ +(declaim (optimize (speed 3) (safety 0) (debug 0))) + +(defconstant +loop-count+ 1000000) +(defconstant +expected-checksum+ 1185071) + +(declaim (ftype (function () fixnum) configured-loop-count)) +(defun configured-loop-count () + (handler-case + (let ((line (read-line *standard-input* nil nil))) + (if line + (let ((value (parse-integer line :junk-allowed t))) + (if (> value 0) value +loop-count+)) + +loop-count+)) + (error () +loop-count+))) + +(declaim (ftype (function (fixnum) fixnum) branch-loop)) +(defun branch-loop (limit) + (declare (type fixnum limit)) + (loop with i of-type fixnum = 0 + with acc of-type fixnum = 7 + while (< i limit) + do (let* ((bounded (if (> acc 1000000000) + (- acc 1000000000) + acc)) + (branched (if (< i 500000) + (+ bounded 3) + (+ bounded 7))) + (next (if (> branched 1234567) + (- branched 1234567) + (+ branched 11)))) + (declare (type fixnum bounded branched next)) + (setf acc next + i (+ i 1))) + finally (return acc))) + +(let ((result (branch-loop (configured-loop-count)))) + (format t "~D~%" result) + (sb-ext:exit :code (if (= result +expected-checksum+) 0 1))) diff --git a/benchmarks/branch-loop/python/branch_loop.py b/benchmarks/branch-loop/python/branch_loop.py new file mode 100644 index 0000000..c0d9239 --- /dev/null +++ b/benchmarks/branch-loop/python/branch_loop.py @@ -0,0 +1,34 @@ +LOOP_COUNT = 1_000_000 +EXPECTED_CHECKSUM = 1_185_071 + + +def configured_loop_count() -> int: + try: + value = int(input().strip()) + except (EOFError, ValueError): + return LOOP_COUNT + + return value if value > 0 else LOOP_COUNT + + +def branch_loop(limit: int) -> int: + i = 0 + acc = 7 + + while i < limit: + acc = acc - 1_000_000_000 if acc > 1_000_000_000 else acc + acc = acc + 3 if i < 500_000 else acc + 7 + acc = acc - 1_234_567 if acc > 1_234_567 else acc + 11 + i += 1 + + return acc + + +def main() -> int: + result = branch_loop(configured_loop_count()) + print(result) + return 0 if result == EXPECTED_CHECKSUM else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/benchmarks/branch-loop/run.py b/benchmarks/branch-loop/run.py new file mode 100644 index 0000000..f3bab65 --- /dev/null +++ b/benchmarks/branch-loop/run.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +"""Run the local branch-loop benchmark scaffold.""" + +from pathlib import Path +import sys + +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from runner import main + + +if __name__ == "__main__": + raise SystemExit(main(Path(__file__).resolve().parent, sys.argv[1:])) diff --git a/benchmarks/branch-loop/rust/branch_loop.rs b/benchmarks/branch-loop/rust/branch_loop.rs new file mode 100644 index 0000000..6a44634 --- /dev/null +++ b/benchmarks/branch-loop/rust/branch_loop.rs @@ -0,0 +1,44 @@ +const LOOP_COUNT: i32 = 1_000_000; +const EXPECTED_CHECKSUM: i32 = 1_185_071; + +fn configured_loop_count() -> i32 { + let mut input = String::new(); + if std::io::stdin().read_line(&mut input).is_err() { + return LOOP_COUNT; + } + + input + .trim() + .parse::() + .ok() + .filter(|value| *value > 0) + .unwrap_or(LOOP_COUNT) +} + +fn branch_loop(limit: i32) -> i32 { + let mut i = 0; + let mut acc = 7; + + while i < limit { + acc = if acc > 1_000_000_000 { + acc - 1_000_000_000 + } else { + acc + }; + acc = if i < 500_000 { acc + 3 } else { acc + 7 }; + acc = if acc > 1_234_567 { + acc - 1_234_567 + } else { + acc + 11 + }; + i += 1; + } + + acc +} + +fn main() { + let result = branch_loop(configured_loop_count()); + println!("{}", result); + std::process::exit(if result == EXPECTED_CHECKSUM { 0 } else { 1 }); +} diff --git a/benchmarks/branch-loop/slovo.toml b/benchmarks/branch-loop/slovo.toml new file mode 100644 index 0000000..ae60ff5 --- /dev/null +++ b/benchmarks/branch-loop/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "branch-loop" +source_root = "src" +entry = "main" diff --git a/benchmarks/branch-loop/src/main.slo b/benchmarks/branch-loop/src/main.slo new file mode 100644 index 0000000..0e4237d --- /dev/null +++ b/benchmarks/branch-loop/src/main.slo @@ -0,0 +1,58 @@ +; Benchmark scaffold fixture for local-machine branch timing comparisons only. + +(module main) + +(fn loop_count () -> i32 + 1000000) + +(fn expected_checksum () -> i32 + 1185071) + +(fn parse_stdin_loop_count () -> (result i32 i32) + (let input (result string i32) (std.io.read_stdin_result)) + (match input + ((ok text) + (std.string.parse_i32_result text)) + ((err code) + (err i32 i32 code)))) + +(fn parse_arg_loop_count () -> (result i32 i32) + (std.string.parse_i32_result (std.process.arg 1))) + +(fn configured_stdin_loop_count () -> i32 + (let parsed_stdin (result i32 i32) (parse_stdin_loop_count)) + (if (is_ok parsed_stdin) + (unwrap_ok parsed_stdin) + (loop_count))) + +(fn configured_loop_count () -> i32 + (let parsed_arg (result i32 i32) (parse_arg_loop_count)) + (if (is_ok parsed_arg) + (unwrap_ok parsed_arg) + (configured_stdin_loop_count))) + +(fn branch_loop ((limit i32)) -> i32 + (var i i32 0) + (var acc i32 7) + (while (< i limit) + (set acc (if (> acc 1000000000) + (- acc 1000000000) + acc)) + (set acc (if (< i 500000) + (+ acc 3) + (+ acc 7))) + (set acc (if (> acc 1234567) + (- acc 1234567) + (+ acc 11))) + (set i (+ i 1))) + acc) + +(fn main () -> i32 + (let result i32 (branch_loop (configured_loop_count))) + (std.io.print_i32 result) + (if (= result (expected_checksum)) + 0 + 1)) + +(test "branch loop checksum is deterministic" + (= (branch_loop (loop_count)) (expected_checksum))) diff --git a/benchmarks/enum-struct-payload-loop/.gitignore b/benchmarks/enum-struct-payload-loop/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/benchmarks/enum-struct-payload-loop/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/benchmarks/enum-struct-payload-loop/README.md b/benchmarks/enum-struct-payload-loop/README.md new file mode 100644 index 0000000..ce9d1f9 --- /dev/null +++ b/benchmarks/enum-struct-payload-loop/README.md @@ -0,0 +1,73 @@ +# Enum Struct Payload Loop Benchmark Scaffold + +Release: `exp-122`. + +This benchmark compares enum payload selection, enum `match`, and immutable +fixed-array access through a struct payload plus scalar accumulation across +Slovo, C, Rust, Python, Clojure, and Common Lisp/SBCL on the same machine. + +It is not a published benchmark result, performance threshold, optimizer +claim, or cross-machine comparison. + +## Files + +- `src/main.slo`: Slovo project benchmark fixture +- `c/enum_struct_payload_loop.c`: C comparison implementation +- `rust/enum_struct_payload_loop.rs`: Rust comparison implementation +- `python/enum_struct_payload_loop.py`: Python comparison implementation +- `clojure/enum_struct_payload_loop.clj`: Clojure comparison implementation +- `common-lisp/enum_struct_payload_loop.lisp`: Common Lisp/SBCL comparison implementation +- `run.py`: build/run/timing harness + +All implementations print checksum `3500013` for loop count `1000000`. +Hot-loop mode uses loop count `10000000` and checksum `35000013`. The runner +supplies the loop count at runtime so native compilers cannot fold the loop +into a constant answer. + +## Commands + +Run from the Glagol repository root: + +```bash +python3 benchmarks/enum-struct-payload-loop/run.py --list +python3 benchmarks/enum-struct-payload-loop/run.py --dry-run +python3 benchmarks/enum-struct-payload-loop/run.py --only python --repeats 3 --warmups 1 +python3 benchmarks/enum-struct-payload-loop/run.py --mode hot-loop --only slovo --only c --only rust +``` + +To include Slovo, build or point at a Glagol binary and make sure host Clang is +available: + +```bash +cargo build --manifest-path compiler/Cargo.toml --bin glagol +python3 benchmarks/enum-struct-payload-loop/run.py --glagol compiler/target/debug/glagol +``` + +The runner skips missing C/Rust/Slovo/Clojure/SBCL toolchains where possible. +Use `--only` multiple times to select implementations: + +```bash +python3 benchmarks/enum-struct-payload-loop/run.py --only slovo --only c --only rust --only clojure --only common_lisp +``` + +Clojure is detected with `clojure` on PATH, `CLOJURE`, or `CLOJURE_JAR`. +Common Lisp is detected with `sbcl` on PATH, `SBCL`, or `--sbcl`. + +## Comparison Method + +- The runner builds each implementation once before timing. The reported + numbers measure execution only, not compile time. +- Slovo timings use `glagol build`, which currently lowers to LLVM and then + invokes host `clang -O2` with `runtime/runtime.c`. +- C timings use `clang -O2 -std=c11`. +- Rust timings use `rustc -C opt-level=3 -C debuginfo=0`. +- The measured loop alternates between two enum payload variants, matches the + selected variant, reads one fixed `i32` array through the bound struct + payload, and accumulates the selected value into an `i32` checksum. + +Timing is cold-process local-machine evidence only. Clojure timings include +JVM and Clojure startup, while Common Lisp timings include SBCL script +startup. + +Hot-loop mode is startup-amortized local evidence. It runs a larger loop count +and reports total time plus normalized time for the base `1000000` loop count. diff --git a/benchmarks/enum-struct-payload-loop/benchmark.json b/benchmarks/enum-struct-payload-loop/benchmark.json new file mode 100644 index 0000000..39ff04b --- /dev/null +++ b/benchmarks/enum-struct-payload-loop/benchmark.json @@ -0,0 +1,10 @@ +{ + "benchmark": "enum-struct-payload-loop", + "source_stem": "enum_struct_payload_loop", + "loop_count": 1000000, + "expected_checksum": "3500013", + "stdin": "1000000\n", + "hot_loop_count": 10000000, + "hot_expected_checksum": "35000013", + "hot_stdin": "10000000\n" +} diff --git a/benchmarks/enum-struct-payload-loop/c/enum_struct_payload_loop.c b/benchmarks/enum-struct-payload-loop/c/enum_struct_payload_loop.c new file mode 100644 index 0000000..f8b20ef --- /dev/null +++ b/benchmarks/enum-struct-payload-loop/c/enum_struct_payload_loop.c @@ -0,0 +1,75 @@ +#include +#include + +#define LOOP_COUNT 1000000 +#define EXPECTED_CHECKSUM 3500013 + +typedef struct { + int32_t digits[8]; +} Packet; + +typedef enum { + PACKET_MISSING = 0, + PACKET_LIVE = 1, + PACKET_CACHED = 2, +} PacketTag; + +typedef struct { + PacketTag tag; + Packet payload; +} PacketState; + +static int32_t configured_loop_count(void) { + int32_t value = LOOP_COUNT; + if (scanf("%d", &value) != 1 || value <= 0) { + return LOOP_COUNT; + } + return value; +} + +static PacketState live_state(void) { + PacketState state = {PACKET_LIVE, {{2, 7, 1, 8, 2, 8, 1, 8}}}; + return state; +} + +static PacketState cached_state(void) { + PacketState state = {PACKET_CACHED, {{1, 6, 1, 8, 0, 3, 4, 5}}}; + return state; +} + +static PacketState select_state(int32_t i, PacketState live, PacketState cached) { + return i % 2 == 0 ? live : cached; +} + +static int32_t state_digit(PacketState state, int32_t index) { + switch (state.tag) { + case PACKET_LIVE: + case PACKET_CACHED: + return state.payload.digits[index]; + case PACKET_MISSING: + default: + return 0; + } +} + +static int32_t enum_struct_payload_loop(int32_t limit) { + PacketState live = live_state(); + PacketState cached = cached_state(); + int32_t i = 0; + int32_t acc = 13; + + while (i < limit) { + PacketState state = select_state(i, live, cached); + acc = acc + state_digit(state, i % 8); + acc = acc > 1000000000 ? acc - 1000000000 : acc; + i = i + 1; + } + + return acc; +} + +int main(void) { + int32_t result = enum_struct_payload_loop(configured_loop_count()); + printf("%d\n", result); + return result == EXPECTED_CHECKSUM ? 0 : 1; +} diff --git a/benchmarks/enum-struct-payload-loop/clojure/enum_struct_payload_loop.clj b/benchmarks/enum-struct-payload-loop/clojure/enum_struct_payload_loop.clj new file mode 100644 index 0000000..ad3d134 --- /dev/null +++ b/benchmarks/enum-struct-payload-loop/clojure/enum_struct_payload_loop.clj @@ -0,0 +1,46 @@ +(set! *warn-on-reflection* true) +(set! *unchecked-math* :warn-on-boxed) + +(def loop-count 1000000) +(def expected-checksum 3500013) + +(defrecord Packet [digits]) + +(defn configured-loop-count [] + (try + (let [line (read-line) + value (Integer/parseInt (.trim ^String line))] + (if (pos? value) value loop-count)) + (catch Exception _ + loop-count))) + +(defn live-state [] + {:tag :live :payload (->Packet [2 7 1 8 2 8 1 8])}) + +(defn cached-state [] + {:tag :cached :payload (->Packet [1 6 1 8 0 3 4 5])}) + +(defn select-state [i live cached] + (if (zero? (rem i 2)) live cached)) + +(defn state-digit [state index] + (case (:tag state) + :missing 0 + (nth (:digits ^Packet (:payload state)) index))) + +(defn enum-struct-payload-loop [limit] + (loop [i 0 + acc 13 + live (live-state) + cached (cached-state)] + (if (< i limit) + (let [next (+ acc (state-digit (select-state i live cached) (rem i 8))) + bounded (if (> next 1000000000) + (- next 1000000000) + next)] + (recur (inc i) bounded live cached)) + acc))) + +(let [result (enum-struct-payload-loop (configured-loop-count))] + (println result) + (System/exit (if (= result expected-checksum) 0 1))) diff --git a/benchmarks/enum-struct-payload-loop/common-lisp/enum_struct_payload_loop.lisp b/benchmarks/enum-struct-payload-loop/common-lisp/enum_struct_payload_loop.lisp new file mode 100644 index 0000000..c031188 --- /dev/null +++ b/benchmarks/enum-struct-payload-loop/common-lisp/enum_struct_payload_loop.lisp @@ -0,0 +1,66 @@ +(declaim (optimize (speed 3) (safety 0) (debug 0))) + +(defconstant +loop-count+ 1000000) +(defconstant +expected-checksum+ 3500013) + +(defstruct (packet (:constructor %make-packet (digits))) digits) +(defstruct (packet-state (:constructor %make-packet-state (tag payload))) tag payload) + +(declaim (ftype (function () fixnum) configured-loop-count)) +(defun configured-loop-count () + (handler-case + (let ((line (read-line *standard-input* nil nil))) + (if line + (let ((value (parse-integer line :junk-allowed t))) + (if (> value 0) value +loop-count+)) + +loop-count+)) + (error () +loop-count+))) + +(declaim (ftype (function () packet-state) live-state)) +(defun live-state () + (%make-packet-state :live (%make-packet #(2 7 1 8 2 8 1 8)))) + +(declaim (ftype (function () packet-state) cached-state)) +(defun cached-state () + (%make-packet-state :cached (%make-packet #(1 6 1 8 0 3 4 5)))) + +(declaim (ftype (function (fixnum packet-state packet-state) packet-state) select-state)) +(defun select-state (i live cached) + (declare (type fixnum i)) + (if (zerop (rem i 2)) + live + cached)) + +(declaim (ftype (function (packet-state fixnum) fixnum) state-digit)) +(defun state-digit (state index) + (declare (type packet-state state) + (type fixnum index)) + (let* ((payload (packet-state-payload state)) + (digits (packet-digits payload)) + (value (svref digits index))) + (declare (type simple-vector digits) + (type fixnum value)) + (case (packet-state-tag state) + (:missing 0) + (otherwise value)))) + +(declaim (ftype (function (fixnum) fixnum) enum-struct-payload-loop)) +(defun enum-struct-payload-loop (limit) + (declare (type fixnum limit)) + (loop with i of-type fixnum = 0 + with acc of-type fixnum = 13 + with live of-type packet-state = (live-state) + with cached of-type packet-state = (cached-state) + while (< i limit) + do (let* ((next (+ acc (state-digit (select-state i live cached) (rem i 8)))) + (bounded (if (> next 1000000000) + (- next 1000000000) + next))) + (declare (type fixnum next bounded)) + (setf acc bounded + i (+ i 1))) + finally (return acc))) + +(let ((result (enum-struct-payload-loop (configured-loop-count)))) + (format t "~D~%" result) + (sb-ext:exit :code (if (= result +expected-checksum+) 0 1))) diff --git a/benchmarks/enum-struct-payload-loop/python/enum_struct_payload_loop.py b/benchmarks/enum-struct-payload-loop/python/enum_struct_payload_loop.py new file mode 100644 index 0000000..df3b379 --- /dev/null +++ b/benchmarks/enum-struct-payload-loop/python/enum_struct_payload_loop.py @@ -0,0 +1,50 @@ +LOOP_COUNT = 1_000_000 +EXPECTED_CHECKSUM = 3_500_013 + +LIVE_STATE = {"tag": "live", "payload": {"digits": [2, 7, 1, 8, 2, 8, 1, 8]}} +CACHED_STATE = {"tag": "cached", "payload": {"digits": [1, 6, 1, 8, 0, 3, 4, 5]}} + + +def configured_loop_count() -> int: + try: + value = int(input().strip()) + except (EOFError, ValueError): + return LOOP_COUNT + + return value if value > 0 else LOOP_COUNT + + +def select_state(i: int) -> dict[str, object]: + return LIVE_STATE if i % 2 == 0 else CACHED_STATE + + +def state_digit(state: dict[str, object], index: int) -> int: + if state["tag"] == "missing": + return 0 + payload = state["payload"] + assert isinstance(payload, dict) + digits = payload["digits"] + assert isinstance(digits, list) + return digits[index] + + +def enum_struct_payload_loop(limit: int) -> int: + i = 0 + acc = 13 + + while i < limit: + acc += state_digit(select_state(i), i % 8) + acc = acc - 1_000_000_000 if acc > 1_000_000_000 else acc + i += 1 + + return acc + + +def main() -> int: + result = enum_struct_payload_loop(configured_loop_count()) + print(result) + return 0 if result == EXPECTED_CHECKSUM else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/benchmarks/enum-struct-payload-loop/run.py b/benchmarks/enum-struct-payload-loop/run.py new file mode 100644 index 0000000..e0777dd --- /dev/null +++ b/benchmarks/enum-struct-payload-loop/run.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +"""Run the local enum-struct-payload-loop benchmark scaffold.""" + +from pathlib import Path +import sys + +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from runner import main + + +if __name__ == "__main__": + raise SystemExit(main(Path(__file__).resolve().parent, sys.argv[1:])) diff --git a/benchmarks/enum-struct-payload-loop/rust/enum_struct_payload_loop.rs b/benchmarks/enum-struct-payload-loop/rust/enum_struct_payload_loop.rs new file mode 100644 index 0000000..60c1f22 --- /dev/null +++ b/benchmarks/enum-struct-payload-loop/rust/enum_struct_payload_loop.rs @@ -0,0 +1,78 @@ +const LOOP_COUNT: i32 = 1_000_000; +const EXPECTED_CHECKSUM: i32 = 3_500_013; + +struct Packet { + digits: [i32; 8], +} + +enum PacketState { + Missing, + Live(Packet), + Cached(Packet), +} + +fn configured_loop_count() -> i32 { + let mut input = String::new(); + if std::io::stdin().read_line(&mut input).is_err() { + return LOOP_COUNT; + } + + input + .trim() + .parse::() + .ok() + .filter(|value| *value > 0) + .unwrap_or(LOOP_COUNT) +} + +fn live_state() -> PacketState { + PacketState::Live(Packet { + digits: [2, 7, 1, 8, 2, 8, 1, 8], + }) +} + +fn cached_state() -> PacketState { + PacketState::Cached(Packet { + digits: [1, 6, 1, 8, 0, 3, 4, 5], + }) +} + +fn select_state<'a>(i: i32, live: &'a PacketState, cached: &'a PacketState) -> &'a PacketState { + if i % 2 == 0 { + live + } else { + cached + } +} + +fn state_digit(state: &PacketState, index: i32) -> i32 { + match state { + PacketState::Missing => 0, + PacketState::Live(payload) | PacketState::Cached(payload) => payload.digits[index as usize], + } +} + +fn enum_struct_payload_loop(limit: i32) -> i32 { + let live = live_state(); + let cached = cached_state(); + let mut i = 0; + let mut acc = 13; + + while i < limit { + acc += state_digit(select_state(i, &live, &cached), i % 8); + acc = if acc > 1_000_000_000 { + acc - 1_000_000_000 + } else { + acc + }; + i += 1; + } + + acc +} + +fn main() { + let result = enum_struct_payload_loop(configured_loop_count()); + println!("{}", result); + std::process::exit(if result == EXPECTED_CHECKSUM { 0 } else { 1 }); +} diff --git a/benchmarks/enum-struct-payload-loop/slovo.toml b/benchmarks/enum-struct-payload-loop/slovo.toml new file mode 100644 index 0000000..28c31fe --- /dev/null +++ b/benchmarks/enum-struct-payload-loop/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "enum-struct-payload-loop" +source_root = "src" +entry = "main" diff --git a/benchmarks/enum-struct-payload-loop/src/main.slo b/benchmarks/enum-struct-payload-loop/src/main.slo new file mode 100644 index 0000000..d39429b --- /dev/null +++ b/benchmarks/enum-struct-payload-loop/src/main.slo @@ -0,0 +1,93 @@ +; Benchmark scaffold fixture for local-machine enum-struct-payload timing +; comparisons only. Keep LOOP_COUNT and EXPECTED_CHECKSUM aligned with the +; C/Rust/Python fixtures. The runner supplies the loop count through stdin or +; argv so native compilers cannot fold the benchmark loop into a constant +; answer. + +(module main) + +(struct Packet + (digits (array i32 8))) + +(enum PacketState + Missing + (Live Packet) + (Cached Packet)) + +(fn loop_count () -> i32 + 1000000) + +(fn expected_checksum () -> i32 + 3500013) + +(fn parse_stdin_loop_count () -> (result i32 i32) + (let input (result string i32) (std.io.read_stdin_result)) + (match input + ((ok text) + (std.string.parse_i32_result text)) + ((err code) + (err i32 i32 code)))) + +(fn parse_arg_loop_count () -> (result i32 i32) + (std.string.parse_i32_result (std.process.arg 1))) + +(fn configured_stdin_loop_count () -> i32 + (let parsed_stdin (result i32 i32) (parse_stdin_loop_count)) + (if (is_ok parsed_stdin) + (unwrap_ok parsed_stdin) + (loop_count))) + +(fn configured_loop_count () -> i32 + (let parsed_arg (result i32 i32) (parse_arg_loop_count)) + (if (is_ok parsed_arg) + (unwrap_ok parsed_arg) + (configured_stdin_loop_count))) + +(fn live_digits () -> (array i32 8) + (array i32 2 7 1 8 2 8 1 8)) + +(fn cached_digits () -> (array i32 8) + (array i32 1 6 1 8 0 3 4 5)) + +(fn live_state () -> PacketState + (PacketState.Live (Packet (digits (live_digits))))) + +(fn cached_state () -> PacketState + (PacketState.Cached (Packet (digits (cached_digits))))) + +(fn select_state ((i i32) (live PacketState) (cached PacketState)) -> PacketState + (if (= (% i 2) 0) + live + cached)) + +(fn state_digit ((state PacketState) (i i32)) -> i32 + (match state + ((PacketState.Missing) + 0) + ((PacketState.Live payload) + (index (. payload digits) i)) + ((PacketState.Cached payload) + (index (. payload digits) i)))) + +(fn enum_struct_payload_loop ((limit i32)) -> i32 + (let live PacketState (live_state)) + (let cached PacketState (cached_state)) + (var i i32 0) + (var acc i32 13) + (while (< i limit) + (set acc (+ acc (state_digit (select_state i live cached) (% i 8)))) + (set acc (if (> acc 1000000000) + (- acc 1000000000) + acc)) + (set i (+ i 1))) + acc) + +(fn main () -> i32 + (let result i32 (enum_struct_payload_loop (configured_loop_count))) + (std.io.print_i32 result) + (if (= result (expected_checksum)) + 0 + 1)) + +(test "enum struct payload loop checksum is deterministic" + (= (enum_struct_payload_loop (loop_count)) (expected_checksum))) diff --git a/benchmarks/math-loop/.gitignore b/benchmarks/math-loop/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/benchmarks/math-loop/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/benchmarks/math-loop/README.md b/benchmarks/math-loop/README.md new file mode 100644 index 0000000..37024f8 --- /dev/null +++ b/benchmarks/math-loop/README.md @@ -0,0 +1,72 @@ +# Math Loop Benchmark Scaffold + +Release: established by `exp-39`; Clojure comparison and shared runner +updated by `exp-40`; Common Lisp/SBCL comparison added by `exp-41`; +hot-loop mode added by `exp-42`. + +This directory is a local timing harness for comparing one deterministic +math-heavy loop across Slovo, C, Rust, Python, Clojure, and Common Lisp/SBCL +on the same machine. + +It is not a published benchmark result, performance threshold, optimizer +claim, or cross-machine comparison. + +## Files + +- `src/main.slo`: Slovo project benchmark fixture +- `c/math_loop.c`: C comparison implementation +- `rust/math_loop.rs`: Rust comparison implementation +- `python/math_loop.py`: Python comparison implementation +- `clojure/math_loop.clj`: Clojure comparison implementation +- `common-lisp/math_loop.lisp`: Common Lisp/SBCL comparison implementation +- `run.py`: build/run/timing harness + +All implementations print the same checksum, `5000001`, for the default loop +count, `1000000`. Hot-loop mode uses loop count `10000000` and checksum +`50000001`. The runner supplies the loop count at runtime so native compilers +cannot fold the loop into a constant answer. + +## Commands + +Run from the Glagol repository root: + +```bash +python3 benchmarks/math-loop/run.py --list +python3 benchmarks/math-loop/run.py --dry-run +python3 benchmarks/math-loop/run.py --only python --repeats 3 --warmups 1 +python3 benchmarks/math-loop/run.py --mode hot-loop --only slovo --only c --only rust +``` + +To include Slovo, build or point at a Glagol binary and make sure host Clang is +available: + +```bash +cargo build --manifest-path compiler/Cargo.toml --bin glagol +python3 benchmarks/math-loop/run.py --glagol compiler/target/debug/glagol +``` + +The runner skips missing C/Rust/Slovo/Clojure/SBCL toolchains where possible. +Use `--only` multiple times to select implementations: + +```bash +python3 benchmarks/math-loop/run.py --only slovo --only c --only rust --only clojure --only common_lisp +``` + +Clojure is detected with `clojure` on PATH, `CLOJURE`, or `CLOJURE_JAR`. +Common Lisp is detected with `sbcl` on PATH, `SBCL`, or `--sbcl`. + +## Comparison Method + +- The runner builds each implementation once before timing. The reported + numbers measure execution only, not compile time. +- Slovo timings use `glagol build`, which currently lowers to LLVM and then + invokes host `clang -O2` with `runtime/runtime.c`. +- C timings use `clang -O2 -std=c11`. +- Rust timings use `rustc -C opt-level=3 -C debuginfo=0`. + +Timing is cold-process local-machine evidence only. Clojure timings include +JVM and Clojure startup, while Common Lisp timings include SBCL script +startup. + +Hot-loop mode is startup-amortized local evidence. It runs a larger loop count +and reports total time plus normalized time for the base `1000000` loop count. diff --git a/benchmarks/math-loop/benchmark.json b/benchmarks/math-loop/benchmark.json new file mode 100644 index 0000000..1e9c0e7 --- /dev/null +++ b/benchmarks/math-loop/benchmark.json @@ -0,0 +1,10 @@ +{ + "benchmark": "math-loop", + "source_stem": "math_loop", + "loop_count": 1000000, + "expected_checksum": "5000001", + "stdin": "1000000\n", + "hot_loop_count": 10000000, + "hot_expected_checksum": "50000001", + "hot_stdin": "10000000\n" +} diff --git a/benchmarks/math-loop/c/math_loop.c b/benchmarks/math-loop/c/math_loop.c new file mode 100644 index 0000000..0f8502d --- /dev/null +++ b/benchmarks/math-loop/c/math_loop.c @@ -0,0 +1,32 @@ +#include +#include + +#define LOOP_COUNT 1000000 +#define EXPECTED_CHECKSUM 5000001 + +static int32_t math_loop(int32_t limit) { + int32_t i = 0; + int32_t acc = 1; + + while (i < limit) { + acc = acc + ((i + 3) * 2); + acc = acc > 1000000000 ? acc - 1000000000 : acc; + i = i + 1; + } + + return acc; +} + +static int32_t configured_loop_count(void) { + int32_t value = LOOP_COUNT; + if (scanf("%d", &value) != 1 || value <= 0) { + return LOOP_COUNT; + } + return value; +} + +int main(void) { + int32_t result = math_loop(configured_loop_count()); + printf("%d\n", result); + return result == EXPECTED_CHECKSUM ? 0 : 1; +} diff --git a/benchmarks/math-loop/clojure/math_loop.clj b/benchmarks/math-loop/clojure/math_loop.clj new file mode 100644 index 0000000..a93dcb5 --- /dev/null +++ b/benchmarks/math-loop/clojure/math_loop.clj @@ -0,0 +1,28 @@ +(set! *warn-on-reflection* true) +(set! *unchecked-math* :warn-on-boxed) + +(def loop-count 1000000) +(def expected-checksum 5000001) + +(defn configured-loop-count [] + (try + (let [line (read-line) + value (Integer/parseInt (.trim ^String line))] + (if (pos? value) value loop-count)) + (catch Exception _ + loop-count))) + +(defn math-loop [limit] + (loop [i 0 + acc 1] + (if (< i limit) + (let [next (+ acc (* (+ i 3) 2)) + bounded (if (> next 1000000000) + (- next 1000000000) + next)] + (recur (inc i) bounded)) + acc))) + +(let [result (math-loop (configured-loop-count))] + (println result) + (System/exit (if (= result expected-checksum) 0 1))) diff --git a/benchmarks/math-loop/common-lisp/math_loop.lisp b/benchmarks/math-loop/common-lisp/math_loop.lisp new file mode 100644 index 0000000..16b3574 --- /dev/null +++ b/benchmarks/math-loop/common-lisp/math_loop.lisp @@ -0,0 +1,33 @@ +(declaim (optimize (speed 3) (safety 0) (debug 0))) + +(defconstant +loop-count+ 1000000) +(defconstant +expected-checksum+ 5000001) + +(declaim (ftype (function () fixnum) configured-loop-count)) +(defun configured-loop-count () + (handler-case + (let ((line (read-line *standard-input* nil nil))) + (if line + (let ((value (parse-integer line :junk-allowed t))) + (if (> value 0) value +loop-count+)) + +loop-count+)) + (error () +loop-count+))) + +(declaim (ftype (function (fixnum) fixnum) math-loop)) +(defun math-loop (limit) + (declare (type fixnum limit)) + (loop with i of-type fixnum = 0 + with acc of-type fixnum = 1 + while (< i limit) + do (let* ((next (+ acc (* (+ i 3) 2))) + (bounded (if (> next 1000000000) + (- next 1000000000) + next))) + (declare (type fixnum next bounded)) + (setf acc bounded + i (+ i 1))) + finally (return acc))) + +(let ((result (math-loop (configured-loop-count)))) + (format t "~D~%" result) + (sb-ext:exit :code (if (= result +expected-checksum+) 0 1))) diff --git a/benchmarks/math-loop/python/math_loop.py b/benchmarks/math-loop/python/math_loop.py new file mode 100644 index 0000000..21cc431 --- /dev/null +++ b/benchmarks/math-loop/python/math_loop.py @@ -0,0 +1,33 @@ +LOOP_COUNT = 1_000_000 +EXPECTED_CHECKSUM = 5_000_001 + + +def math_loop(limit: int) -> int: + i = 0 + acc = 1 + + while i < limit: + acc = acc + ((i + 3) * 2) + acc = acc - 1_000_000_000 if acc > 1_000_000_000 else acc + i += 1 + + return acc + + +def configured_loop_count() -> int: + try: + value = int(input().strip()) + except (EOFError, ValueError): + return LOOP_COUNT + + return value if value > 0 else LOOP_COUNT + + +def main() -> int: + result = math_loop(configured_loop_count()) + print(result) + return 0 if result == EXPECTED_CHECKSUM else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/benchmarks/math-loop/run.py b/benchmarks/math-loop/run.py new file mode 100644 index 0000000..4525fe9 --- /dev/null +++ b/benchmarks/math-loop/run.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +"""Run the local math-loop benchmark scaffold.""" + +from pathlib import Path +import sys + +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from runner import main + + +if __name__ == "__main__": + raise SystemExit(main(Path(__file__).resolve().parent, sys.argv[1:])) diff --git a/benchmarks/math-loop/rust/math_loop.rs b/benchmarks/math-loop/rust/math_loop.rs new file mode 100644 index 0000000..8a7d82f --- /dev/null +++ b/benchmarks/math-loop/rust/math_loop.rs @@ -0,0 +1,39 @@ +const LOOP_COUNT: i32 = 1_000_000; +const EXPECTED_CHECKSUM: i32 = 5_000_001; + +fn math_loop(limit: i32) -> i32 { + let mut i = 0; + let mut acc = 1; + + while i < limit { + acc = acc + ((i + 3) * 2); + acc = if acc > 1_000_000_000 { + acc - 1_000_000_000 + } else { + acc + }; + i += 1; + } + + acc +} + +fn configured_loop_count() -> i32 { + let mut input = String::new(); + if std::io::stdin().read_line(&mut input).is_err() { + return LOOP_COUNT; + } + + input + .trim() + .parse::() + .ok() + .filter(|value| *value > 0) + .unwrap_or(LOOP_COUNT) +} + +fn main() { + let result = math_loop(configured_loop_count()); + println!("{}", result); + std::process::exit(if result == EXPECTED_CHECKSUM { 0 } else { 1 }); +} diff --git a/benchmarks/math-loop/slovo.toml b/benchmarks/math-loop/slovo.toml new file mode 100644 index 0000000..6bf4f7c --- /dev/null +++ b/benchmarks/math-loop/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "math-loop" +source_root = "src" +entry = "main" diff --git a/benchmarks/math-loop/src/main.slo b/benchmarks/math-loop/src/main.slo new file mode 100644 index 0000000..9d15b7a --- /dev/null +++ b/benchmarks/math-loop/src/main.slo @@ -0,0 +1,56 @@ +; Benchmark scaffold fixture for local-machine timing comparisons only. +; Keep LOOP_COUNT and EXPECTED_CHECKSUM aligned with the C/Rust/Python fixtures. +; The runner supplies the loop count through stdin so native compilers cannot +; fold the benchmark loop into a constant answer. + +(module main) + +(fn loop_count () -> i32 + 1000000) + +(fn expected_checksum () -> i32 + 5000001) + +(fn parse_stdin_loop_count () -> (result i32 i32) + (let input (result string i32) (std.io.read_stdin_result)) + (match input + ((ok text) + (std.string.parse_i32_result text)) + ((err code) + (err i32 i32 code)))) + +(fn parse_arg_loop_count () -> (result i32 i32) + (std.string.parse_i32_result (std.process.arg 1))) + +(fn configured_stdin_loop_count () -> i32 + (let parsed_stdin (result i32 i32) (parse_stdin_loop_count)) + (if (is_ok parsed_stdin) + (unwrap_ok parsed_stdin) + (loop_count))) + +(fn configured_loop_count () -> i32 + (let parsed_arg (result i32 i32) (parse_arg_loop_count)) + (if (is_ok parsed_arg) + (unwrap_ok parsed_arg) + (configured_stdin_loop_count))) + +(fn math_loop ((limit i32)) -> i32 + (var i i32 0) + (var acc i32 1) + (while (< i limit) + (set acc (+ acc (* (+ i 3) 2))) + (set acc (if (> acc 1000000000) + (- acc 1000000000) + acc)) + (set i (+ i 1))) + acc) + +(fn main () -> i32 + (let result i32 (math_loop (configured_loop_count))) + (std.io.print_i32 result) + (if (= result (expected_checksum)) + 0 + 1)) + +(test "math loop checksum is deterministic" + (= (math_loop (loop_count)) (expected_checksum))) diff --git a/benchmarks/parse-loop/.gitignore b/benchmarks/parse-loop/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/benchmarks/parse-loop/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/benchmarks/parse-loop/README.md b/benchmarks/parse-loop/README.md new file mode 100644 index 0000000..7e465f2 --- /dev/null +++ b/benchmarks/parse-loop/README.md @@ -0,0 +1,34 @@ +# Parse Loop Benchmark Scaffold + +Release: `exp-40`; Common Lisp/SBCL comparison added by `exp-41`; hot-loop +mode added by `exp-42`. + +This benchmark compares repeated signed decimal `i32` parsing across Slovo, C, +Rust, Python, Clojure, and Common Lisp/SBCL on the same machine. + +It is not a published benchmark result, performance threshold, optimizer +claim, or cross-machine comparison. + +All implementations print checksum `345000001` for loop count `1000000` and +parse text `12345`. Hot-loop mode uses loop count `10000000` and checksum +`450000001`. The runner supplies the loop count and parse text at runtime. + +## Comparison Method + +- The runner builds each implementation once before timing. The reported + numbers measure execution only, not compile time. +- Slovo timings use `glagol build`, which currently lowers to LLVM and then + invokes host `clang -O2` with `runtime/runtime.c`. +- C timings use `clang -O2 -std=c11`. +- Rust timings use `rustc -C opt-level=3 -C debuginfo=0`. +- The parse implementations are intentionally comparable by input and checksum, + not identical by parser internals: Slovo uses `std.string.parse_i32_result`, + C uses `strtol`, Rust uses `parse::()`, Python uses `int`, Clojure uses + `Integer/parseInt`, and Common Lisp uses `parse-integer`. + +Timing is cold-process local-machine evidence only. Clojure timings include +JVM and Clojure startup, while Common Lisp timings include SBCL script +startup. + +Hot-loop mode is startup-amortized local evidence. It reports total time plus +normalized time for the base `1000000` loop count. diff --git a/benchmarks/parse-loop/benchmark.json b/benchmarks/parse-loop/benchmark.json new file mode 100644 index 0000000..4742cec --- /dev/null +++ b/benchmarks/parse-loop/benchmark.json @@ -0,0 +1,11 @@ +{ + "benchmark": "parse-loop", + "source_stem": "parse_loop", + "loop_count": 1000000, + "expected_checksum": "345000001", + "stdin": "1000000\n", + "hot_loop_count": 10000000, + "hot_expected_checksum": "450000001", + "hot_stdin": "10000000\n", + "run_args": ["12345"] +} diff --git a/benchmarks/parse-loop/c/parse_loop.c b/benchmarks/parse-loop/c/parse_loop.c new file mode 100644 index 0000000..f256e97 --- /dev/null +++ b/benchmarks/parse-loop/c/parse_loop.c @@ -0,0 +1,34 @@ +#include +#include +#include + +#define LOOP_COUNT 1000000 +#define EXPECTED_CHECKSUM 345000001 + +static int32_t configured_loop_count(void) { + int32_t value = LOOP_COUNT; + if (scanf("%d", &value) != 1 || value <= 0) { + return LOOP_COUNT; + } + return value; +} + +static int32_t parse_loop(int32_t limit, const char *text) { + int32_t i = 0; + int32_t acc = 1; + + while (i < limit) { + acc = acc + (int32_t)strtol(text, NULL, 10); + acc = acc > 1000000000 ? acc - 1000000000 : acc; + i = i + 1; + } + + return acc; +} + +int main(int argc, char **argv) { + const char *text = argc > 1 ? argv[1] : "12345"; + int32_t result = parse_loop(configured_loop_count(), text); + printf("%d\n", result); + return result == EXPECTED_CHECKSUM ? 0 : 1; +} diff --git a/benchmarks/parse-loop/clojure/parse_loop.clj b/benchmarks/parse-loop/clojure/parse_loop.clj new file mode 100644 index 0000000..34db932 --- /dev/null +++ b/benchmarks/parse-loop/clojure/parse_loop.clj @@ -0,0 +1,31 @@ +(set! *warn-on-reflection* true) +(set! *unchecked-math* :warn-on-boxed) + +(def loop-count 1000000) +(def expected-checksum 345000001) + +(defn configured-loop-count [] + (try + (let [line (read-line) + value (Integer/parseInt (.trim ^String line))] + (if (pos? value) value loop-count)) + (catch Exception _ + loop-count))) + +(defn parse-text [] + (or (first *command-line-args*) "12345")) + +(defn parse-loop [limit text] + (loop [i 0 + acc 1] + (if (< i limit) + (let [next (+ acc (Integer/parseInt ^String text)) + bounded (if (> next 1000000000) + (- next 1000000000) + next)] + (recur (inc i) bounded)) + acc))) + +(let [result (parse-loop (configured-loop-count) (parse-text))] + (println result) + (System/exit (if (= result expected-checksum) 0 1))) diff --git a/benchmarks/parse-loop/common-lisp/parse_loop.lisp b/benchmarks/parse-loop/common-lisp/parse_loop.lisp new file mode 100644 index 0000000..45de743 --- /dev/null +++ b/benchmarks/parse-loop/common-lisp/parse_loop.lisp @@ -0,0 +1,38 @@ +(declaim (optimize (speed 3) (safety 0) (debug 0))) + +(defconstant +loop-count+ 1000000) +(defconstant +expected-checksum+ 345000001) + +(declaim (ftype (function () fixnum) configured-loop-count)) +(defun configured-loop-count () + (handler-case + (let ((line (read-line *standard-input* nil nil))) + (if line + (let ((value (parse-integer line :junk-allowed t))) + (if (> value 0) value +loop-count+)) + +loop-count+)) + (error () +loop-count+))) + +(declaim (ftype (function () string) parse-text)) +(defun parse-text () + (or (second sb-ext:*posix-argv*) "12345")) + +(declaim (ftype (function (fixnum string) fixnum) parse-loop)) +(defun parse-loop (limit text) + (declare (type fixnum limit) + (type string text)) + (loop with i of-type fixnum = 0 + with acc of-type fixnum = 1 + while (< i limit) + do (let* ((next (+ acc (parse-integer text))) + (bounded (if (> next 1000000000) + (- next 1000000000) + next))) + (declare (type fixnum next bounded)) + (setf acc bounded + i (+ i 1))) + finally (return acc))) + +(let ((result (parse-loop (configured-loop-count) (parse-text)))) + (format t "~D~%" result) + (sb-ext:exit :code (if (= result +expected-checksum+) 0 1))) diff --git a/benchmarks/parse-loop/python/parse_loop.py b/benchmarks/parse-loop/python/parse_loop.py new file mode 100644 index 0000000..280d66a --- /dev/null +++ b/benchmarks/parse-loop/python/parse_loop.py @@ -0,0 +1,40 @@ +import sys + + +LOOP_COUNT = 1_000_000 +EXPECTED_CHECKSUM = 345_000_001 + + +def configured_loop_count() -> int: + try: + value = int(input().strip()) + except (EOFError, ValueError): + return LOOP_COUNT + + return value if value > 0 else LOOP_COUNT + + +def parse_text() -> str: + return sys.argv[1] if len(sys.argv) > 1 else "12345" + + +def parse_loop(limit: int, text: str) -> int: + i = 0 + acc = 1 + + while i < limit: + acc += int(text) + acc = acc - 1_000_000_000 if acc > 1_000_000_000 else acc + i += 1 + + return acc + + +def main() -> int: + result = parse_loop(configured_loop_count(), parse_text()) + print(result) + return 0 if result == EXPECTED_CHECKSUM else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/benchmarks/parse-loop/run.py b/benchmarks/parse-loop/run.py new file mode 100644 index 0000000..8626c4f --- /dev/null +++ b/benchmarks/parse-loop/run.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +"""Run the local parse-loop benchmark scaffold.""" + +from pathlib import Path +import sys + +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from runner import main + + +if __name__ == "__main__": + raise SystemExit(main(Path(__file__).resolve().parent, sys.argv[1:])) diff --git a/benchmarks/parse-loop/rust/parse_loop.rs b/benchmarks/parse-loop/rust/parse_loop.rs new file mode 100644 index 0000000..a42916b --- /dev/null +++ b/benchmarks/parse-loop/rust/parse_loop.rs @@ -0,0 +1,44 @@ +const LOOP_COUNT: i32 = 1_000_000; +const EXPECTED_CHECKSUM: i32 = 345_000_001; + +fn configured_loop_count() -> i32 { + let mut input = String::new(); + if std::io::stdin().read_line(&mut input).is_err() { + return LOOP_COUNT; + } + + input + .trim() + .parse::() + .ok() + .filter(|value| *value > 0) + .unwrap_or(LOOP_COUNT) +} + +fn parse_text() -> String { + std::env::args().nth(1).unwrap_or_else(|| "12345".to_string()) +} + +fn parse_loop(limit: i32, text: &str) -> i32 { + let mut i = 0; + let mut acc = 1; + + while i < limit { + acc += text.parse::().unwrap(); + acc = if acc > 1_000_000_000 { + acc - 1_000_000_000 + } else { + acc + }; + i += 1; + } + + acc +} + +fn main() { + let text = parse_text(); + let result = parse_loop(configured_loop_count(), &text); + println!("{}", result); + std::process::exit(if result == EXPECTED_CHECKSUM { 0 } else { 1 }); +} diff --git a/benchmarks/parse-loop/slovo.toml b/benchmarks/parse-loop/slovo.toml new file mode 100644 index 0000000..b9b195a --- /dev/null +++ b/benchmarks/parse-loop/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "parse-loop" +source_root = "src" +entry = "main" diff --git a/benchmarks/parse-loop/src/main.slo b/benchmarks/parse-loop/src/main.slo new file mode 100644 index 0000000..21363fb --- /dev/null +++ b/benchmarks/parse-loop/src/main.slo @@ -0,0 +1,59 @@ +; Benchmark scaffold fixture for local-machine parse timing comparisons only. + +(module main) + +(fn loop_count () -> i32 + 1000000) + +(fn expected_checksum () -> i32 + 345000001) + +(fn parse_stdin_loop_count () -> (result i32 i32) + (let input (result string i32) (std.io.read_stdin_result)) + (match input + ((ok text) + (std.string.parse_i32_result text)) + ((err code) + (err i32 i32 code)))) + +(fn parse_arg_loop_count () -> (result i32 i32) + (std.string.parse_i32_result (std.process.arg 2))) + +(fn configured_stdin_loop_count () -> i32 + (let parsed_stdin (result i32 i32) (parse_stdin_loop_count)) + (if (is_ok parsed_stdin) + (unwrap_ok parsed_stdin) + (loop_count))) + +(fn configured_loop_count () -> i32 + (let parsed_arg (result i32 i32) (parse_arg_loop_count)) + (if (is_ok parsed_arg) + (unwrap_ok parsed_arg) + (configured_stdin_loop_count))) + +(fn parse_text () -> string + (std.process.arg 1)) + +(fn parsed_value ((text string)) -> i32 + (unwrap_ok (std.string.parse_i32_result text))) + +(fn parse_loop ((limit i32) (text string)) -> i32 + (var i i32 0) + (var acc i32 1) + (while (< i limit) + (set acc (+ acc (parsed_value text))) + (set acc (if (> acc 1000000000) + (- acc 1000000000) + acc)) + (set i (+ i 1))) + acc) + +(fn main () -> i32 + (let result i32 (parse_loop (configured_loop_count) (parse_text))) + (std.io.print_i32 result) + (if (= result (expected_checksum)) + 0 + 1)) + +(test "parse loop checksum is deterministic" + (= (parse_loop (loop_count) "12345") (expected_checksum))) diff --git a/benchmarks/runner.py b/benchmarks/runner.py new file mode 100644 index 0000000..afa74ba --- /dev/null +++ b/benchmarks/runner.py @@ -0,0 +1,522 @@ +#!/usr/bin/env python3 +"""Shared local benchmark runner for Glagol benchmark scaffolds.""" + +from __future__ import annotations + +import argparse +import json +import os +import shutil +import statistics +import subprocess +import sys +import time +from dataclasses import dataclass +from pathlib import Path +from typing import Callable + + +TIMING_SCOPE = "local-machine comparison only" +TIMING_MODES = ["cold-process", "hot-loop"] + + +@dataclass(frozen=True) +class BenchmarkSpec: + name: str + source_stem: str + loop_count: int + expected_checksum: str + stdin_text: str + hot_loop_count: int + hot_expected_checksum: str + hot_stdin_text: str + run_args: list[str] + + +@dataclass(frozen=True) +class RunParameters: + mode: str + loop_count: int + expected_checksum: str + stdin_text: str + base_loop_count: int + + +@dataclass(frozen=True) +class Implementation: + name: str + language: str + source: Path + build: Callable[[Path, BenchmarkSpec, argparse.Namespace], tuple[list[str], list[str] | None]] + run: Callable[[Path, BenchmarkSpec, argparse.Namespace], list[str]] + + +def main(root: Path, argv: list[str]) -> int: + spec = read_spec(root) + implementations = available_implementations(root, spec) + parser = argparse.ArgumentParser(description=f"Run local {spec.name} timing comparisons.") + parser.add_argument("--list", action="store_true", help="print benchmark metadata and exit") + parser.add_argument("--json", action="store_true", help="emit JSON for --list or results") + parser.add_argument("--dry-run", action="store_true", help="print planned commands without running them") + parser.add_argument( + "--mode", + choices=TIMING_MODES, + default="cold-process", + help="cold-process measures one normal run; hot-loop uses an amplified loop count and normalized results", + ) + parser.add_argument("--only", choices=[impl.name for impl in implementations], action="append") + parser.add_argument("--repeats", type=positive_int, default=5) + parser.add_argument("--warmups", type=non_negative_int, default=1) + parser.add_argument("--glagol", help="path to the glagol compiler binary") + parser.add_argument("--cc", help="path to the C compiler") + parser.add_argument("--clojure", help="path to the clojure command") + parser.add_argument("--clojure-jar", help="path to a clojure jar for `java -cp ... clojure.main`") + parser.add_argument("--sbcl", help="path to the SBCL executable for Common Lisp comparisons") + args = parser.parse_args(argv) + + selected = select_implementations(implementations, args.only) + + if args.list: + emit_list(root, spec, selected, args.json) + return 0 + + if args.dry_run: + emit_dry_run(root, spec, selected, args) + return 0 + + results = run_benchmarks(root, spec, selected, args) + emit_results(spec, results, args.json) + return 1 if any(result["status"] == "failed" for result in results) else 0 + + +def read_spec(root: Path) -> BenchmarkSpec: + data = json.loads((root / "benchmark.json").read_text(encoding="utf-8")) + loop_count = int(data["loop_count"]) + return BenchmarkSpec( + name=str(data["benchmark"]), + source_stem=str(data["source_stem"]), + loop_count=loop_count, + expected_checksum=str(data["expected_checksum"]), + stdin_text=str(data.get("stdin", f"{loop_count}\n")), + hot_loop_count=int(data.get("hot_loop_count", loop_count)), + hot_expected_checksum=str(data.get("hot_expected_checksum", data["expected_checksum"])), + hot_stdin_text=str(data.get("hot_stdin", f"{int(data.get('hot_loop_count', loop_count))}\n")), + run_args=[str(item) for item in data.get("run_args", [])], + ) + + +def available_implementations(root: Path, spec: BenchmarkSpec) -> list[Implementation]: + candidates = [ + Implementation("slovo", "Slovo", root / "src" / "main.slo", slovo_build, slovo_run), + Implementation("c", "C", root / "c" / f"{spec.source_stem}.c", c_build, c_run), + Implementation("rust", "Rust", root / "rust" / f"{spec.source_stem}.rs", rust_build, rust_run), + Implementation("python", "Python", root / "python" / f"{spec.source_stem}.py", python_build, python_run), + Implementation("clojure", "Clojure", root / "clojure" / f"{spec.source_stem}.clj", clojure_build, clojure_run), + Implementation( + "common_lisp", + "Common Lisp (SBCL)", + root / "common-lisp" / f"{spec.source_stem}.lisp", + common_lisp_build, + common_lisp_run, + ), + ] + return [impl for impl in candidates if impl.source.is_file()] + + +def slovo_compiler(root: Path, args: argparse.Namespace) -> str | None: + if args.glagol: + return args.glagol + env_path = os.environ.get("GLAGOL") + if env_path: + return env_path + candidate = root.parents[1] / "compiler" / "target" / "debug" / executable("glagol") + if candidate.is_file(): + return str(candidate) + return shutil.which("glagol") + + +def executable(name: str) -> str: + return f"{name}.exe" if os.name == "nt" else name + + +def slovo_build(root: Path, spec: BenchmarkSpec, args: argparse.Namespace) -> tuple[list[str], list[str] | None]: + compiler = slovo_compiler(root, args) + if compiler is None: + return [], ["missing glagol compiler; set GLAGOL or pass --glagol"] + if os.environ.get("GLAGOL_CLANG") is None and shutil.which("clang") is None: + return [], ["missing clang for glagol build; set GLAGOL_CLANG"] + output = build_dir(root) / executable(f"slovo-{spec.name}") + return [compiler, "build", str(root), "-o", str(output)], None + + +def slovo_run(root: Path, spec: BenchmarkSpec, _args: argparse.Namespace) -> list[str]: + params = run_parameters(spec, _args.mode) + return [str(build_dir(root) / executable(f"slovo-{spec.name}")), *spec.run_args, str(params.loop_count)] + + +def c_build(root: Path, spec: BenchmarkSpec, args: argparse.Namespace) -> tuple[list[str], list[str] | None]: + compiler = args.cc or os.environ.get("CC") or first_available(["clang", "cc", "gcc"]) + if compiler is None: + return [], ["missing C compiler; set CC or pass --cc"] + output = build_dir(root) / executable(f"c-{spec.name}") + return [compiler, "-O2", "-std=c11", str(root / "c" / f"{spec.source_stem}.c"), "-o", str(output)], None + + +def c_run(root: Path, spec: BenchmarkSpec, _args: argparse.Namespace) -> list[str]: + return [str(build_dir(root) / executable(f"c-{spec.name}")), *spec.run_args] + + +def rust_build(root: Path, spec: BenchmarkSpec, _args: argparse.Namespace) -> tuple[list[str], list[str] | None]: + rustc = first_available(["rustc"]) + if rustc is None: + return [], ["missing rustc"] + output = build_dir(root) / executable(f"rust-{spec.name}") + return [ + rustc, + "-C", + "opt-level=3", + "-C", + "debuginfo=0", + str(root / "rust" / f"{spec.source_stem}.rs"), + "-o", + str(output), + ], None + + +def rust_run(root: Path, spec: BenchmarkSpec, _args: argparse.Namespace) -> list[str]: + return [str(build_dir(root) / executable(f"rust-{spec.name}")), *spec.run_args] + + +def python_build(_root: Path, _spec: BenchmarkSpec, _args: argparse.Namespace) -> tuple[list[str], list[str] | None]: + return [], None + + +def python_run(root: Path, spec: BenchmarkSpec, _args: argparse.Namespace) -> list[str]: + return [sys.executable, str(root / "python" / f"{spec.source_stem}.py"), *spec.run_args] + + +def clojure_build(_root: Path, _spec: BenchmarkSpec, args: argparse.Namespace) -> tuple[list[str], list[str] | None]: + if clojure_command(args) is None: + return [], ["missing clojure command; set CLOJURE, pass --clojure, or set CLOJURE_JAR"] + return [], None + + +def clojure_run(root: Path, spec: BenchmarkSpec, args: argparse.Namespace) -> list[str]: + source = str(root / "clojure" / f"{spec.source_stem}.clj") + command = clojure_command(args) + assert command is not None + return [*command, source, *spec.run_args] + + +def clojure_command(args: argparse.Namespace) -> list[str] | None: + if args.clojure: + return [args.clojure] + env_path = os.environ.get("CLOJURE") + if env_path: + return [env_path] + found = shutil.which("clojure") + if found: + return [found] + + jar = args.clojure_jar or os.environ.get("CLOJURE_JAR") + java = shutil.which("java") + if jar and java: + return [java, "-cp", jar, "clojure.main"] + return None + + +def common_lisp_build(_root: Path, _spec: BenchmarkSpec, args: argparse.Namespace) -> tuple[list[str], list[str] | None]: + if sbcl_command(args) is None: + return [], ["missing SBCL; set SBCL or pass --sbcl"] + return [], None + + +def common_lisp_run(root: Path, spec: BenchmarkSpec, args: argparse.Namespace) -> list[str]: + sbcl = sbcl_command(args) + assert sbcl is not None + return [ + sbcl, + "--noinform", + "--disable-debugger", + "--script", + str(root / "common-lisp" / f"{spec.source_stem}.lisp"), + *spec.run_args, + ] + + +def sbcl_command(args: argparse.Namespace) -> str | None: + if args.sbcl: + return args.sbcl + env_path = os.environ.get("SBCL") + if env_path: + return env_path + return shutil.which("sbcl") + + +def build_dir(root: Path) -> Path: + return root / "build" + + +def positive_int(value: str) -> int: + parsed = int(value) + if parsed <= 0: + raise argparse.ArgumentTypeError("value must be greater than zero") + return parsed + + +def non_negative_int(value: str) -> int: + parsed = int(value) + if parsed < 0: + raise argparse.ArgumentTypeError("value must be zero or greater") + return parsed + + +def select_implementations(implementations: list[Implementation], names: list[str] | None) -> list[Implementation]: + if not names: + return implementations + selected_names = set(names) + return [impl for impl in implementations if impl.name in selected_names] + + +def emit_list(root: Path, spec: BenchmarkSpec, implementations: list[Implementation], as_json: bool) -> None: + metadata = { + "benchmark": spec.name, + "loop_count": spec.loop_count, + "hot_loop_count": spec.hot_loop_count, + "expected_checksum": spec.expected_checksum, + "hot_expected_checksum": spec.hot_expected_checksum, + "timing_scope": TIMING_SCOPE, + "timing_modes": TIMING_MODES, + "loop_count_source": "stdin", + "run_args": spec.run_args, + "implementations": [ + {"name": impl.name, "language": impl.language, "source": str(impl.source.relative_to(root))} + for impl in implementations + ], + } + + if as_json: + print(json.dumps(metadata, indent=2, sort_keys=True)) + return + + print(f"{spec.name}: {TIMING_SCOPE}") + print(f"loop_count={spec.loop_count}") + print(f"hot_loop_count={spec.hot_loop_count}") + print("loop_count_source=stdin") + if spec.run_args: + print(f"run_args={' '.join(spec.run_args)}") + print(f"expected_checksum={spec.expected_checksum}") + print("implementations:") + for impl in implementations: + print(f" {impl.name}: {impl.language} ({impl.source.relative_to(root)})") + + +def emit_dry_run(root: Path, spec: BenchmarkSpec, implementations: list[Implementation], args: argparse.Namespace) -> None: + params = run_parameters(spec, args.mode) + print(f"{spec.name}: {TIMING_SCOPE}") + print(f"mode={params.mode}") + print(f"loop_count={params.loop_count}") + print(f"expected_checksum={params.expected_checksum}") + for impl in implementations: + build_command, skip_reasons = impl.build(root, spec, args) + print(f"{impl.name}:") + if skip_reasons: + print(f" skip: {'; '.join(skip_reasons)}") + continue + if build_command: + print(f" build: {format_command(build_command)}") + else: + print(" build: none") + print(f" stdin: {params.stdin_text.rstrip()}") + print(f" run: {format_command(impl.run(root, spec, args))}") + + +def run_benchmarks(root: Path, spec: BenchmarkSpec, implementations: list[Implementation], args: argparse.Namespace) -> list[dict[str, object]]: + build_dir(root).mkdir(exist_ok=True) + return [run_one(root, spec, impl, args) for impl in implementations] + + +def run_one(root: Path, spec: BenchmarkSpec, impl: Implementation, args: argparse.Namespace) -> dict[str, object]: + params = run_parameters(spec, args.mode) + build_command, skip_reasons = impl.build(root, spec, args) + if skip_reasons: + return skipped_result(impl, skip_reasons) + + if build_command: + build = run_command(build_command) + if build.returncode != 0: + return failed_result(impl, "build failed", build) + + run_command_line = impl.run(root, spec, args) + for _ in range(args.warmups): + warmup = run_command(run_command_line, params.stdin_text) + if not run_succeeded_for_params(warmup, params): + return failed_result(impl, "warmup failed", warmup) + + timings: list[int] = [] + for _ in range(args.repeats): + start = time.perf_counter_ns() + run = run_command(run_command_line, params.stdin_text) + elapsed = time.perf_counter_ns() - start + if not run_succeeded_for_params(run, params): + return failed_result(impl, "run failed", run) + timings.append(elapsed) + + min_ms = ns_to_ms(min(timings)) + median_ms = ns_to_ms(int(statistics.median(timings))) + max_ms = ns_to_ms(max(timings)) + normalization_factor = params.loop_count / params.base_loop_count + return { + "name": impl.name, + "language": impl.language, + "status": "ok", + "timing_mode": params.mode, + "loop_count": params.loop_count, + "base_loop_count": params.base_loop_count, + "normalization_factor": normalization_factor, + "repeats": args.repeats, + "warmups": args.warmups, + "checksum": params.expected_checksum, + "min_ms": min_ms, + "median_ms": median_ms, + "max_ms": max_ms, + "normalized_min_ms": min_ms / normalization_factor, + "normalized_median_ms": median_ms / normalization_factor, + "normalized_max_ms": max_ms / normalization_factor, + "timing_scope": TIMING_SCOPE, + } + + +def skipped_result(impl: Implementation, reasons: list[str]) -> dict[str, object]: + return {"name": impl.name, "language": impl.language, "status": "skipped", "reason": "; ".join(reasons)} + + +def failed_result(impl: Implementation, message: str, process: subprocess.CompletedProcess[str]) -> dict[str, object]: + return { + "name": impl.name, + "language": impl.language, + "status": "failed", + "reason": message, + "returncode": process.returncode, + "stdout": process.stdout, + "stderr": process.stderr, + } + + +def emit_results(spec: BenchmarkSpec, results: list[dict[str, object]], as_json: bool) -> None: + if as_json: + print( + json.dumps( + { + "benchmark": spec.name, + "base_loop_count": spec.loop_count, + "timing_scope": TIMING_SCOPE, + "results": results, + }, + indent=2, + sort_keys=True, + ) + ) + return + + mode = next((str(result["timing_mode"]) for result in results if result["status"] == "ok"), "unknown") + if mode == "hot-loop": + loop_count = next((int(result["loop_count"]) for result in results if result["status"] == "ok"), spec.hot_loop_count) + print( + f"{spec.name}: {TIMING_SCOPE} " + f"(mode=hot-loop, loop_count={loop_count}, normalized_to={spec.loop_count})" + ) + else: + print(f"{spec.name}: {TIMING_SCOPE}") + for result in results: + status = result["status"] + name = result["name"] + if status == "ok": + if result["timing_mode"] == "hot-loop": + print( + "{name}: total_min={min_ms:.3f}ms total_median={median_ms:.3f}ms " + "total_max={max_ms:.3f}ms normalized_median={normalized_median_ms:.3f}ms".format( + name=name, + min_ms=result["min_ms"], + median_ms=result["median_ms"], + max_ms=result["max_ms"], + normalized_median_ms=result["normalized_median_ms"], + ) + ) + else: + print( + "{name}: min={min_ms:.3f}ms median={median_ms:.3f}ms max={max_ms:.3f}ms".format( + name=name, + min_ms=result["min_ms"], + median_ms=result["median_ms"], + max_ms=result["max_ms"], + ) + ) + else: + print(f"{name}: {status} ({result['reason']})") + + +def run_command(command: list[str], stdin_text: str | None = None) -> subprocess.CompletedProcess[str]: + return subprocess.run( + command, + input=stdin_text, + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + check=False, + ) + + +def normalized_stdout(stdout: str) -> str: + lines = [line.strip() for line in stdout.splitlines() if line.strip()] + if not lines: + return "" + return lines[-1] + + +def run_parameters(spec: BenchmarkSpec, mode: str) -> RunParameters: + if mode == "hot-loop": + return RunParameters( + mode=mode, + loop_count=spec.hot_loop_count, + expected_checksum=spec.hot_expected_checksum, + stdin_text=spec.hot_stdin_text, + base_loop_count=spec.loop_count, + ) + return RunParameters( + mode="cold-process", + loop_count=spec.loop_count, + expected_checksum=spec.expected_checksum, + stdin_text=spec.stdin_text, + base_loop_count=spec.loop_count, + ) + + +def run_succeeded_for_params(process: subprocess.CompletedProcess[str], params: RunParameters) -> bool: + if normalized_stdout(process.stdout) != params.expected_checksum: + return False + if params.mode == "hot-loop": + return True + return process.returncode == 0 + + +def ns_to_ms(value: int) -> float: + return value / 1_000_000.0 + + +def first_available(candidates: list[str]) -> str | None: + for candidate in candidates: + found = shutil.which(candidate) + if found: + return found + return None + + +def format_command(command: list[str]) -> str: + return " ".join(shlex_quote(part) for part in command) + + +def shlex_quote(value: str) -> str: + if value and all(char.isalnum() or char in "/._:-" for char in value): + return value + return "'" + value.replace("'", "'\"'\"'") + "'" diff --git a/benchmarks/string-eq-loop/.gitignore b/benchmarks/string-eq-loop/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/benchmarks/string-eq-loop/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/benchmarks/string-eq-loop/README.md b/benchmarks/string-eq-loop/README.md new file mode 100644 index 0000000..4185f09 --- /dev/null +++ b/benchmarks/string-eq-loop/README.md @@ -0,0 +1,71 @@ +# String Equality Loop Benchmark Scaffold + +Release: `exp-119`. + +This benchmark compares exact ASCII string content equality plus `i32` +checksum accumulation across Slovo, C, Rust, Python, Clojure, and Common Lisp/SBCL on the same machine. + +It is not a published benchmark result, performance threshold, optimizer +claim, or cross-machine comparison. + +## Files + +- `src/main.slo`: Slovo project benchmark fixture +- `c/string_eq_loop.c`: C comparison implementation +- `rust/string_eq_loop.rs`: Rust comparison implementation +- `python/string_eq_loop.py`: Python comparison implementation +- `clojure/string_eq_loop.clj`: Clojure comparison implementation +- `common-lisp/string_eq_loop.lisp`: Common Lisp/SBCL comparison implementation +- `run.py`: build/run/timing harness + +All implementations print checksum `4600001` for loop count `1000000` and +target string `omega`. Hot-loop mode uses loop count `10000000` and checksum +`46000001`. The runner supplies the loop count and target string at runtime. + +## Commands + +Run from the Glagol repository root: + +```bash +python3 benchmarks/string-eq-loop/run.py --list +python3 benchmarks/string-eq-loop/run.py --dry-run +python3 benchmarks/string-eq-loop/run.py --only python --repeats 3 --warmups 1 +python3 benchmarks/string-eq-loop/run.py --mode hot-loop --only slovo --only c --only rust +``` + +To include Slovo, build or point at a Glagol binary and make sure host Clang is +available: + +```bash +cargo build --manifest-path compiler/Cargo.toml --bin glagol +python3 benchmarks/string-eq-loop/run.py --glagol compiler/target/debug/glagol +``` + +The runner skips missing C/Rust/Slovo/Clojure/SBCL toolchains where possible. +Use `--only` multiple times to select implementations: + +```bash +python3 benchmarks/string-eq-loop/run.py --only slovo --only c --only rust --only clojure --only common_lisp +``` + +Clojure is detected with `clojure` on PATH, `CLOJURE`, or `CLOJURE_JAR`. +Common Lisp is detected with `sbcl` on PATH, `SBCL`, or `--sbcl`. + +## Comparison Method + +- The runner builds each implementation once before timing. The reported + numbers measure execution only, not compile time. +- Slovo timings use `glagol build`, which currently lowers to LLVM and then + invokes host `clang -O2` with `runtime/runtime.c`. +- C timings use `clang -O2 -std=c11`. +- Rust timings use `rustc -C opt-level=3 -C debuginfo=0`. +- The measured loop keeps one fixed immutable string array in local scope, + indexes it with a loop-carried `% 5` position, and compares the selected + string against a runtime-supplied ASCII target by content equality only. + +Timing is cold-process local-machine evidence only. Clojure timings include +JVM and Clojure startup, while Common Lisp timings include SBCL script +startup. + +Hot-loop mode is startup-amortized local evidence. It runs a larger loop count +and reports total time plus normalized time for the base `1000000` loop count. diff --git a/benchmarks/string-eq-loop/benchmark.json b/benchmarks/string-eq-loop/benchmark.json new file mode 100644 index 0000000..83d4310 --- /dev/null +++ b/benchmarks/string-eq-loop/benchmark.json @@ -0,0 +1,11 @@ +{ + "benchmark": "string-eq-loop", + "source_stem": "string_eq_loop", + "loop_count": 1000000, + "expected_checksum": "4600001", + "stdin": "1000000\n", + "hot_loop_count": 10000000, + "hot_expected_checksum": "46000001", + "hot_stdin": "10000000\n", + "run_args": ["omega"] +} diff --git a/benchmarks/string-eq-loop/c/string_eq_loop.c b/benchmarks/string-eq-loop/c/string_eq_loop.c new file mode 100644 index 0000000..1db8ee4 --- /dev/null +++ b/benchmarks/string-eq-loop/c/string_eq_loop.c @@ -0,0 +1,39 @@ +#include +#include +#include + +#define LOOP_COUNT 1000000 +#define EXPECTED_CHECKSUM 4600001 + +static int32_t configured_loop_count(void) { + int32_t value = LOOP_COUNT; + if (scanf("%d", &value) != 1 || value <= 0) { + return LOOP_COUNT; + } + return value; +} + +static const char *configured_target(int argc, char **argv) { + return argc > 1 ? argv[1] : "omega"; +} + +static int32_t string_eq_loop(int32_t limit, const char *target) { + static const char *words[5] = {"alpha", "omega", "delta", "omega", "sigma"}; + int32_t i = 0; + int32_t acc = 1; + + while (i < limit) { + const char *current = words[i % 5]; + acc = strcmp(current, target) == 0 ? acc + 7 : acc + 3; + acc = acc > 1000000000 ? acc - 1000000000 : acc; + i = i + 1; + } + + return acc; +} + +int main(int argc, char **argv) { + int32_t result = string_eq_loop(configured_loop_count(), configured_target(argc, argv)); + printf("%d\n", result); + return result == EXPECTED_CHECKSUM ? 0 : 1; +} diff --git a/benchmarks/string-eq-loop/clojure/string_eq_loop.clj b/benchmarks/string-eq-loop/clojure/string_eq_loop.clj new file mode 100644 index 0000000..6e4a050 --- /dev/null +++ b/benchmarks/string-eq-loop/clojure/string_eq_loop.clj @@ -0,0 +1,35 @@ +(set! *warn-on-reflection* true) +(set! *unchecked-math* :warn-on-boxed) + +(def loop-count 1000000) +(def expected-checksum 4600001) +(def words ["alpha" "omega" "delta" "omega" "sigma"]) + +(defn configured-loop-count [] + (try + (let [line (read-line) + value (Integer/parseInt (.trim ^String line))] + (if (pos? value) value loop-count)) + (catch Exception _ + loop-count))) + +(defn configured-target [] + (or (first *command-line-args*) "omega")) + +(defn string-eq-loop [limit target] + (loop [i 0 + acc 1] + (if (< i limit) + (let [current (nth words (rem i 5)) + next (if (= ^String current ^String target) + (+ acc 7) + (+ acc 3)) + bounded (if (> next 1000000000) + (- next 1000000000) + next)] + (recur (inc i) bounded)) + acc))) + +(let [result (string-eq-loop (configured-loop-count) (configured-target))] + (println result) + (System/exit (if (= result expected-checksum) 0 1))) diff --git a/benchmarks/string-eq-loop/common-lisp/string_eq_loop.lisp b/benchmarks/string-eq-loop/common-lisp/string_eq_loop.lisp new file mode 100644 index 0000000..2ce9948 --- /dev/null +++ b/benchmarks/string-eq-loop/common-lisp/string_eq_loop.lisp @@ -0,0 +1,43 @@ +(declaim (optimize (speed 3) (safety 0) (debug 0))) + +(defconstant +loop-count+ 1000000) +(defconstant +expected-checksum+ 4600001) +(defparameter +words+ #("alpha" "omega" "delta" "omega" "sigma")) + +(declaim (ftype (function () fixnum) configured-loop-count)) +(defun configured-loop-count () + (handler-case + (let ((line (read-line *standard-input* nil nil))) + (if line + (let ((value (parse-integer line :junk-allowed t))) + (if (> value 0) value +loop-count+)) + +loop-count+)) + (error () +loop-count+))) + +(declaim (ftype (function () string) configured-target)) +(defun configured-target () + (or (second sb-ext:*posix-argv*) "omega")) + +(declaim (ftype (function (fixnum string) fixnum) string-eq-loop)) +(defun string-eq-loop (limit target) + (declare (type fixnum limit) + (type string target)) + (loop with i of-type fixnum = 0 + with acc of-type fixnum = 1 + while (< i limit) + do (let* ((current (svref +words+ (rem i 5))) + (next (if (string= current target) + (+ acc 7) + (+ acc 3))) + (bounded (if (> next 1000000000) + (- next 1000000000) + next))) + (declare (type string current) + (type fixnum next bounded)) + (setf acc bounded + i (+ i 1))) + finally (return acc))) + +(let ((result (string-eq-loop (configured-loop-count) (configured-target)))) + (format t "~D~%" result) + (sb-ext:exit :code (if (= result +expected-checksum+) 0 1))) diff --git a/benchmarks/string-eq-loop/python/string_eq_loop.py b/benchmarks/string-eq-loop/python/string_eq_loop.py new file mode 100644 index 0000000..a583525 --- /dev/null +++ b/benchmarks/string-eq-loop/python/string_eq_loop.py @@ -0,0 +1,42 @@ +import sys + + +LOOP_COUNT = 1_000_000 +EXPECTED_CHECKSUM = 4_600_001 +WORDS = ["alpha", "omega", "delta", "omega", "sigma"] + + +def configured_loop_count() -> int: + try: + value = int(input().strip()) + except (EOFError, ValueError): + return LOOP_COUNT + + return value if value > 0 else LOOP_COUNT + + +def configured_target() -> str: + return sys.argv[1] if len(sys.argv) > 1 else "omega" + + +def string_eq_loop(limit: int, target: str) -> int: + i = 0 + acc = 1 + + while i < limit: + current = WORDS[i % 5] + acc = acc + 7 if current == target else acc + 3 + acc = acc - 1_000_000_000 if acc > 1_000_000_000 else acc + i += 1 + + return acc + + +def main() -> int: + result = string_eq_loop(configured_loop_count(), configured_target()) + print(result) + return 0 if result == EXPECTED_CHECKSUM else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/benchmarks/string-eq-loop/run.py b/benchmarks/string-eq-loop/run.py new file mode 100644 index 0000000..b90115e --- /dev/null +++ b/benchmarks/string-eq-loop/run.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +"""Run the local string-eq-loop benchmark scaffold.""" + +from pathlib import Path +import sys + +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from runner import main + + +if __name__ == "__main__": + raise SystemExit(main(Path(__file__).resolve().parent, sys.argv[1:])) diff --git a/benchmarks/string-eq-loop/rust/string_eq_loop.rs b/benchmarks/string-eq-loop/rust/string_eq_loop.rs new file mode 100644 index 0000000..33b3ff3 --- /dev/null +++ b/benchmarks/string-eq-loop/rust/string_eq_loop.rs @@ -0,0 +1,48 @@ +const LOOP_COUNT: i32 = 1_000_000; +const EXPECTED_CHECKSUM: i32 = 4_600_001; +const WORDS: [&str; 5] = ["alpha", "omega", "delta", "omega", "sigma"]; + +fn configured_loop_count() -> i32 { + let mut input = String::new(); + if std::io::stdin().read_line(&mut input).is_err() { + return LOOP_COUNT; + } + + input + .trim() + .parse::() + .ok() + .filter(|value| *value > 0) + .unwrap_or(LOOP_COUNT) +} + +fn configured_target() -> String { + std::env::args() + .nth(1) + .unwrap_or_else(|| "omega".to_string()) +} + +fn string_eq_loop(limit: i32, target: &str) -> i32 { + let mut i = 0; + let mut acc = 1; + + while i < limit { + let current = WORDS[(i % 5) as usize]; + acc = if current == target { acc + 7 } else { acc + 3 }; + acc = if acc > 1_000_000_000 { + acc - 1_000_000_000 + } else { + acc + }; + i += 1; + } + + acc +} + +fn main() { + let target = configured_target(); + let result = string_eq_loop(configured_loop_count(), &target); + println!("{}", result); + std::process::exit(if result == EXPECTED_CHECKSUM { 0 } else { 1 }); +} diff --git a/benchmarks/string-eq-loop/slovo.toml b/benchmarks/string-eq-loop/slovo.toml new file mode 100644 index 0000000..5924da0 --- /dev/null +++ b/benchmarks/string-eq-loop/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "string-eq-loop" +source_root = "src" +entry = "main" diff --git a/benchmarks/string-eq-loop/src/main.slo b/benchmarks/string-eq-loop/src/main.slo new file mode 100644 index 0000000..7ff67b1 --- /dev/null +++ b/benchmarks/string-eq-loop/src/main.slo @@ -0,0 +1,65 @@ +; Benchmark scaffold fixture for local-machine string-equality timing comparisons only. +; Keep LOOP_COUNT and EXPECTED_CHECKSUM aligned with the C/Rust/Python fixtures. +; The runner supplies the target through argv and the loop count through stdin +; or argv so the equality path stays runtime-configured. + +(module main) + +(fn loop_count () -> i32 + 1000000) + +(fn expected_checksum () -> i32 + 4600001) + +(fn parse_stdin_loop_count () -> (result i32 i32) + (let input (result string i32) (std.io.read_stdin_result)) + (match input + ((ok text) + (std.string.parse_i32_result text)) + ((err code) + (err i32 i32 code)))) + +(fn parse_arg_loop_count () -> (result i32 i32) + (std.string.parse_i32_result (std.process.arg 2))) + +(fn configured_stdin_loop_count () -> i32 + (let parsed_stdin (result i32 i32) (parse_stdin_loop_count)) + (if (is_ok parsed_stdin) + (unwrap_ok parsed_stdin) + (loop_count))) + +(fn configured_loop_count () -> i32 + (let parsed_arg (result i32 i32) (parse_arg_loop_count)) + (if (is_ok parsed_arg) + (unwrap_ok parsed_arg) + (configured_stdin_loop_count))) + +(fn target_text () -> string + (std.process.arg 1)) + +(fn words () -> (array string 5) + (array string "alpha" "omega" "delta" "omega" "sigma")) + +(fn string_eq_loop ((limit i32) (target string)) -> i32 + (let values (array string 5) (words)) + (var i i32 0) + (var acc i32 1) + (while (< i limit) + (set acc (if (= (index values (% i 5)) target) + (+ acc 7) + (+ acc 3))) + (set acc (if (> acc 1000000000) + (- acc 1000000000) + acc)) + (set i (+ i 1))) + acc) + +(fn main () -> i32 + (let result i32 (string_eq_loop (configured_loop_count) (target_text))) + (std.io.print_i32 result) + (if (= result (expected_checksum)) + 0 + 1)) + +(test "string equality loop checksum is deterministic" + (= (string_eq_loop (loop_count) "omega") (expected_checksum))) diff --git a/benchmarks/vec-i32-index-loop/.gitignore b/benchmarks/vec-i32-index-loop/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/benchmarks/vec-i32-index-loop/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/benchmarks/vec-i32-index-loop/README.md b/benchmarks/vec-i32-index-loop/README.md new file mode 100644 index 0000000..8a793f8 --- /dev/null +++ b/benchmarks/vec-i32-index-loop/README.md @@ -0,0 +1,73 @@ +# Vec I32 Index Loop Benchmark Scaffold + +Release: `exp-123`. + +This benchmark compares runtime-owned `(vec i32)` indexing plus scalar +accumulation across Slovo, C, Rust, Python, Clojure, and Common Lisp/SBCL on +the same machine. + +It is not a published benchmark result, performance threshold, optimizer +claim, or cross-machine comparison. + +## Files + +- `src/main.slo`: Slovo project benchmark fixture +- `c/vec_i32_index_loop.c`: C comparison implementation +- `rust/vec_i32_index_loop.rs`: Rust comparison implementation +- `python/vec_i32_index_loop.py`: Python comparison implementation +- `clojure/vec_i32_index_loop.clj`: Clojure comparison implementation +- `common-lisp/vec_i32_index_loop.lisp`: Common Lisp/SBCL comparison implementation +- `run.py`: build/run/timing harness + +All implementations print checksum `3875007` for loop count `1000000`. +Hot-loop mode uses loop count `10000000` and checksum `38750007`. The runner +supplies the loop count at runtime so native compilers cannot fold the loop +into a constant answer. + +## Commands + +Run from the Glagol repository root: + +```bash +python3 benchmarks/vec-i32-index-loop/run.py --list +python3 benchmarks/vec-i32-index-loop/run.py --dry-run +python3 benchmarks/vec-i32-index-loop/run.py --only python --repeats 3 --warmups 1 +python3 benchmarks/vec-i32-index-loop/run.py --mode hot-loop --only slovo --only c --only rust +``` + +To include Slovo, build or point at a Glagol binary and make sure host Clang is +available: + +```bash +cargo build --manifest-path compiler/Cargo.toml --bin glagol +python3 benchmarks/vec-i32-index-loop/run.py --glagol compiler/target/debug/glagol +``` + +The runner skips missing C/Rust/Slovo/Clojure/SBCL toolchains where possible. +Use `--only` multiple times to select implementations: + +```bash +python3 benchmarks/vec-i32-index-loop/run.py --only slovo --only c --only rust --only clojure --only common_lisp +``` + +Clojure is detected with `clojure` on PATH, `CLOJURE`, or `CLOJURE_JAR`. +Common Lisp is detected with `sbcl` on PATH, `SBCL`, or `--sbcl`. + +## Comparison Method + +- The runner builds each implementation once before timing. The reported + numbers measure execution only, not compile time. +- Slovo timings use `glagol build`, which currently lowers to LLVM and then + invokes host `clang -O2` with `runtime/runtime.c`. +- C timings use `clang -O2 -std=c11`. +- Rust timings use `rustc -C opt-level=3 -C debuginfo=0`. +- The measured loop keeps one immutable runtime-owned `i32` vector in local + scope, indexes it with a loop-carried `% 8` position, and accumulates the + selected value into an `i32` checksum. + +Timing is cold-process local-machine evidence only. Clojure timings include +JVM and Clojure startup, while Common Lisp timings include SBCL script +startup. + +Hot-loop mode is startup-amortized local evidence. It runs a larger loop count +and reports total time plus normalized time for the base `1000000` loop count. diff --git a/benchmarks/vec-i32-index-loop/benchmark.json b/benchmarks/vec-i32-index-loop/benchmark.json new file mode 100644 index 0000000..02c577e --- /dev/null +++ b/benchmarks/vec-i32-index-loop/benchmark.json @@ -0,0 +1,10 @@ +{ + "benchmark": "vec-i32-index-loop", + "source_stem": "vec_i32_index_loop", + "loop_count": 1000000, + "expected_checksum": "3875007", + "stdin": "1000000\n", + "hot_loop_count": 10000000, + "hot_expected_checksum": "38750007", + "hot_stdin": "10000000\n" +} diff --git a/benchmarks/vec-i32-index-loop/c/vec_i32_index_loop.c b/benchmarks/vec-i32-index-loop/c/vec_i32_index_loop.c new file mode 100644 index 0000000..653e05f --- /dev/null +++ b/benchmarks/vec-i32-index-loop/c/vec_i32_index_loop.c @@ -0,0 +1,60 @@ +#include +#include +#include + +#define LOOP_COUNT 1000000 +#define EXPECTED_CHECKSUM 3875007 + +typedef struct { + int32_t len; + int32_t *data; +} vec_i32; + +static int32_t configured_loop_count(void) { + int32_t value = LOOP_COUNT; + if (scanf("%d", &value) != 1 || value <= 0) { + return LOOP_COUNT; + } + return value; +} + +static vec_i32 make_digits(void) { + vec_i32 values; + values.len = 8; + values.data = malloc(sizeof(int32_t) * (size_t)values.len); + if (values.data == NULL) { + fputs("allocation failed\n", stderr); + exit(2); + } + + values.data[0] = 3; + values.data[1] = 1; + values.data[2] = 4; + values.data[3] = 1; + values.data[4] = 5; + values.data[5] = 9; + values.data[6] = 2; + values.data[7] = 6; + return values; +} + +static int32_t vec_i32_index_loop(int32_t limit) { + vec_i32 digits = make_digits(); + int32_t i = 0; + int32_t acc = 7; + + while (i < limit) { + acc = acc + digits.data[i % digits.len]; + acc = acc > 1000000000 ? acc - 1000000000 : acc; + i = i + 1; + } + + free(digits.data); + return acc; +} + +int main(void) { + int32_t result = vec_i32_index_loop(configured_loop_count()); + printf("%d\n", result); + return result == EXPECTED_CHECKSUM ? 0 : 1; +} diff --git a/benchmarks/vec-i32-index-loop/clojure/vec_i32_index_loop.clj b/benchmarks/vec-i32-index-loop/clojure/vec_i32_index_loop.clj new file mode 100644 index 0000000..28b5e9b --- /dev/null +++ b/benchmarks/vec-i32-index-loop/clojure/vec_i32_index_loop.clj @@ -0,0 +1,28 @@ +(set! *warn-on-reflection* true) +(set! *unchecked-math* :warn-on-boxed) + +(def loop-count 1000000) +(def expected-checksum 3875007) +(defn configured-loop-count [] + (try + (let [line (read-line) + value (Integer/parseInt (.trim ^String line))] + (if (pos? value) value loop-count)) + (catch Exception _ + loop-count))) + +(defn vec-i32-index-loop [limit] + (let [digits [3 1 4 1 5 9 2 6]] + (loop [i 0 + acc 7] + (if (< i limit) + (let [next (+ acc (nth digits (rem i 8))) + bounded (if (> next 1000000000) + (- next 1000000000) + next)] + (recur (inc i) bounded)) + acc)))) + +(let [result (vec-i32-index-loop (configured-loop-count))] + (println result) + (System/exit (if (= result expected-checksum) 0 1))) diff --git a/benchmarks/vec-i32-index-loop/common-lisp/vec_i32_index_loop.lisp b/benchmarks/vec-i32-index-loop/common-lisp/vec_i32_index_loop.lisp new file mode 100644 index 0000000..48a8323 --- /dev/null +++ b/benchmarks/vec-i32-index-loop/common-lisp/vec_i32_index_loop.lisp @@ -0,0 +1,33 @@ +(declaim (optimize (speed 3) (safety 0) (debug 0))) + +(defconstant +loop-count+ 1000000) +(defconstant +expected-checksum+ 3875007) +(declaim (ftype (function () fixnum) configured-loop-count)) +(defun configured-loop-count () + (handler-case + (let ((line (read-line *standard-input* nil nil))) + (if line + (let ((value (parse-integer line :junk-allowed t))) + (if (> value 0) value +loop-count+)) + +loop-count+)) + (error () +loop-count+))) + +(declaim (ftype (function (fixnum) fixnum) vec-i32-index-loop)) +(defun vec-i32-index-loop (limit) + (declare (type fixnum limit)) + (let ((digits #(3 1 4 1 5 9 2 6))) + (loop with i of-type fixnum = 0 + with acc of-type fixnum = 7 + while (< i limit) + do (let* ((next (+ acc (svref digits (rem i 8)))) + (bounded (if (> next 1000000000) + (- next 1000000000) + next))) + (declare (type fixnum next bounded)) + (setf acc bounded + i (+ i 1))) + finally (return acc)))) + +(let ((result (vec-i32-index-loop (configured-loop-count)))) + (format t "~D~%" result) + (sb-ext:exit :code (if (= result +expected-checksum+) 0 1))) diff --git a/benchmarks/vec-i32-index-loop/python/vec_i32_index_loop.py b/benchmarks/vec-i32-index-loop/python/vec_i32_index_loop.py new file mode 100644 index 0000000..8c69fba --- /dev/null +++ b/benchmarks/vec-i32-index-loop/python/vec_i32_index_loop.py @@ -0,0 +1,34 @@ +LOOP_COUNT = 1_000_000 +EXPECTED_CHECKSUM = 3_875_007 + + +def configured_loop_count() -> int: + try: + value = int(input().strip()) + except (EOFError, ValueError): + return LOOP_COUNT + + return value if value > 0 else LOOP_COUNT + + +def vec_i32_index_loop(limit: int) -> int: + digits = [3, 1, 4, 1, 5, 9, 2, 6] + i = 0 + acc = 7 + + while i < limit: + acc += digits[i % len(digits)] + acc = acc - 1_000_000_000 if acc > 1_000_000_000 else acc + i += 1 + + return acc + + +def main() -> int: + result = vec_i32_index_loop(configured_loop_count()) + print(result) + return 0 if result == EXPECTED_CHECKSUM else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/benchmarks/vec-i32-index-loop/run.py b/benchmarks/vec-i32-index-loop/run.py new file mode 100644 index 0000000..832b0ef --- /dev/null +++ b/benchmarks/vec-i32-index-loop/run.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +"""Run the local vec-i32-index-loop benchmark scaffold.""" + +from pathlib import Path +import sys + +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from runner import main + + +if __name__ == "__main__": + raise SystemExit(main(Path(__file__).resolve().parent, sys.argv[1:])) diff --git a/benchmarks/vec-i32-index-loop/rust/vec_i32_index_loop.rs b/benchmarks/vec-i32-index-loop/rust/vec_i32_index_loop.rs new file mode 100644 index 0000000..4ae6e7e --- /dev/null +++ b/benchmarks/vec-i32-index-loop/rust/vec_i32_index_loop.rs @@ -0,0 +1,40 @@ +const LOOP_COUNT: i32 = 1_000_000; +const EXPECTED_CHECKSUM: i32 = 3_875_007; + +fn configured_loop_count() -> i32 { + let mut input = String::new(); + if std::io::stdin().read_line(&mut input).is_err() { + return LOOP_COUNT; + } + + input + .trim() + .parse::() + .ok() + .filter(|value| *value > 0) + .unwrap_or(LOOP_COUNT) +} + +fn vec_i32_index_loop(limit: i32) -> i32 { + let digits = vec![3, 1, 4, 1, 5, 9, 2, 6]; + let mut i = 0; + let mut acc = 7; + + while i < limit { + acc += digits[(i % digits.len() as i32) as usize]; + acc = if acc > 1_000_000_000 { + acc - 1_000_000_000 + } else { + acc + }; + i += 1; + } + + acc +} + +fn main() { + let result = vec_i32_index_loop(configured_loop_count()); + println!("{}", result); + std::process::exit(if result == EXPECTED_CHECKSUM { 0 } else { 1 }); +} diff --git a/benchmarks/vec-i32-index-loop/slovo.toml b/benchmarks/vec-i32-index-loop/slovo.toml new file mode 100644 index 0000000..1b874b2 --- /dev/null +++ b/benchmarks/vec-i32-index-loop/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "vec-i32-index-loop" +source_root = "src" +entry = "main" diff --git a/benchmarks/vec-i32-index-loop/src/main.slo b/benchmarks/vec-i32-index-loop/src/main.slo new file mode 100644 index 0000000..9f848fb --- /dev/null +++ b/benchmarks/vec-i32-index-loop/src/main.slo @@ -0,0 +1,62 @@ +; Benchmark scaffold fixture for local-machine vec-i32-index timing comparisons only. +; Keep LOOP_COUNT and EXPECTED_CHECKSUM aligned with the C/Rust/Python fixtures. +; The runner supplies the loop count through stdin or argv so native compilers +; cannot fold the benchmark loop into a constant answer. + +(module main) + +(import std.vec_i32 (empty append2 append3 at)) + +(fn loop_count () -> i32 + 1000000) + +(fn expected_checksum () -> i32 + 3875007) + +(fn parse_stdin_loop_count () -> (result i32 i32) + (let input (result string i32) (std.io.read_stdin_result)) + (match input + ((ok text) + (std.string.parse_i32_result text)) + ((err code) + (err i32 i32 code)))) + +(fn parse_arg_loop_count () -> (result i32 i32) + (std.string.parse_i32_result (std.process.arg 1))) + +(fn configured_stdin_loop_count () -> i32 + (let parsed_stdin (result i32 i32) (parse_stdin_loop_count)) + (if (is_ok parsed_stdin) + (unwrap_ok parsed_stdin) + (loop_count))) + +(fn configured_loop_count () -> i32 + (let parsed_arg (result i32 i32) (parse_arg_loop_count)) + (if (is_ok parsed_arg) + (unwrap_ok parsed_arg) + (configured_stdin_loop_count))) + +(fn values () -> (vec i32) + (append2 (append3 (append3 (empty) 3 1 4) 1 5 9) 2 6)) + +(fn vec_i32_index_loop ((limit i32)) -> i32 + (let digits (vec i32) (values)) + (var i i32 0) + (var acc i32 7) + (while (< i limit) + (set acc (+ acc (at digits (% i 8)))) + (set acc (if (> acc 1000000000) + (- acc 1000000000) + acc)) + (set i (+ i 1))) + acc) + +(fn main () -> i32 + (let result i32 (vec_i32_index_loop (configured_loop_count))) + (std.io.print_i32 result) + (if (= result (expected_checksum)) + 0 + 1)) + +(test "vec i32 index loop checksum is deterministic" + (= (vec_i32_index_loop (loop_count)) (expected_checksum))) diff --git a/benchmarks/vec-string-eq-loop/.gitignore b/benchmarks/vec-string-eq-loop/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/benchmarks/vec-string-eq-loop/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/benchmarks/vec-string-eq-loop/README.md b/benchmarks/vec-string-eq-loop/README.md new file mode 100644 index 0000000..cb7be5e --- /dev/null +++ b/benchmarks/vec-string-eq-loop/README.md @@ -0,0 +1,73 @@ +# Vec String Equality Loop Benchmark Scaffold + +Release: `exp-123`. + +This benchmark compares runtime-owned `(vec string)` indexing plus exact ASCII +string content equality and `i32` checksum accumulation across Slovo, C, Rust, +Python, Clojure, and Common Lisp/SBCL on the same machine. + +It is not a published benchmark result, performance threshold, optimizer +claim, or cross-machine comparison. + +## Files + +- `src/main.slo`: Slovo project benchmark fixture +- `c/vec_string_eq_loop.c`: C comparison implementation +- `rust/vec_string_eq_loop.rs`: Rust comparison implementation +- `python/vec_string_eq_loop.py`: Python comparison implementation +- `clojure/vec_string_eq_loop.clj`: Clojure comparison implementation +- `common-lisp/vec_string_eq_loop.lisp`: Common Lisp/SBCL comparison implementation +- `run.py`: build/run/timing harness + +All implementations print checksum `4600001` for loop count `1000000` and +target string `omega`. Hot-loop mode uses loop count `10000000` and checksum +`46000001`. The runner supplies the loop count and target string at runtime. + +## Commands + +Run from the Glagol repository root: + +```bash +python3 benchmarks/vec-string-eq-loop/run.py --list +python3 benchmarks/vec-string-eq-loop/run.py --dry-run +python3 benchmarks/vec-string-eq-loop/run.py --only python --repeats 3 --warmups 1 +python3 benchmarks/vec-string-eq-loop/run.py --mode hot-loop --only slovo --only c --only rust +``` + +To include Slovo, build or point at a Glagol binary and make sure host Clang is +available: + +```bash +cargo build --manifest-path compiler/Cargo.toml --bin glagol +python3 benchmarks/vec-string-eq-loop/run.py --glagol compiler/target/debug/glagol +``` + +The runner skips missing C/Rust/Slovo/Clojure/SBCL toolchains where possible. +Use `--only` multiple times to select implementations: + +```bash +python3 benchmarks/vec-string-eq-loop/run.py --only slovo --only c --only rust --only clojure --only common_lisp +``` + +Clojure is detected with `clojure` on PATH, `CLOJURE`, or `CLOJURE_JAR`. +Common Lisp is detected with `sbcl` on PATH, `SBCL`, or `--sbcl`. + +## Comparison Method + +- The runner builds each implementation once before timing. The reported + numbers measure execution only, not compile time. +- Slovo timings use `glagol build`, which currently lowers to LLVM and then + invokes host `clang -O2` with `runtime/runtime.c`. +- C timings use `clang -O2 -std=c11`. +- Rust timings use `rustc -C opt-level=3 -C debuginfo=0`. +- The measured loop keeps one immutable runtime-owned string vector in local + scope, indexes it with a loop-carried `% 5` position, and compares the + selected string against a runtime-supplied ASCII target by content equality + only. + +Timing is cold-process local-machine evidence only. Clojure timings include +JVM and Clojure startup, while Common Lisp timings include SBCL script +startup. + +Hot-loop mode is startup-amortized local evidence. It runs a larger loop count +and reports total time plus normalized time for the base `1000000` loop count. diff --git a/benchmarks/vec-string-eq-loop/benchmark.json b/benchmarks/vec-string-eq-loop/benchmark.json new file mode 100644 index 0000000..a921997 --- /dev/null +++ b/benchmarks/vec-string-eq-loop/benchmark.json @@ -0,0 +1,11 @@ +{ + "benchmark": "vec-string-eq-loop", + "source_stem": "vec_string_eq_loop", + "loop_count": 1000000, + "expected_checksum": "4600001", + "stdin": "1000000\n", + "hot_loop_count": 10000000, + "hot_expected_checksum": "46000001", + "hot_stdin": "10000000\n", + "run_args": ["omega"] +} diff --git a/benchmarks/vec-string-eq-loop/c/vec_string_eq_loop.c b/benchmarks/vec-string-eq-loop/c/vec_string_eq_loop.c new file mode 100644 index 0000000..eb5bcc1 --- /dev/null +++ b/benchmarks/vec-string-eq-loop/c/vec_string_eq_loop.c @@ -0,0 +1,63 @@ +#include +#include +#include +#include + +#define LOOP_COUNT 1000000 +#define EXPECTED_CHECKSUM 4600001 + +typedef struct { + int32_t len; + const char **data; +} vec_string; + +static int32_t configured_loop_count(void) { + int32_t value = LOOP_COUNT; + if (scanf("%d", &value) != 1 || value <= 0) { + return LOOP_COUNT; + } + return value; +} + +static const char *configured_target(int argc, char **argv) { + return argc > 1 ? argv[1] : "omega"; +} + +static vec_string make_words(void) { + vec_string values; + values.len = 5; + values.data = malloc(sizeof(char *) * (size_t)values.len); + if (values.data == NULL) { + fputs("allocation failed\n", stderr); + exit(2); + } + + values.data[0] = "alpha"; + values.data[1] = "omega"; + values.data[2] = "delta"; + values.data[3] = "omega"; + values.data[4] = "sigma"; + return values; +} + +static int32_t vec_string_eq_loop(int32_t limit, const char *target) { + vec_string words = make_words(); + int32_t i = 0; + int32_t acc = 1; + + while (i < limit) { + const char *current = words.data[i % words.len]; + acc = strcmp(current, target) == 0 ? acc + 7 : acc + 3; + acc = acc > 1000000000 ? acc - 1000000000 : acc; + i = i + 1; + } + + free(words.data); + return acc; +} + +int main(int argc, char **argv) { + int32_t result = vec_string_eq_loop(configured_loop_count(), configured_target(argc, argv)); + printf("%d\n", result); + return result == EXPECTED_CHECKSUM ? 0 : 1; +} diff --git a/benchmarks/vec-string-eq-loop/clojure/vec_string_eq_loop.clj b/benchmarks/vec-string-eq-loop/clojure/vec_string_eq_loop.clj new file mode 100644 index 0000000..6ddfc8f --- /dev/null +++ b/benchmarks/vec-string-eq-loop/clojure/vec_string_eq_loop.clj @@ -0,0 +1,34 @@ +(set! *warn-on-reflection* true) +(set! *unchecked-math* :warn-on-boxed) + +(def loop-count 1000000) +(def expected-checksum 4600001) +(defn configured-loop-count [] + (try + (let [line (read-line) + value (Integer/parseInt (.trim ^String line))] + (if (pos? value) value loop-count)) + (catch Exception _ + loop-count))) + +(defn configured-target [] + (or (first *command-line-args*) "omega")) + +(defn vec-string-eq-loop [limit target] + (let [words ["alpha" "omega" "delta" "omega" "sigma"]] + (loop [i 0 + acc 1] + (if (< i limit) + (let [current (nth words (rem i 5)) + next (if (= ^String current ^String target) + (+ acc 7) + (+ acc 3)) + bounded (if (> next 1000000000) + (- next 1000000000) + next)] + (recur (inc i) bounded)) + acc)))) + +(let [result (vec-string-eq-loop (configured-loop-count) (configured-target))] + (println result) + (System/exit (if (= result expected-checksum) 0 1))) diff --git a/benchmarks/vec-string-eq-loop/common-lisp/vec_string_eq_loop.lisp b/benchmarks/vec-string-eq-loop/common-lisp/vec_string_eq_loop.lisp new file mode 100644 index 0000000..b7e4934 --- /dev/null +++ b/benchmarks/vec-string-eq-loop/common-lisp/vec_string_eq_loop.lisp @@ -0,0 +1,42 @@ +(declaim (optimize (speed 3) (safety 0) (debug 0))) + +(defconstant +loop-count+ 1000000) +(defconstant +expected-checksum+ 4600001) +(declaim (ftype (function () fixnum) configured-loop-count)) +(defun configured-loop-count () + (handler-case + (let ((line (read-line *standard-input* nil nil))) + (if line + (let ((value (parse-integer line :junk-allowed t))) + (if (> value 0) value +loop-count+)) + +loop-count+)) + (error () +loop-count+))) + +(declaim (ftype (function () string) configured-target)) +(defun configured-target () + (or (second sb-ext:*posix-argv*) "omega")) + +(declaim (ftype (function (fixnum string) fixnum) vec-string-eq-loop)) +(defun vec-string-eq-loop (limit target) + (declare (type fixnum limit) + (type string target)) + (let ((words #("alpha" "omega" "delta" "omega" "sigma"))) + (loop with i of-type fixnum = 0 + with acc of-type fixnum = 1 + while (< i limit) + do (let* ((current (svref words (rem i 5))) + (next (if (string= current target) + (+ acc 7) + (+ acc 3))) + (bounded (if (> next 1000000000) + (- next 1000000000) + next))) + (declare (type string current) + (type fixnum next bounded)) + (setf acc bounded + i (+ i 1))) + finally (return acc)))) + +(let ((result (vec-string-eq-loop (configured-loop-count) (configured-target)))) + (format t "~D~%" result) + (sb-ext:exit :code (if (= result +expected-checksum+) 0 1))) diff --git a/benchmarks/vec-string-eq-loop/python/vec_string_eq_loop.py b/benchmarks/vec-string-eq-loop/python/vec_string_eq_loop.py new file mode 100644 index 0000000..d44cedb --- /dev/null +++ b/benchmarks/vec-string-eq-loop/python/vec_string_eq_loop.py @@ -0,0 +1,42 @@ +import sys + + +LOOP_COUNT = 1_000_000 +EXPECTED_CHECKSUM = 4_600_001 + + +def configured_loop_count() -> int: + try: + value = int(input().strip()) + except (EOFError, ValueError): + return LOOP_COUNT + + return value if value > 0 else LOOP_COUNT + + +def configured_target() -> str: + return sys.argv[1] if len(sys.argv) > 1 else "omega" + + +def vec_string_eq_loop(limit: int, target: str) -> int: + words = ["alpha", "omega", "delta", "omega", "sigma"] + i = 0 + acc = 1 + + while i < limit: + current = words[i % len(words)] + acc = acc + 7 if current == target else acc + 3 + acc = acc - 1_000_000_000 if acc > 1_000_000_000 else acc + i += 1 + + return acc + + +def main() -> int: + result = vec_string_eq_loop(configured_loop_count(), configured_target()) + print(result) + return 0 if result == EXPECTED_CHECKSUM else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/benchmarks/vec-string-eq-loop/run.py b/benchmarks/vec-string-eq-loop/run.py new file mode 100644 index 0000000..49ef9d0 --- /dev/null +++ b/benchmarks/vec-string-eq-loop/run.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +"""Run the local vec-string-eq-loop benchmark scaffold.""" + +from pathlib import Path +import sys + +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from runner import main + + +if __name__ == "__main__": + raise SystemExit(main(Path(__file__).resolve().parent, sys.argv[1:])) diff --git a/benchmarks/vec-string-eq-loop/rust/vec_string_eq_loop.rs b/benchmarks/vec-string-eq-loop/rust/vec_string_eq_loop.rs new file mode 100644 index 0000000..d67d242 --- /dev/null +++ b/benchmarks/vec-string-eq-loop/rust/vec_string_eq_loop.rs @@ -0,0 +1,48 @@ +const LOOP_COUNT: i32 = 1_000_000; +const EXPECTED_CHECKSUM: i32 = 4_600_001; + +fn configured_loop_count() -> i32 { + let mut input = String::new(); + if std::io::stdin().read_line(&mut input).is_err() { + return LOOP_COUNT; + } + + input + .trim() + .parse::() + .ok() + .filter(|value| *value > 0) + .unwrap_or(LOOP_COUNT) +} + +fn configured_target() -> String { + std::env::args() + .nth(1) + .unwrap_or_else(|| "omega".to_string()) +} + +fn vec_string_eq_loop(limit: i32, target: &str) -> i32 { + let words = vec!["alpha", "omega", "delta", "omega", "sigma"]; + let mut i = 0; + let mut acc = 1; + + while i < limit { + let current = words[(i % words.len() as i32) as usize]; + acc = if current == target { acc + 7 } else { acc + 3 }; + acc = if acc > 1_000_000_000 { + acc - 1_000_000_000 + } else { + acc + }; + i += 1; + } + + acc +} + +fn main() { + let target = configured_target(); + let result = vec_string_eq_loop(configured_loop_count(), &target); + println!("{}", result); + std::process::exit(if result == EXPECTED_CHECKSUM { 0 } else { 1 }); +} diff --git a/benchmarks/vec-string-eq-loop/slovo.toml b/benchmarks/vec-string-eq-loop/slovo.toml new file mode 100644 index 0000000..79b4686 --- /dev/null +++ b/benchmarks/vec-string-eq-loop/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "vec-string-eq-loop" +source_root = "src" +entry = "main" diff --git a/benchmarks/vec-string-eq-loop/src/main.slo b/benchmarks/vec-string-eq-loop/src/main.slo new file mode 100644 index 0000000..230fad9 --- /dev/null +++ b/benchmarks/vec-string-eq-loop/src/main.slo @@ -0,0 +1,67 @@ +; Benchmark scaffold fixture for local-machine vec-string-equality timing comparisons only. +; Keep LOOP_COUNT and EXPECTED_CHECKSUM aligned with the C/Rust/Python fixtures. +; The runner supplies the target through argv and the loop count through stdin +; or argv so the equality path stays runtime-configured. + +(module main) + +(import std.vec_string (empty append2 append3 at)) + +(fn loop_count () -> i32 + 1000000) + +(fn expected_checksum () -> i32 + 4600001) + +(fn parse_stdin_loop_count () -> (result i32 i32) + (let input (result string i32) (std.io.read_stdin_result)) + (match input + ((ok text) + (std.string.parse_i32_result text)) + ((err code) + (err i32 i32 code)))) + +(fn parse_arg_loop_count () -> (result i32 i32) + (std.string.parse_i32_result (std.process.arg 2))) + +(fn configured_stdin_loop_count () -> i32 + (let parsed_stdin (result i32 i32) (parse_stdin_loop_count)) + (if (is_ok parsed_stdin) + (unwrap_ok parsed_stdin) + (loop_count))) + +(fn configured_loop_count () -> i32 + (let parsed_arg (result i32 i32) (parse_arg_loop_count)) + (if (is_ok parsed_arg) + (unwrap_ok parsed_arg) + (configured_stdin_loop_count))) + +(fn target_text () -> string + (std.process.arg 1)) + +(fn words () -> (vec string) + (append2 (append3 (empty) "alpha" "omega" "delta") "omega" "sigma")) + +(fn vec_string_eq_loop ((limit i32) (target string)) -> i32 + (let values (vec string) (words)) + (var i i32 0) + (var acc i32 1) + (while (< i limit) + (set acc (if (= (at values (% i 5)) target) + (+ acc 7) + (+ acc 3))) + (set acc (if (> acc 1000000000) + (- acc 1000000000) + acc)) + (set i (+ i 1))) + acc) + +(fn main () -> i32 + (let result i32 (vec_string_eq_loop (configured_loop_count) (target_text))) + (std.io.print_i32 result) + (if (= result (expected_checksum)) + 0 + 1)) + +(test "vec string equality loop checksum is deterministic" + (= (vec_string_eq_loop (loop_count) "omega") (expected_checksum))) diff --git a/compiler/Cargo.lock b/compiler/Cargo.lock new file mode 100644 index 0000000..01cdf20 --- /dev/null +++ b/compiler/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "glagol" +version = "1.0.0-beta" diff --git a/compiler/Cargo.toml b/compiler/Cargo.toml new file mode 100644 index 0000000..4abf633 --- /dev/null +++ b/compiler/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "glagol" +version = "1.0.0-beta" +edition = "2021" +description = "Glagol, the first compiler for the Slovo language" +license = "MIT OR Apache-2.0" + +[dependencies] diff --git a/compiler/src/ast.rs b/compiler/src/ast.rs new file mode 100644 index 0000000..85893fe --- /dev/null +++ b/compiler/src/ast.rs @@ -0,0 +1,256 @@ +use crate::{token::Span, types::Type}; + +#[derive(Debug, Clone)] +pub struct Program { + pub module: String, + pub enums: Vec, + pub structs: Vec, + pub c_imports: Vec, + pub functions: Vec, + pub tests: Vec, +} + +#[derive(Debug, Clone)] +pub struct EnumDecl { + pub name: String, + pub name_span: Span, + pub variants: Vec, + pub span: Span, +} + +#[derive(Debug, Clone)] +pub struct EnumVariantDecl { + pub name: String, + pub name_span: Span, + pub payload_ty: Option, + pub payload_ty_span: Option, +} + +#[derive(Debug, Clone)] +pub struct StructDecl { + pub name: String, + pub name_span: Span, + pub fields: Vec, + pub span: Span, +} + +#[derive(Debug, Clone)] +pub struct StructField { + pub name: String, + pub name_span: Span, + pub ty: Type, + pub ty_span: Span, +} + +#[derive(Debug, Clone)] +pub struct CImportDecl { + pub name: String, + pub name_span: Span, + pub params: Vec, + pub return_type: Type, + pub return_type_span: Span, + pub span: Span, +} + +#[derive(Debug, Clone)] +pub struct Function { + pub name: String, + pub params: Vec, + pub return_type: Type, + pub return_type_span: Span, + pub body: Vec, + pub span: Span, +} + +#[derive(Debug, Clone)] +pub struct Test { + pub name: String, + pub name_span: Span, + pub body: Vec, + pub span: Span, +} + +#[derive(Debug, Clone)] +pub struct Param { + pub name: String, + pub name_span: Span, + pub ty: Type, + pub ty_span: Span, +} + +#[derive(Debug, Clone)] +pub struct Expr { + pub kind: ExprKind, + pub span: Span, +} + +#[derive(Debug, Clone)] +pub enum ExprKind { + Int(i32), + Int64(i64), + UInt32(u32), + UInt64(u64), + Float(f64), + Bool(bool), + String(String), + Var(String), + StructInit { + name: String, + name_span: Span, + fields: Vec, + }, + ArrayInit { + elem_ty: Type, + elem_ty_span: Span, + elements: Vec, + }, + OptionSome { + payload_ty: Type, + payload_ty_span: Span, + value: Box, + }, + OptionNone { + payload_ty: Type, + payload_ty_span: Span, + }, + ResultOk { + ok_ty: Type, + ok_ty_span: Span, + err_ty: Type, + err_ty_span: Span, + value: Box, + }, + ResultErr { + ok_ty: Type, + ok_ty_span: Span, + err_ty: Type, + err_ty_span: Span, + value: Box, + }, + OptionIsSome { + value: Box, + }, + OptionIsNone { + value: Box, + }, + OptionUnwrapSome { + value: Box, + }, + ResultIsOk { + source_name: String, + value: Box, + }, + ResultIsErr { + source_name: String, + value: Box, + }, + ResultUnwrapOk { + source_name: String, + value: Box, + }, + ResultUnwrapErr { + source_name: String, + value: Box, + }, + EnumVariant { + enum_name: String, + variant: String, + name_span: Span, + args: Vec, + }, + FieldAccess { + value: Box, + field: String, + field_span: Span, + }, + Index { + array: Box, + index: Box, + }, + Local { + mutable: bool, + name: String, + name_span: Span, + ty: Type, + expr: Box, + }, + Set { + name: String, + name_span: Span, + expr: Box, + }, + Binary { + op: BinaryOp, + left: Box, + right: Box, + }, + If { + condition: Box, + then_expr: Box, + else_expr: Box, + }, + Match { + subject: Box, + arms: Vec, + }, + While { + condition: Box, + body: Vec, + }, + Unsafe { + body: Vec, + }, + Call { + name: String, + name_span: Span, + args: Vec, + }, +} + +#[derive(Debug, Clone)] +pub struct MatchArm { + pub pattern: MatchPattern, + pub body: Vec, + pub span: Span, +} + +#[derive(Debug, Clone)] +pub struct MatchPattern { + pub kind: MatchPatternKind, + pub binding: Option, + pub binding_span: Option, + pub span: Span, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum MatchPatternKind { + Some, + None, + Ok, + Err, + EnumVariant { enum_name: String, variant: String }, +} + +#[derive(Debug, Clone)] +pub struct StructInitField { + pub name: String, + pub name_span: Span, + pub expr: Expr, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum BinaryOp { + Add, + Sub, + Mul, + Div, + Rem, + BitAnd, + BitOr, + BitXor, + Eq, + Lt, + Gt, + Le, + Ge, +} diff --git a/compiler/src/check.rs b/compiler/src/check.rs new file mode 100644 index 0000000..14f6781 --- /dev/null +++ b/compiler/src/check.rs @@ -0,0 +1,4951 @@ +use std::collections::{hash_map::Entry, HashMap, HashSet}; + +use crate::{ + ast::{ + BinaryOp, EnumDecl, Expr, ExprKind, Function, MatchArm, MatchPatternKind, Program, + StructDecl, StructInitField, Test, + }, + diag::Diagnostic, + lower, std_runtime, + token::Span, + types::Type, + unsafe_ops, +}; + +#[derive(Debug, Clone)] +pub struct CheckedProgram { + pub module: String, + pub enums: Vec, + pub structs: Vec, + pub c_imports: Vec, + pub functions: Vec, + pub tests: Vec, +} + +#[derive(Debug, Clone)] +pub struct CheckedEnum { + pub name: String, + pub variants: Vec, + pub span: Span, + pub file: String, +} + +#[derive(Debug, Clone)] +pub struct CheckedEnumVariant { + pub name: String, + pub payload_ty: Option, +} + +#[derive(Debug, Clone)] +pub struct CheckedStruct { + pub name: String, + pub fields: Vec<(String, Type)>, + pub span: Span, + pub file: String, +} + +#[derive(Debug, Clone)] +pub struct CheckedCImport { + pub name: String, + pub params: Vec<(String, Type)>, + pub return_type: Type, + pub span: Span, + pub file: String, +} + +#[derive(Debug, Clone)] +pub struct CheckedFunction { + pub name: String, + pub params: Vec<(String, Type)>, + pub return_type: Type, + pub body: Vec, + pub span: Span, + pub file: String, +} + +#[derive(Debug, Clone)] +pub struct CheckedTest { + pub name: String, + pub name_span: Span, + pub body: Vec, + pub span: Span, + pub file: String, +} + +#[derive(Debug, Clone)] +pub struct TExpr { + pub ty: Type, + pub span: Span, + pub kind: TExprKind, +} + +#[derive(Debug, Clone)] +pub enum TExprKind { + Int(i32), + Int64(i64), + UInt32(u32), + UInt64(u64), + Float(f64), + Bool(bool), + String(String), + Var(String), + StructInit { + name: String, + fields: Vec<(String, TExpr)>, + }, + ArrayInit { + elements: Vec, + }, + OptionSome { + value: Box, + }, + OptionNone, + ResultOk { + value: Box, + }, + ResultErr { + value: Box, + }, + OptionIsSome { + value: Box, + }, + OptionIsNone { + value: Box, + }, + OptionUnwrapSome { + value: Box, + }, + ResultIsOk { + source_name: String, + value: Box, + }, + ResultIsErr { + source_name: String, + value: Box, + }, + ResultUnwrapOk { + source_name: String, + value: Box, + }, + ResultUnwrapErr { + source_name: String, + value: Box, + }, + EnumVariant { + enum_name: String, + variant: String, + discriminant: i32, + payload: Option>, + }, + FieldAccess { + value: Box, + field: String, + }, + Index { + array: Box, + index: Box, + }, + Local { + mutable: bool, + name: String, + initializer: Box, + }, + Set { + name: String, + expr: Box, + }, + Binary { + op: BinaryOp, + left: Box, + right: Box, + }, + If { + condition: Box, + then_expr: Box, + else_expr: Box, + }, + Match { + subject: Box, + arms: Vec, + }, + While { + condition: Box, + body: Vec, + }, + Unsafe { + body: Vec, + }, + Call { + name: String, + args: Vec, + }, +} + +#[derive(Debug, Clone)] +pub struct TMatchArm { + pub pattern: MatchPatternKind, + pub binding: Option, + pub discriminant: Option, + pub body: Vec, +} + +#[derive(Debug, Clone)] +struct FnSig { + params: Vec, + return_type: Type, + foreign: bool, +} + +#[derive(Debug, Clone)] +pub struct ExternalFunction { + pub name: String, + pub params: Vec, + pub return_type: Type, + pub foreign: bool, +} + +#[derive(Debug, Clone)] +pub struct ExternalStruct { + pub name: String, + pub fields: Vec<(String, Type)>, + pub span: Span, +} + +#[derive(Debug, Clone)] +pub struct ExternalEnum { + pub name: String, + pub variants: Vec, + pub span: Span, +} + +#[derive(Debug, Clone)] +pub struct ExternalEnumVariant { + pub name: String, + pub name_span: Span, + pub payload_ty: Option, + pub payload_ty_span: Option, +} + +#[derive(Debug, Clone)] +struct StructSig { + span: Span, + fields: Vec, + fields_by_name: HashMap, +} + +#[derive(Debug, Clone)] +struct EnumSig { + span: Span, + variants: Vec, + variants_by_name: HashMap, +} + +#[derive(Debug, Clone)] +struct EnumVariantSig { + name: String, + name_span: Span, + payload_ty: Option, + payload_ty_span: Option, +} + +#[derive(Debug, Clone)] +struct StructFieldSig { + name: String, + name_span: Span, + ty_span: Span, + ty: Type, +} + +#[derive(Debug, Clone)] +struct Binding { + ty: Type, + mutable: bool, + kind: BindingKind, + span: Span, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +enum BindingKind { + Param, + Local, + MatchPayload, +} + +pub fn check_program(file: &str, program: Program) -> Result> { + check_program_inner(file, program, &[], &[], &[], false) +} + +pub fn check_program_with_imports( + file: &str, + program: Program, + external_functions: &[ExternalFunction], + external_structs: &[ExternalStruct], + external_enums: &[ExternalEnum], +) -> Result> { + check_program_inner( + file, + program, + external_functions, + external_structs, + external_enums, + true, + ) +} + +fn check_program_inner( + file: &str, + program: Program, + external_functions: &[ExternalFunction], + external_structs: &[ExternalStruct], + external_enums: &[ExternalEnum], + project_resolution: bool, +) -> Result> { + let mut errors = Vec::new(); + let mut functions = HashMap::new(); + let mut structs = HashMap::new(); + let mut enums = HashMap::new(); + + if !project_resolution { + insert_builtin_functions(&mut functions, false); + } + + for function in &program.functions { + let sig = FnSig { + params: function.params.iter().map(|p| p.ty.clone()).collect(), + return_type: function.return_type.clone(), + foreign: false, + }; + + if is_reserved_callable_name(&function.name) && !functions.contains_key(&function.name) { + errors.push( + Diagnostic::new( + file, + "DuplicateFunction", + format!("compiler-known callable `{}` is reserved", function.name), + ) + .with_span(function.span), + ); + } + + if functions.insert(function.name.clone(), sig).is_some() { + errors.push( + Diagnostic::new( + file, + "DuplicateFunction", + format!("duplicate function `{}`", function.name), + ) + .with_span(function.span), + ); + } + } + + for function in external_functions { + functions.insert( + function.name.clone(), + FnSig { + params: function.params.clone(), + return_type: function.return_type.clone(), + foreign: function.foreign, + }, + ); + } + + for import in &program.c_imports { + if is_reserved_callable_name(&import.name) { + errors.push( + Diagnostic::new( + file, + "ReservedName", + format!("C import name `{}` is reserved", import.name), + ) + .with_span(import.name_span), + ); + continue; + } + + let sig = FnSig { + params: import.params.iter().map(|p| p.ty.clone()).collect(), + return_type: import.return_type.clone(), + foreign: true, + }; + if functions.insert(import.name.clone(), sig).is_some() { + errors.push( + Diagnostic::new( + file, + "DuplicateTopLevelName", + format!("duplicate top-level callable `{}`", import.name), + ) + .with_span(import.name_span), + ); + } + } + + if project_resolution { + insert_builtin_functions(&mut functions, true); + } + + let mut declared_struct_names = HashMap::new(); + for struct_decl in &program.structs { + declared_struct_names + .entry(struct_decl.name.clone()) + .or_insert(struct_decl.name_span); + } + for struct_decl in external_structs { + declared_struct_names + .entry(struct_decl.name.clone()) + .or_insert(struct_decl.span); + } + + let mut checked_structs = Vec::new(); + + let mut checked_enums = Vec::new(); + + for enum_decl in &program.enums { + match check_enum_decl(file, enum_decl, &functions, &declared_struct_names) { + Ok((checked, sig)) => match enums.entry(enum_decl.name.clone()) { + Entry::Vacant(entry) => { + entry.insert(sig); + checked_enums.push(checked); + } + Entry::Occupied(entry) => errors.push( + Diagnostic::new( + file, + "DuplicateEnum", + format!("duplicate enum `{}`", enum_decl.name), + ) + .with_span(enum_decl.name_span) + .related("original enum declaration", entry.get().span), + ), + }, + Err(mut errs) => errors.append(&mut errs), + } + } + + for enum_decl in external_enums { + enums.insert(enum_decl.name.clone(), external_enum_sig(enum_decl)); + } + + for struct_decl in &program.structs { + if let Some(import) = program + .c_imports + .iter() + .find(|import| import.name == struct_decl.name) + { + errors.push( + Diagnostic::new( + file, + "DuplicateTopLevelName", + format!("C import `{}` conflicts with a struct", import.name), + ) + .with_span(import.name_span) + .related("struct declaration", struct_decl.name_span), + ); + } + match check_struct_decl( + file, + struct_decl, + &functions, + &declared_struct_names, + &enums, + ) { + Ok((checked, sig)) => match structs.entry(struct_decl.name.clone()) { + Entry::Vacant(entry) => { + entry.insert(sig); + checked_structs.push(checked); + } + Entry::Occupied(entry) => errors.push( + Diagnostic::new( + file, + "DuplicateStruct", + format!("duplicate struct `{}`", struct_decl.name), + ) + .with_span(struct_decl.name_span) + .related("original struct declaration", entry.get().span), + ), + }, + Err(mut errs) => errors.append(&mut errs), + } + } + + for struct_decl in external_structs { + let mut fields = Vec::new(); + let mut fields_by_name = HashMap::new(); + for (index, (name, ty)) in struct_decl.fields.iter().enumerate() { + fields_by_name.insert(name.clone(), index); + fields.push(StructFieldSig { + name: name.clone(), + name_span: struct_decl.span, + ty_span: struct_decl.span, + ty: ty.clone(), + }); + } + structs.insert( + struct_decl.name.clone(), + StructSig { + span: struct_decl.span, + fields, + fields_by_name, + }, + ); + } + + errors.extend(validate_struct_field_cycles(file, &structs)); + errors.extend(validate_enum_payload_struct_cycles(file, &enums, &structs)); + + let mut checked_functions = Vec::new(); + for enum_decl in &program.enums { + if let Some(import) = program + .c_imports + .iter() + .find(|import| import.name == enum_decl.name) + { + errors.push( + Diagnostic::new( + file, + "DuplicateTopLevelName", + format!("C import `{}` conflicts with an enum", import.name), + ) + .with_span(import.name_span) + .related("enum declaration", enum_decl.name_span), + ); + } + } + let checked_c_imports = program + .c_imports + .iter() + .map(|import| CheckedCImport { + name: import.name.clone(), + params: import + .params + .iter() + .map(|param| (param.name.clone(), param.ty.clone())) + .collect(), + return_type: import.return_type.clone(), + span: import.span, + file: file.to_string(), + }) + .collect(); + + for function in program.functions { + match check_function(file, function, &functions, &structs, &enums) { + Ok(function) => checked_functions.push(function), + Err(mut errs) => errors.append(&mut errs), + } + } + + let mut checked_tests = Vec::new(); + + for test in program.tests { + match check_test(file, test, &functions, &structs, &enums) { + Ok(test) => checked_tests.push(test), + Err(mut errs) => errors.append(&mut errs), + } + } + + if errors.is_empty() { + Ok(CheckedProgram { + module: program.module, + enums: checked_enums, + structs: checked_structs, + c_imports: checked_c_imports, + functions: checked_functions, + tests: checked_tests, + }) + } else { + Err(errors) + } +} + +fn insert_builtin_functions(functions: &mut HashMap, keep_existing: bool) { + for function in std_runtime::FUNCTIONS { + if keep_existing && functions.contains_key(function.source_name) { + continue; + } + functions.insert( + function.source_name.to_string(), + FnSig { + params: function + .params + .iter() + .map(|param| param.to_type()) + .collect(), + return_type: function.return_type.to_type(), + foreign: false, + }, + ); + } +} + +fn is_reserved_callable_name(name: &str) -> bool { + std_runtime::is_reserved_name(name) || unsafe_ops::is_reserved_head(name) +} + +fn check_struct_decl( + file: &str, + struct_decl: &StructDecl, + functions: &HashMap, + declared_struct_names: &HashMap, + enums: &HashMap, +) -> Result<(CheckedStruct, StructSig), Vec> { + let mut errors = Vec::new(); + let mut fields: Vec = Vec::new(); + let mut fields_by_name = HashMap::new(); + + if functions.contains_key(&struct_decl.name) || is_reserved_callable_name(&struct_decl.name) { + errors.push( + Diagnostic::new( + file, + "StructNameConflictsCallable", + format!( + "struct `{}` conflicts with a function or compiler intrinsic", + struct_decl.name + ), + ) + .with_span(struct_decl.name_span) + .hint("choose a struct name distinct from functions and compiler intrinsics"), + ); + } + + if struct_decl.fields.is_empty() { + errors.push( + Diagnostic::new( + file, + "EmptyStructUnsupported", + "first-pass structs must declare at least one field", + ) + .with_span(struct_decl.span) + .hint("declare at least one `i32` field"), + ); + } + + for field in &struct_decl.fields { + match fields_by_name.entry(field.name.clone()) { + Entry::Vacant(entry) => { + let index = fields.len(); + entry.insert(index); + } + Entry::Occupied(entry) => { + let original = &fields[*entry.get()]; + errors.push( + Diagnostic::new( + file, + "DuplicateStructField", + format!( + "duplicate field `{}` in struct `{}`", + field.name, struct_decl.name + ), + ) + .with_span(field.name_span) + .related("original field declaration", original.name_span), + ); + continue; + } + } + + let direct_primitive_field = matches!( + field.ty, + Type::I32 | Type::I64 | Type::U32 | Type::U64 | Type::F64 | Type::Bool | Type::String + ); + let direct_enum_field = is_known_enum_type(&field.ty, enums); + let direct_struct_field = matches!( + &field.ty, + Type::Named(name) if declared_struct_names.contains_key(name) + ); + if contains_enum_type(&field.ty, enums) && !direct_enum_field && !is_array_type(&field.ty) { + errors.push( + Diagnostic::new( + file, + "UnsupportedEnumContainer", + "enum values in struct field containers are not supported", + ) + .with_span(field.ty_span) + .hint("store enum values directly as struct fields"), + ); + } else if is_array_type(&field.ty) { + if let Err(err) = + check_array_type_decl(file, &field.ty, field.ty_span, declared_struct_names, enums) + { + errors.push(err); + } + } else if is_vector_type(&field.ty) { + if let Err(err) = check_vector_type(file, &field.ty, field.ty_span) { + errors.push(err); + } + } else if is_option_result_type(&field.ty) { + if let Err(err) = check_option_result_type(file, &field.ty, field.ty_span) { + errors.push(err); + } + } else if !direct_primitive_field && !direct_enum_field && !direct_struct_field { + errors.push( + Diagnostic::new( + file, + "UnsupportedStructFieldType", + supported_struct_field_message(), + ) + .with_span(field.ty_span) + .expected(supported_struct_field_expected()) + .found(field.ty.to_string()), + ); + } + + fields.push(StructFieldSig { + name: field.name.clone(), + name_span: field.name_span, + ty_span: field.ty_span, + ty: field.ty.clone(), + }); + } + + if errors.is_empty() { + Ok(( + CheckedStruct { + name: struct_decl.name.clone(), + fields: fields + .iter() + .map(|field| (field.name.clone(), field.ty.clone())) + .collect(), + span: struct_decl.span, + file: file.to_string(), + }, + StructSig { + span: struct_decl.name_span, + fields, + fields_by_name, + }, + )) + } else { + Err(errors) + } +} + +fn check_enum_decl( + file: &str, + enum_decl: &EnumDecl, + functions: &HashMap, + declared_struct_names: &HashMap, +) -> Result<(CheckedEnum, EnumSig), Vec> { + let mut errors = Vec::new(); + let mut variants: Vec = Vec::new(); + let mut variants_by_name = HashMap::new(); + + if functions.contains_key(&enum_decl.name) || is_reserved_callable_name(&enum_decl.name) { + errors.push( + Diagnostic::new( + file, + "EnumNameConflictsCallable", + format!( + "enum `{}` conflicts with a function or compiler intrinsic", + enum_decl.name + ), + ) + .with_span(enum_decl.name_span) + .hint("choose an enum name distinct from functions and compiler intrinsics"), + ); + } + + if enum_decl.variants.is_empty() { + errors.push( + Diagnostic::new( + file, + "EmptyEnumUnsupported", + "enum declarations must contain at least one variant", + ) + .with_span(enum_decl.span) + .hint("declare at least one payloadless or unary direct scalar/string payload variant"), + ); + } + + let mut enum_payload_ty: Option<(Type, Span)> = None; + for variant in &enum_decl.variants { + match variants_by_name.entry(variant.name.clone()) { + Entry::Vacant(entry) => { + entry.insert(variants.len()); + } + Entry::Occupied(entry) => { + let original = &variants[*entry.get()]; + errors.push( + Diagnostic::new( + file, + "DuplicateEnumVariant", + format!( + "duplicate variant `{}` in enum `{}`", + variant.name, enum_decl.name + ), + ) + .with_span(variant.name_span) + .related("original variant declaration", original.name_span), + ); + continue; + } + } + + if let Some(payload_ty) = &variant.payload_ty { + if !enum_payload_type_supported(payload_ty, declared_struct_names) { + errors.push( + Diagnostic::new( + file, + "UnsupportedEnumPayloadType", + supported_enum_payload_message(), + ) + .with_span(variant.payload_ty_span.unwrap_or(variant.name_span)) + .expected(supported_enum_payload_expected()) + .found(payload_ty.to_string()) + .hint("use a direct scalar/string payload, a known non-recursive struct payload, or keep the variant payloadless"), + ); + } else if let Some((expected_ty, expected_span)) = &enum_payload_ty { + if payload_ty != expected_ty { + errors.push( + Diagnostic::new( + file, + "MixedEnumPayloadTypesUnsupported", + "enum payload variants in one enum must use the same payload type", + ) + .with_span(variant.payload_ty_span.unwrap_or(variant.name_span)) + .related("first payload type in this enum", *expected_span) + .expected(expected_ty.to_string()) + .found(payload_ty.to_string()) + .hint("split mixed payload kinds into separate enums"), + ); + } + } else { + enum_payload_ty = Some(( + payload_ty.clone(), + variant.payload_ty_span.unwrap_or(variant.name_span), + )); + } + } + + variants.push(EnumVariantSig { + name: variant.name.clone(), + name_span: variant.name_span, + payload_ty: variant.payload_ty.clone(), + payload_ty_span: variant.payload_ty_span, + }); + } + + if errors.is_empty() { + Ok(( + CheckedEnum { + name: enum_decl.name.clone(), + variants: variants + .iter() + .map(|variant| CheckedEnumVariant { + name: variant.name.clone(), + payload_ty: variant.payload_ty.clone(), + }) + .collect(), + span: enum_decl.span, + file: file.to_string(), + }, + EnumSig { + span: enum_decl.name_span, + variants, + variants_by_name, + }, + )) + } else { + Err(errors) + } +} + +fn enum_payload_type_supported(ty: &Type, declared_struct_names: &HashMap) -> bool { + matches!( + ty, + Type::I32 | Type::I64 | Type::U32 | Type::U64 | Type::F64 | Type::Bool | Type::String + ) || matches!(ty, Type::Named(name) if declared_struct_names.contains_key(name)) +} + +fn supported_enum_payload_message() -> &'static str { + "enum payload variants support only unary direct `i32`, `i64`, `u32`, `u64`, `f64`, `bool`, `string`, or known non-recursive struct payloads" +} + +fn supported_enum_payload_expected() -> &'static str { + "i32, i64, u32, u64, f64, bool, string, or known non-recursive struct type" +} + +fn enum_payload_equality_supported(ty: &Type, enums: &HashMap) -> bool { + match ty { + Type::Named(name) => enums + .get(name) + .and_then(|sig| { + sig.variants + .iter() + .find_map(|variant| variant.payload_ty.as_ref()) + }) + .map(enum_scalar_string_payload_type_supported) + .unwrap_or(true), + _ => false, + } +} + +fn enum_scalar_string_payload_type_supported(ty: &Type) -> bool { + matches!( + ty, + Type::I32 | Type::I64 | Type::U32 | Type::U64 | Type::F64 | Type::Bool | Type::String + ) +} + +fn validate_enum_payload_struct_cycles( + file: &str, + enums: &HashMap, + structs: &HashMap, +) -> Vec { + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + enum Node { + Enum(String), + Struct(String), + } + + impl Node { + fn name(&self) -> &str { + match self { + Self::Enum(name) | Self::Struct(name) => name, + } + } + } + + fn visit( + file: &str, + node: Node, + enums: &HashMap, + structs: &HashMap, + stack: &mut Vec, + visited: &mut HashSet, + errors: &mut Vec, + ) { + if !visited.insert(node.clone()) { + return; + } + + stack.push(node.clone()); + + match &node { + Node::Enum(enum_name) => { + let Some(sig) = enums.get(enum_name) else { + stack.pop(); + return; + }; + + for variant in &sig.variants { + let Some(Type::Named(struct_name)) = &variant.payload_ty else { + continue; + }; + let Some(struct_sig) = structs.get(struct_name) else { + errors.push( + Diagnostic::new( + file, + "UnsupportedEnumPayloadType", + supported_enum_payload_message(), + ) + .with_span(variant.payload_ty_span.unwrap_or(variant.name_span)) + .expected(supported_enum_payload_expected()) + .found(struct_name.clone()) + .hint("use a direct scalar/string payload, a checked non-recursive struct payload, or keep the variant payloadless"), + ); + continue; + }; + + let target = Node::Struct(struct_name.clone()); + if let Some(index) = stack.iter().position(|entry| entry == &target) { + let mut cycle = stack[index..] + .iter() + .map(|entry| entry.name().to_string()) + .collect::>(); + cycle.push(struct_name.clone()); + errors.push( + Diagnostic::new( + file, + "RecursiveEnumPayloadStructUnsupported", + format!( + "recursive enum payload/struct cycles are not supported (`{}`)", + cycle.join(" -> ") + ), + ) + .with_span(variant.payload_ty_span.unwrap_or(variant.name_span)) + .related("recursive struct declaration", struct_sig.span) + .hint("break the cycle by removing the struct payload edge or a direct enum/struct field edge"), + ); + continue; + } + + visit(file, target, enums, structs, stack, visited, errors); + } + } + Node::Struct(struct_name) => { + let Some(sig) = structs.get(struct_name) else { + stack.pop(); + return; + }; + + for field in &sig.fields { + let target = match &field.ty { + Type::Named(target) if structs.contains_key(target) => { + Node::Struct(target.clone()) + } + Type::Named(target) if enums.contains_key(target) => { + Node::Enum(target.clone()) + } + _ => continue, + }; + + let related_span = match &target { + Node::Struct(target_name) => structs + .get(target_name) + .map(|target_sig| ("recursive struct declaration", target_sig.span)), + Node::Enum(target_name) => enums + .get(target_name) + .map(|target_sig| ("recursive enum declaration", target_sig.span)), + }; + + if let Some(index) = stack.iter().position(|entry| entry == &target) { + let mut cycle = stack[index..] + .iter() + .map(|entry| entry.name().to_string()) + .collect::>(); + cycle.push(target.name().to_string()); + let diagnostic = Diagnostic::new( + file, + "RecursiveEnumPayloadStructUnsupported", + format!( + "recursive enum payload/struct cycles are not supported (`{}`)", + cycle.join(" -> ") + ), + ) + .with_span(field.ty_span) + .hint("break the cycle by removing the struct payload edge or a direct enum/struct field edge"); + errors.push(if let Some((label, span)) = related_span { + diagnostic.related(label, span) + } else { + diagnostic + }); + continue; + } + + visit(file, target, enums, structs, stack, visited, errors); + } + } + } + + stack.pop(); + } + + let mut errors = Vec::new(); + let mut visited = HashSet::new(); + let mut enum_names = enums.keys().cloned().collect::>(); + enum_names.sort(); + for name in enum_names { + visit( + file, + Node::Enum(name), + enums, + structs, + &mut Vec::new(), + &mut visited, + &mut errors, + ); + } + errors +} + +fn external_enum_sig(enum_decl: &ExternalEnum) -> EnumSig { + let mut variants = Vec::new(); + let mut variants_by_name = HashMap::new(); + + for variant in &enum_decl.variants { + variants_by_name.insert(variant.name.clone(), variants.len()); + variants.push(EnumVariantSig { + name: variant.name.clone(), + name_span: variant.name_span, + payload_ty: variant.payload_ty.clone(), + payload_ty_span: variant.payload_ty_span, + }); + } + + EnumSig { + span: enum_decl.span, + variants, + variants_by_name, + } +} + +pub fn print_checked_program(program: &CheckedProgram) -> String { + let mut output = String::new(); + + output.push_str("program "); + output.push_str(&program.module); + output.push('\n'); + + for enum_decl in &program.enums { + output.push_str(" enum "); + output.push_str(&enum_decl.name); + output.push('\n'); + for variant in &enum_decl.variants { + output.push_str(" variant "); + output.push_str(&variant.name); + if let Some(payload_ty) = &variant.payload_ty { + output.push(' '); + output.push_str(&payload_ty.to_string()); + } + output.push('\n'); + } + } + + for struct_decl in &program.structs { + output.push_str(" struct "); + output.push_str(&struct_decl.name); + output.push('\n'); + for (name, ty) in &struct_decl.fields { + output.push_str(" field "); + output.push_str(name); + output.push_str(": "); + output.push_str(&ty.to_string()); + output.push('\n'); + } + } + + for import in &program.c_imports { + output.push_str(" import_c "); + output.push_str(&import.name); + output.push('('); + for (index, (name, ty)) in import.params.iter().enumerate() { + if index > 0 { + output.push_str(", "); + } + output.push_str(name); + output.push_str(": "); + output.push_str(&ty.to_string()); + } + output.push_str(") -> "); + output.push_str(&import.return_type.to_string()); + output.push('\n'); + } + + for function in &program.functions { + output.push_str(" fn "); + output.push_str(&function.name); + output.push('('); + for (index, (name, ty)) in function.params.iter().enumerate() { + if index > 0 { + output.push_str(", "); + } + output.push_str(name); + output.push_str(": "); + output.push_str(&ty.to_string()); + } + output.push_str(") -> "); + output.push_str(&function.return_type.to_string()); + output.push('\n'); + + for expr in &function.body { + write_checked_expr(expr, 2, &mut output); + } + } + + for test in &program.tests { + output.push_str(" test \""); + for ch in test.name.chars() { + output.extend(ch.escape_default()); + } + output.push_str("\"\n"); + for expr in &test.body { + write_checked_expr(expr, 2, &mut output); + } + } + + output +} + +fn write_checked_expr(expr: &TExpr, indent: usize, output: &mut String) { + output.push_str(&" ".repeat(indent)); + + match &expr.kind { + TExprKind::Int(value) => { + output.push_str("int "); + output.push_str(&value.to_string()); + } + TExprKind::Int64(value) => { + output.push_str("i64 "); + output.push_str(&value.to_string()); + } + TExprKind::UInt32(value) => { + output.push_str("u32 "); + output.push_str(&value.to_string()); + } + TExprKind::UInt64(value) => { + output.push_str("u64 "); + output.push_str(&value.to_string()); + } + TExprKind::Float(value) => { + output.push_str("float "); + output.push_str(&value.to_string()); + } + TExprKind::Bool(value) => { + output.push_str("bool "); + output.push_str(&value.to_string()); + } + TExprKind::String(value) => { + output.push_str("string \""); + for ch in value.chars() { + output.extend(ch.escape_default()); + } + output.push('"'); + } + TExprKind::Var(name) => { + output.push_str("var "); + output.push_str(name); + } + TExprKind::StructInit { name, fields } => { + output.push_str("construct "); + output.push_str(name); + write_checked_type(&expr.ty, output); + for (field, value) in fields { + output.push_str(&" ".repeat(indent + 1)); + output.push_str("field "); + output.push_str(field); + output.push('\n'); + write_checked_expr(value, indent + 2, output); + } + return; + } + TExprKind::ArrayInit { elements } => { + output.push_str("array"); + write_checked_type(&expr.ty, output); + for element in elements { + write_checked_expr(element, indent + 1, output); + } + return; + } + TExprKind::OptionSome { value } => { + output.push_str("some"); + write_checked_type(&expr.ty, output); + write_checked_expr(value, indent + 1, output); + return; + } + TExprKind::OptionNone => { + output.push_str("none"); + } + TExprKind::ResultOk { value } => { + output.push_str("ok"); + write_checked_type(&expr.ty, output); + write_checked_expr(value, indent + 1, output); + return; + } + TExprKind::ResultErr { value } => { + output.push_str("err"); + write_checked_type(&expr.ty, output); + write_checked_expr(value, indent + 1, output); + return; + } + TExprKind::OptionIsSome { value } => { + output.push_str("is_some"); + write_checked_type(&expr.ty, output); + write_checked_expr(value, indent + 1, output); + return; + } + TExprKind::OptionIsNone { value } => { + output.push_str("is_none"); + write_checked_type(&expr.ty, output); + write_checked_expr(value, indent + 1, output); + return; + } + TExprKind::OptionUnwrapSome { value } => { + output.push_str("unwrap_some"); + write_checked_type(&expr.ty, output); + write_checked_expr(value, indent + 1, output); + return; + } + TExprKind::ResultIsOk { source_name, value } => { + output.push_str(source_name); + write_checked_type(&expr.ty, output); + write_checked_expr(value, indent + 1, output); + return; + } + TExprKind::ResultIsErr { source_name, value } => { + output.push_str(source_name); + write_checked_type(&expr.ty, output); + write_checked_expr(value, indent + 1, output); + return; + } + TExprKind::ResultUnwrapOk { source_name, value } => { + output.push_str(source_name); + write_checked_type(&expr.ty, output); + write_checked_expr(value, indent + 1, output); + return; + } + TExprKind::ResultUnwrapErr { source_name, value } => { + output.push_str(source_name); + write_checked_type(&expr.ty, output); + write_checked_expr(value, indent + 1, output); + return; + } + TExprKind::EnumVariant { + enum_name, + variant, + discriminant, + payload, + } => { + output.push_str("enum-variant "); + output.push_str(enum_name); + output.push('.'); + output.push_str(variant); + output.push_str(" #"); + output.push_str(&discriminant.to_string()); + if let Some(payload) = payload { + output.push_str(" payload"); + write_checked_type(&expr.ty, output); + write_checked_expr(payload, indent + 1, output); + return; + } + } + TExprKind::FieldAccess { value, field } => { + output.push_str("field-access "); + output.push_str(field); + write_checked_type(&expr.ty, output); + write_checked_expr(value, indent + 1, output); + return; + } + TExprKind::Index { array, index } => { + output.push_str("index"); + write_checked_type(&expr.ty, output); + write_checked_expr(array, indent + 1, output); + write_checked_expr(index, indent + 1, output); + return; + } + TExprKind::Local { + mutable, + name, + initializer, + } => { + output.push_str(if *mutable { "local var " } else { "local let " }); + output.push_str(name); + write_checked_type(&expr.ty, output); + write_checked_expr(initializer, indent + 1, output); + return; + } + TExprKind::Set { name, expr: value } => { + output.push_str("set "); + output.push_str(name); + write_checked_type(&expr.ty, output); + write_checked_expr(value, indent + 1, output); + return; + } + TExprKind::Binary { op, left, right } => { + output.push_str("binary "); + output.push_str(lower::binary_op_name(*op)); + write_checked_type(&expr.ty, output); + write_checked_expr(left, indent + 1, output); + write_checked_expr(right, indent + 1, output); + return; + } + TExprKind::If { + condition, + then_expr, + else_expr, + } => { + output.push_str("if"); + write_checked_type(&expr.ty, output); + write_checked_expr(condition, indent + 1, output); + write_checked_expr(then_expr, indent + 1, output); + write_checked_expr(else_expr, indent + 1, output); + return; + } + TExprKind::Match { subject, arms } => { + output.push_str("match"); + write_checked_type(&expr.ty, output); + output.push_str(&" ".repeat(indent + 1)); + output.push_str("subject\n"); + write_checked_expr(subject, indent + 2, output); + for arm in arms { + output.push_str(&" ".repeat(indent + 1)); + output.push_str("arm "); + output.push_str(&lower::match_pattern_name(&arm.pattern)); + if let Some(binding) = &arm.binding { + output.push(' '); + output.push_str(binding); + } + output.push('\n'); + for expr in &arm.body { + write_checked_expr(expr, indent + 2, output); + } + } + return; + } + TExprKind::While { condition, body } => { + output.push_str("while"); + write_checked_type(&expr.ty, output); + write_checked_expr(condition, indent + 1, output); + for expr in body { + write_checked_expr(expr, indent + 1, output); + } + return; + } + TExprKind::Unsafe { body } => { + output.push_str("unsafe"); + write_checked_type(&expr.ty, output); + for expr in body { + write_checked_expr(expr, indent + 1, output); + } + return; + } + TExprKind::Call { name, args } => { + output.push_str("call "); + output.push_str(name); + write_checked_type(&expr.ty, output); + for arg in args { + write_checked_expr(arg, indent + 1, output); + } + return; + } + } + + write_checked_type(&expr.ty, output); +} + +fn write_checked_type(ty: &Type, output: &mut String) { + output.push_str(" : "); + output.push_str(&ty.to_string()); + output.push('\n'); +} + +fn check_function( + file: &str, + function: Function, + functions: &HashMap, + structs: &HashMap, + enums: &HashMap, +) -> Result> { + let mut locals = HashMap::new(); + let mut errors = Vec::new(); + + for param in &function.params { + if is_reserved_callable_name(¶m.name) { + errors.push( + Diagnostic::new( + file, + "ParameterShadowsCallable", + format!( + "parameter `{}` conflicts with a compiler-known callable name", + param.name + ), + ) + .with_span(param.name_span) + .hint("choose a parameter name distinct from compiler-known callable names"), + ); + } + + locals.insert( + param.name.clone(), + Binding { + ty: param.ty.clone(), + mutable: false, + kind: BindingKind::Param, + span: param.name_span, + }, + ); + } + + validate_unit_signature_types(file, &function, &mut errors); + validate_named_signature_types(file, &function, structs, enums, &mut errors); + validate_array_signature_types(file, &function, structs, enums, &mut errors); + validate_vector_signature_types(file, &function, &mut errors); + validate_option_result_signature_types(file, &function, &mut errors); + validate_enum_container_signature_types(file, &function, enums, &mut errors); + let source_body_is_empty = function.body.is_empty(); + let body = check_body( + file, + &function.body, + locals, + functions, + structs, + enums, + false, + &mut errors, + ); + + if source_body_is_empty { + errors.push( + Diagnostic::new( + file, + "EmptyFunction", + format!("function `{}` has no body", function.name), + ) + .with_span(function.span), + ); + } else if errors.is_empty() { + if let Some(last) = body.last() { + if last.ty != function.return_type { + errors.push( + Diagnostic::new( + file, + "ReturnTypeMismatch", + format!("function `{}` returns wrong type", function.name), + ) + .with_span(function.span) + .expected(function.return_type.to_string()) + .found(last.ty.to_string()), + ); + } + } + } + + if errors.is_empty() { + Ok(CheckedFunction { + name: function.name, + params: function + .params + .into_iter() + .map(|p| (p.name, p.ty)) + .collect(), + return_type: function.return_type, + body, + span: function.span, + file: file.to_string(), + }) + } else { + Err(errors) + } +} + +fn check_test( + file: &str, + test: Test, + functions: &HashMap, + structs: &HashMap, + enums: &HashMap, +) -> Result> { + let locals = HashMap::new(); + let mut errors = Vec::new(); + let body = check_body( + file, + &test.body, + locals, + functions, + structs, + enums, + false, + &mut errors, + ); + + if errors.is_empty() { + if let Some(expr) = body.last() { + if expr.ty != Type::Bool { + errors.push( + Diagnostic::new( + file, + "TestExpressionNotBool", + format!("test `{}` must evaluate to bool", test.name), + ) + .with_span(expr.span) + .expected(Type::Bool.to_string()) + .found(expr.ty.to_string()), + ); + } + } + } + + if errors.is_empty() { + Ok(CheckedTest { + name: test.name, + name_span: test.name_span, + body, + span: test.span, + file: file.to_string(), + }) + } else { + Err(errors) + } +} + +fn validate_array_signature_types( + file: &str, + function: &Function, + structs: &HashMap, + enums: &HashMap, + errors: &mut Vec, +) { + for param in &function.params { + if matches!(¶m.ty, Type::Array(_, _)) { + if let Err(err) = check_array_type(file, ¶m.ty, param.ty_span, structs, enums) { + errors.push(err); + } + } + } + + if matches!(&function.return_type, Type::Array(_, _)) { + if let Err(err) = check_array_type( + file, + &function.return_type, + function.return_type_span, + structs, + enums, + ) { + errors.push(err); + } + } +} + +fn validate_vector_signature_types(file: &str, function: &Function, errors: &mut Vec) { + for param in &function.params { + if matches!(¶m.ty, Type::Vec(_)) { + if let Err(err) = check_vector_type(file, ¶m.ty, param.ty_span) { + errors.push(err); + } + } + } + + if matches!(&function.return_type, Type::Vec(_)) { + if let Err(err) = check_vector_type(file, &function.return_type, function.return_type_span) + { + errors.push(err); + } + } +} + +fn validate_unit_signature_types(file: &str, function: &Function, errors: &mut Vec) { + for param in &function.params { + if param.ty == Type::Unit { + errors.push( + Diagnostic::new( + file, + "UnsupportedUnitSignatureType", + "function parameter type `unit` is unsupported", + ) + .with_span(param.ty_span) + .expected("non-unit function parameter type") + .found(Type::Unit.to_string()) + .hint("`unit` is reserved for compiler/runtime unit-producing forms"), + ); + } + } + + if function.return_type == Type::Unit { + errors.push( + Diagnostic::new( + file, + "UnsupportedUnitSignatureType", + "function return type `unit` is unsupported", + ) + .with_span(function.return_type_span) + .expected("non-unit function return type") + .found(Type::Unit.to_string()) + .hint("`unit` is reserved for compiler/runtime unit-producing forms"), + ); + } +} + +fn validate_named_signature_types( + file: &str, + function: &Function, + structs: &HashMap, + enums: &HashMap, + errors: &mut Vec, +) { + for param in &function.params { + if let Type::Named(name) = ¶m.ty { + if !structs.contains_key(name) && !enums.contains_key(name) { + errors.push(unknown_named_type( + file, + param.ty_span, + name, + "function parameter", + )); + } + } + } + + if let Type::Named(name) = &function.return_type { + if !structs.contains_key(name) && !enums.contains_key(name) { + errors.push(unknown_named_type( + file, + function.return_type_span, + name, + "function return", + )); + } + } +} + +fn validate_option_result_signature_types( + file: &str, + function: &Function, + errors: &mut Vec, +) { + for param in &function.params { + if let Err(err) = check_option_result_type(file, ¶m.ty, param.ty_span) { + errors.push(err); + } + } + + if let Err(err) = + check_option_result_type(file, &function.return_type, function.return_type_span) + { + errors.push(err); + } +} + +fn validate_enum_container_signature_types( + file: &str, + function: &Function, + enums: &HashMap, + errors: &mut Vec, +) { + for param in &function.params { + if !matches!(param.ty, Type::Named(_) | Type::Array(_, _)) + && contains_enum_type(¶m.ty, enums) + { + errors.push(unsupported_enum_container(file, param.ty_span, ¶m.ty)); + } + } + + if !matches!(function.return_type, Type::Named(_) | Type::Array(_, _)) + && contains_enum_type(&function.return_type, enums) + { + errors.push(unsupported_enum_container( + file, + function.return_type_span, + &function.return_type, + )); + } +} + +fn is_known_struct_type(ty: &Type, structs: &HashMap) -> bool { + matches!(ty, Type::Named(name) if structs.contains_key(name)) +} + +fn is_known_enum_type(ty: &Type, enums: &HashMap) -> bool { + matches!(ty, Type::Named(name) if enums.contains_key(name)) +} + +fn is_enum_type(ty: &Type, enums: &HashMap) -> bool { + is_known_enum_type(ty, enums) +} + +fn is_array_type(ty: &Type) -> bool { + matches!(ty, Type::Array(_, _)) +} + +fn is_vector_type(ty: &Type) -> bool { + matches!(ty, Type::Vec(_)) +} + +fn is_vec_i32_type(ty: &Type) -> bool { + matches!(ty, Type::Vec(inner) if **inner == Type::I32) +} + +fn is_vec_i64_type(ty: &Type) -> bool { + matches!(ty, Type::Vec(inner) if **inner == Type::I64) +} + +fn is_vec_f64_type(ty: &Type) -> bool { + matches!(ty, Type::Vec(inner) if **inner == Type::F64) +} + +fn is_vec_bool_type(ty: &Type) -> bool { + matches!(ty, Type::Vec(inner) if **inner == Type::Bool) +} + +fn is_vec_string_type(ty: &Type) -> bool { + matches!(ty, Type::Vec(inner) if **inner == Type::String) +} + +fn is_option_result_type(ty: &Type) -> bool { + matches!(ty, Type::Option(_) | Type::Result(_, _)) +} + +fn check_body( + file: &str, + body: &[Expr], + mut locals: HashMap, + functions: &HashMap, + structs: &HashMap, + enums: &HashMap, + unsafe_context: bool, + errors: &mut Vec, +) -> Vec { + let mut checked = Vec::new(); + + for expr in body { + match &expr.kind { + ExprKind::Local { + mutable, + name, + name_span, + ty, + expr: initializer, + } => { + let checked_initializer = match check_expr( + file, + initializer, + &locals, + functions, + structs, + enums, + unsafe_context, + ) { + Ok(initializer) => Some(initializer), + Err(err) => { + errors.push(err); + None + } + }; + + let local_type_supported = + match check_local_type(file, ty, *mutable, *name_span, structs, enums) { + Ok(()) => true, + Err(err) => { + errors.push(err); + false + } + }; + + if let Some(existing) = locals.get(name) { + match existing.kind { + BindingKind::Param => { + errors.push( + Diagnostic::new( + file, + "LocalRedeclaresParameter", + format!("local `{}` redeclares a parameter", name), + ) + .with_span(*name_span) + .hint("choose a local name distinct from parameters"), + ); + } + BindingKind::MatchPayload => { + errors.push( + Diagnostic::new( + file, + "MatchBindingCollision", + format!( + "local `{}` collides with a match payload binding", + name + ), + ) + .with_span(*name_span) + .related("match payload binding", existing.span), + ); + } + BindingKind::Local => { + errors.push( + Diagnostic::new( + file, + "DuplicateLocal", + format!("duplicate local declaration `{}`", name), + ) + .with_span(*name_span) + .related("original local declaration", existing.span), + ); + } + } + } + + if functions.contains_key(name) || is_reserved_callable_name(name) { + errors.push( + Diagnostic::new( + file, + "LocalShadowsCallable", + format!( + "local `{}` conflicts with a function or compiler intrinsic", + name + ), + ) + .with_span(*name_span) + .hint( + "choose a local name distinct from functions and compiler intrinsics", + ), + ); + } + + if structs.contains_key(name) { + errors.push( + Diagnostic::new( + file, + "LocalShadowsStruct", + format!("local `{}` conflicts with a struct", name), + ) + .with_span(*name_span) + .hint("choose a local name distinct from structs"), + ); + } + + if enums.contains_key(name) { + errors.push( + Diagnostic::new( + file, + "LocalShadowsEnum", + format!("local `{}` conflicts with an enum", name), + ) + .with_span(*name_span) + .hint("choose a local name distinct from enums"), + ); + } + + let Some(initializer) = checked_initializer else { + continue; + }; + + if local_type_supported && initializer.ty != *ty { + errors.push( + Diagnostic::new( + file, + "TypeMismatch", + format!("local `{}` initializer has the wrong type", name), + ) + .with_span(initializer.span) + .expected(ty.to_string()) + .found(initializer.ty.to_string()), + ); + continue; + } + + if local_type_supported && !locals.contains_key(name) { + locals.insert( + name.clone(), + Binding { + ty: ty.clone(), + mutable: *mutable, + kind: BindingKind::Local, + span: *name_span, + }, + ); + + checked.push(TExpr { + ty: Type::Unit, + span: expr.span, + kind: TExprKind::Local { + mutable: *mutable, + name: name.clone(), + initializer: Box::new(initializer), + }, + }); + } + } + _ => match check_expr( + file, + expr, + &locals, + functions, + structs, + enums, + unsafe_context, + ) { + Ok(texpr) => checked.push(texpr), + Err(err) => errors.push(err), + }, + } + } + + checked +} + +fn check_local_type( + file: &str, + ty: &Type, + mutable: bool, + span: Span, + structs: &HashMap, + enums: &HashMap, +) -> Result<(), Diagnostic> { + match ty { + Type::I32 => Ok(()), + Type::Bool => Ok(()), + Type::I64 => Ok(()), + Type::U32 => Ok(()), + Type::U64 => Ok(()), + Type::F64 => Ok(()), + Type::String => Ok(()), + Type::Array(_, _) => { + check_array_type(file, ty, span, structs, enums)?; + if mutable { + return Err(Diagnostic::new( + file, + "MutableArrayLocalUnsupported", + "first-pass arrays can be stored only in immutable locals", + ) + .with_span(span) + .hint("declare array locals with `let`")); + } + Ok(()) + } + Type::Vec(_) => { + if contains_enum_type(ty, enums) { + return Err(Diagnostic::new( + file, + "UnsupportedEnumContainer", + "enum values in vector containers are not supported", + ) + .with_span(span) + .hint("store enum values directly in immutable locals")); + } + check_vector_type(file, ty, span)?; + Ok(()) + } + Type::Option(_) | Type::Result(_, _) => { + if contains_enum_type(ty, enums) { + return Err(Diagnostic::new( + file, + "UnsupportedEnumContainer", + "enum values in option/result containers are not supported", + ) + .with_span(span) + .hint("store enum values directly in immutable locals")); + } + check_option_result_type(file, ty, span)?; + Ok(()) + } + ty if is_known_struct_type(ty, structs) => Ok(()), + ty if is_known_enum_type(ty, enums) => Ok(()), + Type::Named(name) => Err(unknown_named_type(file, span, name, "local declaration")), + _ => Err(Diagnostic::new( + file, + "UnsupportedLocalType", + "local variables support `i32`, `bool`, `i64`, `u32`, `u64`, `f64`, `string`, immutable direct-scalar-or-string arrays, concrete vectors, option/result, known struct values, and enum values", + ) + .with_span(span) + .expected("i32, bool, i64, u32, u64, f64, string, (array i32 N), (array i64 N), (array u32 N), (array u64 N), (array f64 N), (array bool N), (array string N), (vec i32), (vec i64), (vec f64), (vec bool), (vec string), option/result, known struct, or enum") + .found(ty.to_string())), + } +} + +fn unknown_named_type(file: &str, span: Span, name: &str, context: &str) -> Diagnostic { + Diagnostic::new( + file, + "UnknownStructType", + format!("{} type `{}` is not a declared struct", context, name), + ) + .with_span(span) + .expected("known struct") + .found(name.to_string()) + .hint("declare the struct before using it as a type") +} + +fn check_expr( + file: &str, + expr: &Expr, + locals: &HashMap, + functions: &HashMap, + structs: &HashMap, + enums: &HashMap, + unsafe_context: bool, +) -> Result { + match &expr.kind { + ExprKind::Int(value) => Ok(TExpr { + ty: Type::I32, + span: expr.span, + kind: TExprKind::Int(*value), + }), + ExprKind::Int64(value) => Ok(TExpr { + ty: Type::I64, + span: expr.span, + kind: TExprKind::Int64(*value), + }), + ExprKind::UInt32(value) => Ok(TExpr { + ty: Type::U32, + span: expr.span, + kind: TExprKind::UInt32(*value), + }), + ExprKind::UInt64(value) => Ok(TExpr { + ty: Type::U64, + span: expr.span, + kind: TExprKind::UInt64(*value), + }), + ExprKind::Float(value) => Ok(TExpr { + ty: Type::F64, + span: expr.span, + kind: TExprKind::Float(*value), + }), + ExprKind::Bool(value) => Ok(TExpr { + ty: Type::Bool, + span: expr.span, + kind: TExprKind::Bool(*value), + }), + ExprKind::String(value) => Ok(TExpr { + ty: Type::String, + span: expr.span, + kind: TExprKind::String(value.clone()), + }), + ExprKind::Var(name) => { + let binding = locals.get(name).ok_or_else(|| { + Diagnostic::new( + file, + "UnknownVariable", + format!("unknown variable `{}`", name), + ) + .with_span(expr.span) + })?; + + Ok(TExpr { + ty: binding.ty.clone(), + span: expr.span, + kind: TExprKind::Var(name.clone()), + }) + } + ExprKind::StructInit { + name, + name_span, + fields, + } => check_struct_init( + file, + expr, + name, + *name_span, + fields, + locals, + functions, + structs, + enums, + unsafe_context, + ), + ExprKind::ArrayInit { + elem_ty, + elem_ty_span, + elements, + } => check_array_init( + file, + expr, + elem_ty, + *elem_ty_span, + elements, + locals, + functions, + structs, + enums, + unsafe_context, + ), + ExprKind::OptionSome { + payload_ty, + payload_ty_span, + value, + } => check_option_some( + file, + expr, + payload_ty, + *payload_ty_span, + value, + locals, + functions, + structs, + enums, + unsafe_context, + ), + ExprKind::OptionNone { + payload_ty, + payload_ty_span, + } => check_option_none(file, expr, payload_ty, *payload_ty_span), + ExprKind::ResultOk { + ok_ty, + ok_ty_span, + err_ty, + err_ty_span, + value, + } => check_result_ok( + file, + expr, + ok_ty, + *ok_ty_span, + err_ty, + *err_ty_span, + value, + locals, + functions, + structs, + enums, + unsafe_context, + ), + ExprKind::ResultErr { + ok_ty, + ok_ty_span, + err_ty, + err_ty_span, + value, + } => check_result_err( + file, + expr, + ok_ty, + *ok_ty_span, + err_ty, + *err_ty_span, + value, + locals, + functions, + structs, + enums, + unsafe_context, + ), + ExprKind::OptionIsSome { value } => check_option_observer( + file, + expr, + value, + "is_some", + locals, + functions, + structs, + enums, + unsafe_context, + ), + ExprKind::OptionIsNone { value } => check_option_observer( + file, + expr, + value, + "is_none", + locals, + functions, + structs, + enums, + unsafe_context, + ), + ExprKind::OptionUnwrapSome { value } => check_option_unwrap( + file, + expr, + value, + locals, + functions, + structs, + enums, + unsafe_context, + ), + ExprKind::ResultIsOk { source_name, value } => check_result_observer( + file, + expr, + value, + source_name, + locals, + functions, + structs, + enums, + unsafe_context, + ), + ExprKind::ResultIsErr { source_name, value } => check_result_observer( + file, + expr, + value, + source_name, + locals, + functions, + structs, + enums, + unsafe_context, + ), + ExprKind::ResultUnwrapOk { source_name, value } => check_result_unwrap( + file, + expr, + value, + source_name, + locals, + functions, + structs, + enums, + unsafe_context, + ), + ExprKind::ResultUnwrapErr { source_name, value } => check_result_unwrap( + file, + expr, + value, + source_name, + locals, + functions, + structs, + enums, + unsafe_context, + ), + ExprKind::FieldAccess { + value, + field, + field_span, + } => check_field_access( + file, + expr, + value, + field, + *field_span, + locals, + functions, + structs, + enums, + unsafe_context, + ), + ExprKind::Index { array, index } => check_index( + file, + expr, + array, + index, + locals, + functions, + structs, + enums, + unsafe_context, + ), + ExprKind::EnumVariant { + enum_name, + variant, + name_span, + args, + } => check_enum_variant( + file, + expr, + enum_name, + variant, + *name_span, + args, + locals, + functions, + structs, + enums, + unsafe_context, + ), + ExprKind::Local { .. } => Err(Diagnostic::new( + file, + "LocalDeclarationNotAllowed", + "local declarations are allowed only as body forms", + ) + .with_span(expr.span) + .hint("move the declaration before the final body expression")), + ExprKind::Set { + name, + name_span, + expr: value, + } => { + let Some(binding) = locals.get(name) else { + return Err(Diagnostic::new( + file, + "UnknownVariable", + format!("unknown variable `{}`", name), + ) + .with_span(*name_span)); + }; + + if binding.kind == BindingKind::Param { + return Err(Diagnostic::new( + file, + "CannotAssignParameter", + format!("cannot assign to parameter `{}`", name), + ) + .with_span(*name_span)); + } + + if !binding.mutable { + return Err(Diagnostic::new( + file, + "CannotAssignImmutableLocal", + format!("cannot assign to immutable local `{}`", name), + ) + .with_span(*name_span) + .hint("declare it with `var` to make it mutable")); + } + + let value = check_expr( + file, + value, + locals, + functions, + structs, + enums, + unsafe_context, + )?; + if value.ty != binding.ty { + return Err(Diagnostic::new( + file, + "TypeMismatch", + format!("cannot assign value of wrong type to `{}`", name), + ) + .with_span(value.span) + .expected(binding.ty.to_string()) + .found(value.ty.to_string())); + } + + Ok(TExpr { + ty: Type::Unit, + span: expr.span, + kind: TExprKind::Set { + name: name.clone(), + expr: Box::new(value), + }, + }) + } + ExprKind::Binary { op, left, right } => { + let left = check_expr( + file, + left, + locals, + functions, + structs, + enums, + unsafe_context, + )?; + let right = check_expr( + file, + right, + locals, + functions, + structs, + enums, + unsafe_context, + )?; + + match op { + BinaryOp::Add | BinaryOp::Sub | BinaryOp::Mul | BinaryOp::Div => { + if matches!(op, BinaryOp::Add) + && (left.ty == Type::String || right.ty == Type::String) + { + return Err(Diagnostic::new( + file, + "UnsupportedStringConcatenation", + "string concatenation is not supported in the v1.2 runtime value slice", + ) + .with_span(expr.span) + .hint("pass immutable string values through lets, parameters, returns, and calls without concatenating them")); + } + if left.ty == Type::F64 && right.ty == Type::F64 { + return Ok(TExpr { + ty: Type::F64, + span: expr.span, + kind: TExprKind::Binary { + op: *op, + left: Box::new(left), + right: Box::new(right), + }, + }); + } + if left.ty == Type::I64 && right.ty == Type::I64 { + return Ok(TExpr { + ty: Type::I64, + span: expr.span, + kind: TExprKind::Binary { + op: *op, + left: Box::new(left), + right: Box::new(right), + }, + }); + } + if left.ty == Type::U32 && right.ty == Type::U32 { + return Ok(TExpr { + ty: Type::U32, + span: expr.span, + kind: TExprKind::Binary { + op: *op, + left: Box::new(left), + right: Box::new(right), + }, + }); + } + if left.ty == Type::U64 && right.ty == Type::U64 { + return Ok(TExpr { + ty: Type::U64, + span: expr.span, + kind: TExprKind::Binary { + op: *op, + left: Box::new(left), + right: Box::new(right), + }, + }); + } + if left.ty == Type::U64 && right.ty == Type::U64 { + return Ok(TExpr { + ty: Type::U64, + span: expr.span, + kind: TExprKind::Binary { + op: *op, + left: Box::new(left), + right: Box::new(right), + }, + }); + } + if left.ty == Type::U32 && right.ty == Type::U32 { + return Ok(TExpr { + ty: Type::U32, + span: expr.span, + kind: TExprKind::Binary { + op: *op, + left: Box::new(left), + right: Box::new(right), + }, + }); + } + if is_numeric_primitive(&left.ty) + && is_numeric_primitive(&right.ty) + && left.ty != right.ty + { + return Err(numeric_operand_mismatch(file, expr, &left.ty, &right.ty)); + } + expect_type(file, expr, &left.ty, &Type::I32)?; + expect_type(file, expr, &right.ty, &Type::I32)?; + Ok(TExpr { + ty: Type::I32, + span: expr.span, + kind: TExprKind::Binary { + op: *op, + left: Box::new(left), + right: Box::new(right), + }, + }) + } + BinaryOp::Rem => { + if left.ty == Type::F64 || right.ty == Type::F64 { + return Err(Diagnostic::new( + file, + "UnsupportedF64Remainder", + "floating-point remainder is not supported in the current numeric slice", + ) + .with_span(expr.span) + .expected("i32 % i32, i64 % i64, u32 % u32, or u64 % u64") + .found(format!("{} and {}", left.ty, right.ty)) + .hint("use integer remainder in exp-56; f64 remainder remains deferred")); + } + if left.ty == Type::I64 && right.ty == Type::I64 { + return Ok(TExpr { + ty: Type::I64, + span: expr.span, + kind: TExprKind::Binary { + op: *op, + left: Box::new(left), + right: Box::new(right), + }, + }); + } + if left.ty == Type::U32 && right.ty == Type::U32 { + return Ok(TExpr { + ty: Type::U32, + span: expr.span, + kind: TExprKind::Binary { + op: *op, + left: Box::new(left), + right: Box::new(right), + }, + }); + } + if left.ty == Type::U64 && right.ty == Type::U64 { + return Ok(TExpr { + ty: Type::U64, + span: expr.span, + kind: TExprKind::Binary { + op: *op, + left: Box::new(left), + right: Box::new(right), + }, + }); + } + if left.ty == Type::U64 && right.ty == Type::U64 { + return Ok(TExpr { + ty: Type::U64, + span: expr.span, + kind: TExprKind::Binary { + op: *op, + left: Box::new(left), + right: Box::new(right), + }, + }); + } + if left.ty == Type::U32 && right.ty == Type::U32 { + return Ok(TExpr { + ty: Type::U32, + span: expr.span, + kind: TExprKind::Binary { + op: *op, + left: Box::new(left), + right: Box::new(right), + }, + }); + } + if is_numeric_primitive(&left.ty) + && is_numeric_primitive(&right.ty) + && left.ty != right.ty + { + return Err(numeric_operand_mismatch(file, expr, &left.ty, &right.ty)); + } + expect_type(file, expr, &left.ty, &Type::I32)?; + expect_type(file, expr, &right.ty, &Type::I32)?; + Ok(TExpr { + ty: Type::I32, + span: expr.span, + kind: TExprKind::Binary { + op: *op, + left: Box::new(left), + right: Box::new(right), + }, + }) + } + BinaryOp::BitAnd | BinaryOp::BitOr | BinaryOp::BitXor => { + if left.ty == Type::F64 || right.ty == Type::F64 { + return Err(Diagnostic::new( + file, + "UnsupportedF64Bitwise", + "floating-point bitwise operations are not supported in the current numeric slice", + ) + .with_span(expr.span) + .expected("i32 bitwise i32, i64 bitwise i64, u32 bitwise u32, or u64 bitwise u64") + .found(format!("{} and {}", left.ty, right.ty)) + .hint("use integer bitwise operations in exp-57; f64 bitwise operations remain deferred")); + } + if left.ty == Type::I64 && right.ty == Type::I64 { + return Ok(TExpr { + ty: Type::I64, + span: expr.span, + kind: TExprKind::Binary { + op: *op, + left: Box::new(left), + right: Box::new(right), + }, + }); + } + if left.ty == Type::U32 && right.ty == Type::U32 { + return Ok(TExpr { + ty: Type::U32, + span: expr.span, + kind: TExprKind::Binary { + op: *op, + left: Box::new(left), + right: Box::new(right), + }, + }); + } + if left.ty == Type::U64 && right.ty == Type::U64 { + return Ok(TExpr { + ty: Type::U64, + span: expr.span, + kind: TExprKind::Binary { + op: *op, + left: Box::new(left), + right: Box::new(right), + }, + }); + } + if left.ty == Type::U64 && right.ty == Type::U64 { + return Ok(TExpr { + ty: Type::U64, + span: expr.span, + kind: TExprKind::Binary { + op: *op, + left: Box::new(left), + right: Box::new(right), + }, + }); + } + if left.ty == Type::U32 && right.ty == Type::U32 { + return Ok(TExpr { + ty: Type::U32, + span: expr.span, + kind: TExprKind::Binary { + op: *op, + left: Box::new(left), + right: Box::new(right), + }, + }); + } + if is_numeric_primitive(&left.ty) + && is_numeric_primitive(&right.ty) + && left.ty != right.ty + { + return Err(numeric_operand_mismatch(file, expr, &left.ty, &right.ty)); + } + expect_type(file, expr, &left.ty, &Type::I32)?; + expect_type(file, expr, &right.ty, &Type::I32)?; + Ok(TExpr { + ty: Type::I32, + span: expr.span, + kind: TExprKind::Binary { + op: *op, + left: Box::new(left), + right: Box::new(right), + }, + }) + } + BinaryOp::Eq | BinaryOp::Lt | BinaryOp::Gt | BinaryOp::Le | BinaryOp::Ge => { + if matches!(op, BinaryOp::Eq) + && (is_array_type(&left.ty) || is_array_type(&right.ty)) + { + return Err(Diagnostic::new( + file, + "UnsupportedArrayEquality", + "array equality is not supported in the first-pass array feature", + ) + .with_span(expr.span) + .hint("compare indexed array elements instead")); + } + if matches!(op, BinaryOp::Eq) + && is_vec_i32_type(&left.ty) + && is_vec_i32_type(&right.ty) + { + return Ok(TExpr { + ty: Type::Bool, + span: expr.span, + kind: TExprKind::Binary { + op: *op, + left: Box::new(left), + right: Box::new(right), + }, + }); + } + if matches!(op, BinaryOp::Eq) + && is_vec_i64_type(&left.ty) + && is_vec_i64_type(&right.ty) + { + return Ok(TExpr { + ty: Type::Bool, + span: expr.span, + kind: TExprKind::Binary { + op: *op, + left: Box::new(left), + right: Box::new(right), + }, + }); + } + if matches!(op, BinaryOp::Eq) + && is_vec_f64_type(&left.ty) + && is_vec_f64_type(&right.ty) + { + return Ok(TExpr { + ty: Type::Bool, + span: expr.span, + kind: TExprKind::Binary { + op: *op, + left: Box::new(left), + right: Box::new(right), + }, + }); + } + if matches!(op, BinaryOp::Eq) + && is_vec_bool_type(&left.ty) + && is_vec_bool_type(&right.ty) + { + return Ok(TExpr { + ty: Type::Bool, + span: expr.span, + kind: TExprKind::Binary { + op: *op, + left: Box::new(left), + right: Box::new(right), + }, + }); + } + if matches!(op, BinaryOp::Eq) + && is_vec_string_type(&left.ty) + && is_vec_string_type(&right.ty) + { + return Ok(TExpr { + ty: Type::Bool, + span: expr.span, + kind: TExprKind::Binary { + op: *op, + left: Box::new(left), + right: Box::new(right), + }, + }); + } + if matches!(op, BinaryOp::Eq) + && (is_vector_type(&left.ty) || is_vector_type(&right.ty)) + { + return Err(Diagnostic::new( + file, + "UnsupportedVectorEquality", + "vector equality is supported only for `(vec i32)`, `(vec i64)`, `(vec f64)`, `(vec bool)`, or `(vec string)` values", + ) + .with_span(expr.span) + .expected("(vec i32), (vec i64), (vec f64), (vec bool), or (vec string) on both sides") + .found(format!("{} and {}", left.ty, right.ty))); + } + if matches!(op, BinaryOp::Eq) + && (is_option_result_type(&left.ty) || is_option_result_type(&right.ty)) + { + return Err(Diagnostic::new( + file, + "UnsupportedOptionResultEquality", + "option/result equality is not supported in the current value-flow slice", + ) + .with_span(expr.span) + .hint("observe the tag with `is_some`, `is_none`, `is_ok`, or `is_err`")); + } + if matches!(op, BinaryOp::Eq) + && is_enum_type(&left.ty, enums) + && left.ty == right.ty + { + if !enum_payload_equality_supported(&left.ty, enums) { + return Err(Diagnostic::new( + file, + "UnsupportedEnumEquality", + "enum equality is supported only for payloadless enums and enums whose payload type is direct `i32`, `i64`, `u32`, `u64`, `f64`, `bool`, or `string`", + ) + .with_span(expr.span) + .hint("match the enum and compare fields after extracting the struct payload")); + } + return Ok(TExpr { + ty: Type::Bool, + span: expr.span, + kind: TExprKind::Binary { + op: *op, + left: Box::new(left), + right: Box::new(right), + }, + }); + } + if !matches!(op, BinaryOp::Eq) + && (is_enum_type(&left.ty, enums) || is_enum_type(&right.ty, enums)) + { + return Err(Diagnostic::new( + file, + "UnsupportedEnumOrdering", + "enum ordering comparisons are not supported", + ) + .with_span(expr.span) + .hint("only enum equality with `=` is supported")); + } + if matches!(op, BinaryOp::Eq) + && (is_enum_type(&left.ty, enums) || is_enum_type(&right.ty, enums)) + { + return Err(Diagnostic::new( + file, + "EnumSubjectMismatch", + "enum equality requires both operands to have the same enum type", + ) + .with_span(expr.span) + .expected(left.ty.to_string()) + .found(right.ty.to_string())); + } + if matches!(op, BinaryOp::Eq) + && left.ty == Type::String + && right.ty == Type::String + { + return Ok(TExpr { + ty: Type::Bool, + span: expr.span, + kind: TExprKind::Binary { + op: *op, + left: Box::new(left), + right: Box::new(right), + }, + }); + } + if matches!(op, BinaryOp::Eq) && left.ty == Type::Bool && right.ty == Type::Bool + { + return Ok(TExpr { + ty: Type::Bool, + span: expr.span, + kind: TExprKind::Binary { + op: *op, + left: Box::new(left), + right: Box::new(right), + }, + }); + } + if left.ty == Type::F64 && right.ty == Type::F64 { + return Ok(TExpr { + ty: Type::Bool, + span: expr.span, + kind: TExprKind::Binary { + op: *op, + left: Box::new(left), + right: Box::new(right), + }, + }); + } + if left.ty == Type::I64 && right.ty == Type::I64 { + return Ok(TExpr { + ty: Type::Bool, + span: expr.span, + kind: TExprKind::Binary { + op: *op, + left: Box::new(left), + right: Box::new(right), + }, + }); + } + if left.ty == Type::U32 && right.ty == Type::U32 { + return Ok(TExpr { + ty: Type::Bool, + span: expr.span, + kind: TExprKind::Binary { + op: *op, + left: Box::new(left), + right: Box::new(right), + }, + }); + } + if left.ty == Type::U64 && right.ty == Type::U64 { + return Ok(TExpr { + ty: Type::Bool, + span: expr.span, + kind: TExprKind::Binary { + op: *op, + left: Box::new(left), + right: Box::new(right), + }, + }); + } + if left.ty == Type::U64 && right.ty == Type::U64 { + return Ok(TExpr { + ty: Type::Bool, + span: expr.span, + kind: TExprKind::Binary { + op: *op, + left: Box::new(left), + right: Box::new(right), + }, + }); + } + if left.ty == Type::U32 && right.ty == Type::U32 { + return Ok(TExpr { + ty: Type::Bool, + span: expr.span, + kind: TExprKind::Binary { + op: *op, + left: Box::new(left), + right: Box::new(right), + }, + }); + } + if is_numeric_primitive(&left.ty) + && is_numeric_primitive(&right.ty) + && left.ty != right.ty + { + return Err(numeric_operand_mismatch(file, expr, &left.ty, &right.ty)); + } + expect_type(file, expr, &left.ty, &Type::I32)?; + expect_type(file, expr, &right.ty, &Type::I32)?; + Ok(TExpr { + ty: Type::Bool, + span: expr.span, + kind: TExprKind::Binary { + op: *op, + left: Box::new(left), + right: Box::new(right), + }, + }) + } + } + } + ExprKind::If { + condition, + then_expr, + else_expr, + } => { + let condition = check_expr( + file, + condition, + locals, + functions, + structs, + enums, + unsafe_context, + )?; + if condition.ty != Type::Bool { + return Err(Diagnostic::new( + file, + "IfConditionNotBool", + "`if` condition must be bool", + ) + .with_span(condition.span) + .expected(Type::Bool.to_string()) + .found(condition.ty.to_string())); + } + + let then_expr = check_expr( + file, + then_expr, + locals, + functions, + structs, + enums, + unsafe_context, + )?; + let else_expr = check_expr( + file, + else_expr, + locals, + functions, + structs, + enums, + unsafe_context, + )?; + + if then_expr.ty != else_expr.ty { + return Err(Diagnostic::new( + file, + "IfBranchTypeMismatch", + "`if` branches must have the same type", + ) + .with_span(expr.span) + .expected(then_expr.ty.to_string()) + .found(else_expr.ty.to_string())); + } + + Ok(TExpr { + ty: then_expr.ty.clone(), + span: expr.span, + kind: TExprKind::If { + condition: Box::new(condition), + then_expr: Box::new(then_expr), + else_expr: Box::new(else_expr), + }, + }) + } + ExprKind::Match { subject, arms } => check_match( + file, + expr, + subject, + arms, + locals, + functions, + structs, + enums, + unsafe_context, + ), + ExprKind::While { condition, body } => { + let condition = check_expr( + file, + condition, + locals, + functions, + structs, + enums, + unsafe_context, + )?; + if condition.ty != Type::Bool { + return Err(Diagnostic::new( + file, + "WhileConditionNotBool", + "`while` condition must be bool", + ) + .with_span(condition.span) + .expected(Type::Bool.to_string()) + .found(condition.ty.to_string())); + } + + let mut checked_body = Vec::new(); + for body_expr in body { + if matches!(body_expr.kind, ExprKind::Local { .. }) { + return Err(Diagnostic::new( + file, + "LocalDeclarationInWhileBodyUnsupported", + "local declarations are not allowed inside `while` bodies", + ) + .with_span(body_expr.span) + .hint("declare loop locals before the `while` form")); + } + + if matches!(body_expr.kind, ExprKind::While { .. }) { + return Err(Diagnostic::new( + file, + "NestedWhileUnsupported", + "nested `while` loops are not supported in first-pass loop bodies", + ) + .with_span(body_expr.span) + .hint("keep first-pass loop bodies flat")); + } + + let checked = check_expr( + file, + body_expr, + locals, + functions, + structs, + enums, + unsafe_context, + )?; + if checked.ty != Type::Unit { + return Err(Diagnostic::new( + file, + "WhileBodyFormNotUnit", + "`while` body forms must produce unit", + ) + .with_span(checked.span) + .expected(Type::Unit.to_string()) + .found(checked.ty.to_string())); + } + checked_body.push(checked); + } + + Ok(TExpr { + ty: Type::Unit, + span: expr.span, + kind: TExprKind::While { + condition: Box::new(condition), + body: checked_body, + }, + }) + } + ExprKind::Unsafe { body } => { + check_unsafe_block(file, expr, body, locals, functions, structs, enums) + } + ExprKind::Call { + name, + name_span, + args, + } => { + if unsafe_ops::is_reserved_head(name) { + return Err(unsafe_operation_diagnostic( + file, + expr.span, + *name_span, + name, + unsafe_context, + )); + } + + if std_runtime::is_standard_path(name) && !std_runtime::is_promoted_source_name(name) { + return Err(std_runtime::unsupported_standard_library_call( + file, *name_span, name, + )); + } + + let sig = functions.get(name).ok_or_else(|| { + Diagnostic::new( + file, + "UnknownFunction", + format!("unknown function `{}`", name), + ) + .with_span(expr.span) + })?; + + if sig.foreign && !unsafe_context { + return Err(Diagnostic::new( + file, + "UnsafeRequired", + format!("C import `{}` requires an `unsafe` block", name), + ) + .with_span(*name_span) + .hint("wrap the call in `(unsafe ...)`") + .related("C import call head", *name_span)); + } + + if args.len() != sig.params.len() { + return Err(Diagnostic::new( + file, + "ArityMismatch", + format!("function `{}` called with wrong number of arguments", name), + ) + .with_span(expr.span) + .expected(sig.params.len().to_string()) + .found(args.len().to_string())); + } + + let mut checked_args = Vec::new(); + + for (arg, expected) in args.iter().zip(&sig.params) { + let checked = + check_expr(file, arg, locals, functions, structs, enums, unsafe_context)?; + let runtime_symbol = std_runtime::runtime_symbol(name).unwrap_or(name); + if matches!( + runtime_symbol, + "print_i32" + | "print_i64" + | "print_u32" + | "print_u64" + | "print_f64" + | "print_bool" + ) && is_array_type(&checked.ty) + { + return Err(Diagnostic::new( + file, + "UnsupportedArrayPrint", + "array printing is not supported in the first-pass array feature", + ) + .with_span(arg.span) + .hint("index one array element and print that value instead")); + } + if matches!( + runtime_symbol, + "print_i32" | "print_i64" | "print_u32" | "print_u64" + ) && is_option_result_type(&checked.ty) + { + return Err(Diagnostic::new( + file, + "UnsupportedOptionResultPrint", + "option/result printing is not supported in the current value-flow slice", + ) + .with_span(arg.span) + .hint("observe the tag with `is_some`, `is_none`, `is_ok`, or `is_err`")); + } + if matches!( + runtime_symbol, + "print_i32" | "print_i64" | "print_u32" | "print_u64" + ) && is_enum_type(&checked.ty, enums) + { + return Err(Diagnostic::new( + file, + "UnsupportedEnumPrint", + "enum printing is not supported", + ) + .with_span(arg.span) + .hint("compare enum values with `=` or match on variants")); + } + if &checked.ty != expected { + return Err(Diagnostic::new( + file, + "TypeMismatch", + format!("cannot call `{}` with argument of wrong type", name), + ) + .with_span(arg.span) + .expected(expected.to_string()) + .found(checked.ty.to_string())); + } + if runtime_symbol == "print_string" { + check_print_string_literal(file, arg.span, &checked)?; + } + checked_args.push(checked); + } + + Ok(TExpr { + ty: sig.return_type.clone(), + span: expr.span, + kind: TExprKind::Call { + name: name.clone(), + args: checked_args, + }, + }) + } + } +} + +fn check_print_string_literal(file: &str, span: Span, value: &TExpr) -> Result<(), Diagnostic> { + let TExprKind::String(value) = &value.kind else { + return Ok(()); + }; + + if value.bytes().any(|byte| byte == 0 || !byte.is_ascii()) { + return Err(Diagnostic::new( + file, + "UnsupportedStringLiteral", + "string literal contains bytes outside the first runtime string slice", + ) + .with_span(span) + .expected("ASCII string literal without embedded NUL") + .found("unsupported string literal") + .hint("use ASCII text plus the current `\\n`, `\\t`, `\\\"`, and `\\\\` escapes")); + } + + Ok(()) +} + +fn numeric_operand_mismatch(file: &str, expr: &Expr, left: &Type, right: &Type) -> Diagnostic { + Diagnostic::new( + file, + "TypeMismatch", + "numeric operands must have the same primitive type", + ) + .with_span(expr.span) + .expected("i32 with i32, i64 with i64, u32 with u32, u64 with u64, or f64 with f64") + .found(format!("{} and {}", left, right)) + .hint("mixed i32/i64/u32/u64/f64 arithmetic and comparison are deferred") +} + +fn is_numeric_primitive(ty: &Type) -> bool { + matches!( + ty, + Type::I32 | Type::I64 | Type::U32 | Type::U64 | Type::F64 + ) +} + +enum MatchFamily { + Option { + payload_ty: Type, + }, + Result { + ok_ty: Type, + err_ty: Type, + }, + Enum { + name: String, + variants: Vec, + }, +} + +impl MatchFamily { + fn allows(&self, pattern: &MatchPatternKind) -> bool { + match self { + Self::Option { .. } => { + matches!(pattern, MatchPatternKind::Some | MatchPatternKind::None) + } + Self::Result { .. } => matches!(pattern, MatchPatternKind::Ok | MatchPatternKind::Err), + Self::Enum { name, variants } => matches!( + pattern, + MatchPatternKind::EnumVariant { enum_name, variant } + if enum_name == name && variants.iter().any(|candidate| &candidate.name == variant) + ), + } + } + + fn required(&self) -> Vec { + match self { + Self::Option { .. } => vec![MatchPatternKind::Some, MatchPatternKind::None], + Self::Result { .. } => vec![MatchPatternKind::Ok, MatchPatternKind::Err], + Self::Enum { name, variants } => variants + .iter() + .map(|variant| MatchPatternKind::EnumVariant { + enum_name: name.clone(), + variant: variant.name.clone(), + }) + .collect(), + } + } + + fn subject_type(&self) -> String { + match self { + Self::Option { payload_ty } => format!("(option {})", payload_ty), + Self::Result { ok_ty, err_ty } => format!("(result {} {})", ok_ty, err_ty), + Self::Enum { name, .. } => name.clone(), + } + } + + fn binding_type(&self, pattern: &MatchPatternKind) -> Option { + match (self, pattern) { + (Self::Option { payload_ty }, MatchPatternKind::Some) => Some(payload_ty.clone()), + (Self::Result { ok_ty, .. }, MatchPatternKind::Ok) => Some(ok_ty.clone()), + (Self::Result { err_ty, .. }, MatchPatternKind::Err) => Some(err_ty.clone()), + (Self::Enum { variants, .. }, MatchPatternKind::EnumVariant { variant, .. }) => { + variants + .iter() + .find(|candidate| &candidate.name == variant) + .and_then(|candidate| candidate.payload_ty.clone()) + } + _ => None, + } + } + + fn discriminant(&self, pattern: &MatchPatternKind) -> Option { + match (self, pattern) { + (Self::Enum { variants, .. }, MatchPatternKind::EnumVariant { variant, .. }) => { + variants + .iter() + .position(|candidate| &candidate.name == variant) + .map(|index| index as i32) + } + _ => None, + } + } + + fn pattern_requires_binding(&self, pattern: &MatchPatternKind) -> bool { + self.binding_type(pattern).is_some() + } +} + +fn check_match( + file: &str, + expr: &Expr, + subject: &Expr, + arms: &[MatchArm], + locals: &HashMap, + functions: &HashMap, + structs: &HashMap, + enums: &HashMap, + unsafe_context: bool, +) -> Result { + if let Some(err) = match_subject_syntax_diagnostic(file, subject, locals, enums) { + return Err(err); + } + + let subject = check_expr( + file, + subject, + locals, + functions, + structs, + enums, + unsafe_context, + )?; + let family = match_subject_family(file, &subject, enums)?; + + validate_match_arm_set(file, &family, arms, expr.span)?; + + let mut checked_arms = Vec::new(); + let mut expected_arm_ty: Option<(Type, Span)> = None; + + for arm in arms { + let mut arm_locals = locals.clone(); + if let Some(binding) = &arm.pattern.binding { + let binding_span = arm.pattern.binding_span.unwrap_or(arm.pattern.span); + if let Some(existing) = locals.get(binding) { + return Err(Diagnostic::new( + file, + "MatchBindingCollision", + format!( + "match payload binding `{}` collides with a visible name", + binding + ), + ) + .with_span(binding_span) + .related("visible binding", existing.span)); + } + + if functions.contains_key(binding) || is_reserved_callable_name(binding) { + return Err(Diagnostic::new( + file, + "MatchBindingCollision", + format!( + "match payload binding `{}` collides with a function or intrinsic", + binding + ), + ) + .with_span(binding_span) + .hint("choose a payload name distinct from visible callables")); + } + + if structs.contains_key(binding) { + return Err(Diagnostic::new( + file, + "MatchBindingCollision", + format!("match payload binding `{}` collides with a struct", binding), + ) + .with_span(binding_span) + .hint("choose a payload name distinct from visible structs")); + } + + if let Some(ty) = family.binding_type(&arm.pattern.kind) { + arm_locals.insert( + binding.clone(), + Binding { + ty, + mutable: false, + kind: BindingKind::MatchPayload, + span: binding_span, + }, + ); + } + } + + let mut arm_errors = Vec::new(); + let checked_body = check_body( + file, + &arm.body, + arm_locals, + functions, + structs, + enums, + unsafe_context, + &mut arm_errors, + ); + if let Some(err) = arm_errors.into_iter().next() { + return Err(err); + } + + let Some(last) = checked_body.last() else { + return Err(Diagnostic::new( + file, + "MalformedMatchPattern", + "match arm must contain at least one body expression", + ) + .with_span(arm.span)); + }; + + match &expected_arm_ty { + Some((expected, expected_span)) if last.ty != *expected => { + return Err(Diagnostic::new( + file, + "MatchArmTypeMismatch", + "match arm final expressions must have the same type", + ) + .with_span(last.span) + .expected(expected.to_string()) + .found(last.ty.to_string()) + .related("first arm result type", *expected_span)); + } + None => expected_arm_ty = Some((last.ty.clone(), last.span)), + _ => {} + } + + checked_arms.push(TMatchArm { + pattern: arm.pattern.kind.clone(), + binding: arm.pattern.binding.clone(), + discriminant: family.discriminant(&arm.pattern.kind), + body: checked_body, + }); + } + + let ty = expected_arm_ty.map(|(ty, _)| ty).unwrap_or(Type::Unit); + + Ok(TExpr { + ty, + span: expr.span, + kind: TExprKind::Match { + subject: Box::new(subject), + arms: checked_arms, + }, + }) +} + +fn match_subject_syntax_diagnostic( + file: &str, + subject: &Expr, + locals: &HashMap, + enums: &HashMap, +) -> Option { + match &subject.kind { + ExprKind::Var(name) => { + locals.get(name)?; + None + } + ExprKind::Set { + name, name_span, .. + } => { + let binding = locals.get(name)?; + if is_option_result_type(&binding.ty) { + Some( + Diagnostic::new( + file, + "UnsupportedMatchMutation", + "match subject cannot be an option/result assignment", + ) + .with_span(*name_span) + .hint("evaluate and match an immutable option/result value"), + ) + } else if is_enum_type(&binding.ty, enums) { + Some( + Diagnostic::new( + file, + "EnumLocalMutationUnsupported", + "match subject cannot be an enum assignment", + ) + .with_span(*name_span) + .hint("evaluate and match an immutable enum value"), + ) + } else { + None + } + } + ExprKind::ArrayInit { + elem_ty, + elem_ty_span, + .. + } if contains_enum_type(elem_ty, enums) => Some( + Diagnostic::new( + file, + "UnsupportedEnumContainer", + "match does not support enum values in array containers", + ) + .with_span(*elem_ty_span) + .hint("match a direct enum value"), + ), + ExprKind::ArrayInit { + elem_ty, + elem_ty_span, + .. + } if contains_option_result_type(elem_ty) => Some( + Diagnostic::new( + file, + "UnsupportedMatchContainer", + "match does not support option/result values in array containers", + ) + .with_span(*elem_ty_span) + .hint( + "match a direct `(option i32)`, `(option i64)`, `(option u32)`, `(option u64)`, `(option f64)`, `(option bool)`, `(option string)`, `(result i32 i32)`, `(result i64 i32)`, `(result u32 i32)`, `(result u64 i32)`, `(result f64 i32)`, `(result bool i32)`, or `(result string i32)` value", + ), + ), + ExprKind::OptionSome { + payload_ty, + payload_ty_span, + .. + } + | ExprKind::OptionNone { + payload_ty, + payload_ty_span, + } if !option_payload_type_supported(payload_ty) => Some(unsupported_match_payload_type( + file, + *payload_ty_span, + payload_ty, + )), + ExprKind::ResultOk { + ok_ty, + ok_ty_span, + err_ty, + err_ty_span, + .. + } + | ExprKind::ResultErr { + ok_ty, + ok_ty_span, + err_ty, + err_ty_span, + .. + } => check_result_payload_types(file, ok_ty, *ok_ty_span, err_ty, *err_ty_span).err(), + _ => None, + } +} + +fn match_subject_family( + file: &str, + subject: &TExpr, + enums: &HashMap, +) -> Result { + match &subject.ty { + Type::Option(inner) if option_payload_type_supported(inner) => Ok(MatchFamily::Option { + payload_ty: (**inner).clone(), + }), + Type::Result(ok, err) if result_match_payload_types_supported(ok, err) => { + Ok(MatchFamily::Result { + ok_ty: (**ok).clone(), + err_ty: (**err).clone(), + }) + } + Type::Named(name) if enums.contains_key(name) => { + let sig = enums.get(name).expect("enum checked above"); + Ok(MatchFamily::Enum { + name: name.clone(), + variants: sig.variants.clone(), + }) + } + Type::Option(inner) => Err(unsupported_match_payload_type(file, subject.span, inner)), + Type::Result(ok, err) => { + if **err != Type::I32 { + Err(unsupported_match_payload_type(file, subject.span, err)) + } else { + Err(unsupported_match_payload_type(file, subject.span, ok)) + } + } + Type::Array(inner, _) if contains_option_result_type(inner) => Err(Diagnostic::new( + file, + "UnsupportedMatchContainer", + "match does not support option/result values in array containers", + ) + .with_span(subject.span) + .hint("match a direct `(option i32)`, `(option i64)`, `(option u32)`, `(option u64)`, `(option f64)`, `(option bool)`, `(option string)`, `(result i32 i32)`, `(result i64 i32)`, `(result u32 i32)`, `(result u64 i32)`, `(result f64 i32)`, `(result bool i32)`, or `(result string i32)` value")), + Type::Array(inner, _) if contains_enum_type(inner, enums) => Err(Diagnostic::new( + file, + "UnsupportedEnumContainer", + "match does not support enum values in array containers", + ) + .with_span(subject.span) + .hint("match a direct enum value")), + _ => Err(Diagnostic::new( + file, + "MatchSubjectTypeMismatch", + "match subject must be `(option i32)`, `(option i64)`, `(option u32)`, `(option u64)`, `(option f64)`, `(option bool)`, `(option string)`, `(result i32 i32)`, `(result i64 i32)`, `(result u32 i32)`, `(result u64 i32)`, `(result f64 i32)`, `(result bool i32)`, `(result string i32)`, or a known enum", + ) + .with_span(subject.span) + .expected("(option i32), (option i64), (option u32), (option u64), (option f64), (option bool), (option string), (result i32 i32), (result i64 i32), (result u32 i32), (result u64 i32), (result f64 i32), (result bool i32), (result string i32), or known enum") + .found(subject.ty.to_string())), + } +} + +fn validate_match_arm_set( + file: &str, + family: &MatchFamily, + arms: &[MatchArm], + span: Span, +) -> Result<(), Diagnostic> { + let mut seen = HashMap::new(); + + for arm in arms { + let pattern = &arm.pattern.kind; + if !family.allows(pattern) { + let code = if matches!(pattern, MatchPatternKind::EnumVariant { .. }) { + "InvalidEnumMatchArm" + } else { + "MalformedMatchPattern" + }; + return Err(Diagnostic::new( + file, + code, + format!( + "`{}` arm is not valid for `{}` match", + lower::match_pattern_name(pattern), + family.subject_type() + ), + ) + .with_span(arm.pattern.span)); + } + + let requires_binding = family.pattern_requires_binding(pattern); + if requires_binding && arm.pattern.binding.is_none() { + return Err(Diagnostic::new( + file, + "InvalidEnumMatchArm", + format!( + "`{}` match arm must bind its payload", + lower::match_pattern_name(pattern) + ), + ) + .with_span(arm.pattern.span) + .expected(format!("({} binding)", lower::match_pattern_name(pattern)))); + } + if !requires_binding && arm.pattern.binding.is_some() { + return Err(Diagnostic::new( + file, + "InvalidEnumMatchArm", + format!( + "`{}` match arm does not have a payload to bind", + lower::match_pattern_name(pattern) + ), + ) + .with_span(arm.pattern.span) + .expected(format!("({})", lower::match_pattern_name(pattern)))); + } + + if let Some(original) = seen.insert(pattern.clone(), arm.pattern.span) { + return Err(Diagnostic::new( + file, + "DuplicateMatchArm", + format!( + "duplicate `{}` match arm", + lower::match_pattern_name(pattern) + ), + ) + .with_span(arm.pattern.span) + .related("original match arm", original)); + } + } + + let required = family.required(); + let missing = required + .iter() + .filter(|pattern| !seen.contains_key(*pattern)) + .map(lower::match_pattern_name) + .collect::>(); + + if !missing.is_empty() { + return Err(Diagnostic::new( + file, + "NonExhaustiveMatch", + format!("match is missing `{}` arm(s)", missing.join("`, `")), + ) + .with_span(span) + .expected( + required + .iter() + .map(lower::match_pattern_name) + .collect::>() + .join(" and "), + ) + .found( + seen.keys() + .map(lower::match_pattern_name) + .collect::>() + .join(", "), + )); + } + + Ok(()) +} + +fn contains_option_result_type(ty: &Type) -> bool { + match ty { + Type::Option(_) | Type::Result(_, _) => true, + Type::Ptr(inner) | Type::Array(inner, _) | Type::Vec(inner) | Type::Slice(inner) => { + contains_option_result_type(inner) + } + _ => false, + } +} + +fn contains_enum_type(ty: &Type, enums: &HashMap) -> bool { + match ty { + Type::Named(name) => enums.contains_key(name), + Type::Ptr(inner) | Type::Array(inner, _) | Type::Vec(inner) | Type::Slice(inner) => { + contains_enum_type(inner, enums) + } + Type::Option(inner) => contains_enum_type(inner, enums), + Type::Result(ok, err) => contains_enum_type(ok, enums) || contains_enum_type(err, enums), + _ => false, + } +} + +fn supported_struct_field_message() -> &'static str { + "released struct fields support direct `i32`, `i64`, `f64`, `bool`, `string`, direct fixed arrays of those element families, direct enum fields, current concrete vec/option/result fields, and current non-recursive struct fields" +} + +fn supported_struct_field_expected() -> &'static str { + "direct `i32`, `i64`, `f64`, `bool`, `string`, direct fixed array of those element families, direct enum field, current concrete vec/option/result field, or current non-recursive struct field" +} + +fn validate_struct_field_cycles( + file: &str, + structs: &HashMap, +) -> Vec { + fn visit( + file: &str, + name: &str, + structs: &HashMap, + stack: &mut Vec, + visited: &mut HashSet, + errors: &mut Vec, + ) { + if !visited.insert(name.to_string()) { + return; + } + + stack.push(name.to_string()); + + if let Some(sig) = structs.get(name) { + for field in &sig.fields { + let Type::Named(target) = &field.ty else { + continue; + }; + let Some(target_sig) = structs.get(target) else { + continue; + }; + + if let Some(index) = stack.iter().position(|entry| entry == target) { + let cycle = stack[index..].join(" -> "); + errors.push( + Diagnostic::new( + file, + "RecursiveStructFieldUnsupported", + format!( + "recursive struct fields are not supported (`{} -> {}`)", + cycle, target + ), + ) + .with_span(field.ty_span) + .related("recursive struct declaration", target_sig.span) + .hint("flatten the struct graph or remove the recursive field"), + ); + continue; + } + + visit(file, target, structs, stack, visited, errors); + } + } + + stack.pop(); + } + + let mut errors = Vec::new(); + let mut visited = HashSet::new(); + let mut names = structs.keys().cloned().collect::>(); + names.sort(); + for name in names { + let mut stack = Vec::new(); + visit(file, &name, structs, &mut stack, &mut visited, &mut errors); + } + errors +} + +fn unsupported_enum_container(file: &str, span: Span, ty: &Type) -> Diagnostic { + Diagnostic::new( + file, + "UnsupportedEnumContainer", + "enum values in container types are not supported", + ) + .with_span(span) + .expected("direct enum type") + .found(ty.to_string()) + .hint("use enum values directly in immutable locals, parameters, returns, calls, equality, or match") +} + +fn unsupported_match_payload_type(file: &str, span: Span, ty: &Type) -> Diagnostic { + Diagnostic::new( + file, + "UnsupportedMatchPayloadType", + "match supports only `(option i32)`, `(option i64)`, `(option f64)`, `(option bool)`, `(option string)`, `(result i32 i32)`, `(result i64 i32)`, `(result f64 i32)`, `(result bool i32)`, and `(result string i32)` payloads", + ) + .with_span(span) + .expected("i32 option payloads, i64 option payloads, f64 option payloads, bool option payloads, string option payloads, i32/i32 result payloads, i64/i32 result payloads, f64/i32 result payloads, bool/i32 result payloads, or string/i32 result payloads") + .found(ty.to_string()) +} + +fn check_unsafe_block( + file: &str, + expr: &Expr, + body: &[Expr], + locals: &HashMap, + functions: &HashMap, + structs: &HashMap, + enums: &HashMap, +) -> Result { + if body.is_empty() { + return Err(Diagnostic::new( + file, + "MalformedUnsafeForm", + "`unsafe` block must contain a final expression", + ) + .with_span(expr.span) + .expected("(unsafe body-form... final-expression)")); + } + + let mut errors = Vec::new(); + let checked_body = check_body( + file, + body, + locals.clone(), + functions, + structs, + enums, + true, + &mut errors, + ); + + if let Some(err) = errors.into_iter().next() { + return Err(err); + } + + let Some(last) = checked_body.last() else { + return Err(Diagnostic::new( + file, + "MalformedUnsafeForm", + "`unsafe` block must contain a final expression", + ) + .with_span(expr.span) + .expected("(unsafe body-form... final-expression)")); + }; + + Ok(TExpr { + ty: last.ty.clone(), + span: expr.span, + kind: TExprKind::Unsafe { body: checked_body }, + }) +} + +fn unsafe_operation_diagnostic( + file: &str, + span: Span, + name_span: Span, + name: &str, + unsafe_context: bool, +) -> Diagnostic { + if unsafe_context { + Diagnostic::new( + file, + "UnsupportedUnsafeOperation", + format!( + "unsafe operation `{}` is outside the v1.6 unsafe contract", + name + ), + ) + .with_span(span) + .hint("raw memory operations are not supported by v1.6 unsafe blocks") + .related("unsafe operation head", name_span) + } else { + Diagnostic::new( + file, + "UnsafeRequired", + format!("unsafe operation `{}` requires an `unsafe` block", name), + ) + .with_span(span) + .hint("wrap the operation in `(unsafe ...)`") + .related("unsafe operation head", name_span) + } +} + +fn check_struct_init( + file: &str, + expr: &Expr, + name: &str, + name_span: Span, + fields: &[StructInitField], + locals: &HashMap, + functions: &HashMap, + structs: &HashMap, + enums: &HashMap, + unsafe_context: bool, +) -> Result { + let sig = structs.get(name).ok_or_else(|| { + Diagnostic::new(file, "UnknownStruct", format!("unknown struct `{}`", name)) + .with_span(name_span) + })?; + + let mut seen = HashMap::new(); + let mut checked_by_name = HashMap::new(); + + for (position, field) in fields.iter().enumerate() { + match seen.entry(field.name.clone()) { + Entry::Vacant(entry) => { + entry.insert(field.name_span); + } + Entry::Occupied(entry) => { + return Err(Diagnostic::new( + file, + "DuplicateStructConstructorField", + format!("constructor for `{}` repeats field `{}`", name, field.name), + ) + .with_span(field.name_span) + .related("original constructor field", *entry.get())); + } + } + + let Some(index) = sig.fields_by_name.get(&field.name) else { + return Err(Diagnostic::new( + file, + "UnknownStructField", + format!("struct `{}` has no field `{}`", name, field.name), + ) + .with_span(field.name_span)); + }; + + let expected_field = &sig.fields[position]; + if expected_field.name != field.name { + return Err(Diagnostic::new( + file, + "StructConstructorFieldOrderMismatch", + format!("constructor for `{}` lists fields out of order", name), + ) + .with_span(field.name_span) + .expected(expected_field.name.clone()) + .found(field.name.clone())); + } + + let expected = &sig.fields[*index].ty; + let checked = check_expr( + file, + &field.expr, + locals, + functions, + structs, + enums, + unsafe_context, + )?; + if &checked.ty != expected { + return Err(Diagnostic::new( + file, + "TypeMismatch", + format!("field `{}` initializer has the wrong type", field.name), + ) + .with_span(checked.span) + .expected(expected.to_string()) + .found(checked.ty.to_string())); + } + + checked_by_name.insert(field.name.clone(), checked); + } + + for field in &sig.fields { + if !checked_by_name.contains_key(&field.name) { + return Err(Diagnostic::new( + file, + "MissingStructField", + format!( + "constructor for `{}` is missing field `{}`", + name, field.name + ), + ) + .with_span(expr.span) + .expected(format!("field `{}`", field.name))); + } + } + + let fields = sig + .fields + .iter() + .map(|field| { + ( + field.name.clone(), + checked_by_name + .get(&field.name) + .expect("checked constructor has all declared fields") + .clone(), + ) + }) + .collect(); + + Ok(TExpr { + ty: Type::Named(name.to_string()), + span: expr.span, + kind: TExprKind::StructInit { + name: name.to_string(), + fields, + }, + }) +} + +fn check_enum_variant( + file: &str, + expr: &Expr, + enum_name: &str, + variant: &str, + name_span: Span, + args: &[Expr], + locals: &HashMap, + functions: &HashMap, + structs: &HashMap, + enums: &HashMap, + unsafe_context: bool, +) -> Result { + let sig = enums.get(enum_name).ok_or_else(|| { + Diagnostic::new( + file, + "UnknownEnumConstructor", + format!("unknown enum `{}` in variant constructor", enum_name), + ) + .with_span(name_span) + })?; + + let Some(discriminant) = sig.variants_by_name.get(variant).copied() else { + return Err(Diagnostic::new( + file, + "UnknownVariantConstructor", + format!("enum `{}` has no variant `{}`", enum_name, variant), + ) + .with_span(name_span) + .expected(format!( + "one of {}", + sig.variants + .iter() + .map(|variant| variant.name.as_str()) + .collect::>() + .join(", ") + ))); + }; + let variant_sig = &sig.variants[discriminant]; + let expected_arity = if variant_sig.payload_ty.is_some() { + 1 + } else { + 0 + }; + if args.len() != expected_arity { + let message = if expected_arity == 0 { + format!( + "enum constructor `{}.{}` takes no arguments", + enum_name, variant + ) + } else { + let payload_ty = variant_sig + .payload_ty + .as_ref() + .expect("payload arity without payload type"); + format!( + "enum constructor `{}.{}` requires one {} payload argument", + enum_name, variant, payload_ty + ) + }; + return Err(Diagnostic::new(file, "VariantConstructorArity", message) + .with_span(expr.span) + .expected(expected_arity.to_string()) + .found(args.len().to_string())); + } + + let payload = if let Some(payload_ty) = &variant_sig.payload_ty { + let checked = check_expr( + file, + &args[0], + locals, + functions, + structs, + enums, + unsafe_context, + )?; + if &checked.ty != payload_ty { + return Err(Diagnostic::new( + file, + "TypeMismatch", + format!( + "enum constructor `{}.{}` payload has wrong type", + enum_name, variant + ), + ) + .with_span(checked.span) + .expected(payload_ty.to_string()) + .found(checked.ty.to_string())); + } + Some(Box::new(checked)) + } else { + None + }; + + Ok(TExpr { + ty: Type::Named(enum_name.to_string()), + span: expr.span, + kind: TExprKind::EnumVariant { + enum_name: enum_name.to_string(), + variant: variant.to_string(), + discriminant: discriminant as i32, + payload, + }, + }) +} + +fn check_option_some( + file: &str, + expr: &Expr, + payload_ty: &Type, + payload_ty_span: Span, + value: &Expr, + locals: &HashMap, + functions: &HashMap, + structs: &HashMap, + enums: &HashMap, + unsafe_context: bool, +) -> Result { + check_option_payload_type(file, payload_ty, payload_ty_span)?; + + let value = check_expr( + file, + value, + locals, + functions, + structs, + enums, + unsafe_context, + )?; + if value.ty != *payload_ty { + return Err(Diagnostic::new( + file, + "TypeMismatch", + "option `some` value has the wrong type", + ) + .with_span(value.span) + .expected(payload_ty.to_string()) + .found(value.ty.to_string())); + } + + Ok(TExpr { + ty: Type::Option(Box::new(payload_ty.clone())), + span: expr.span, + kind: TExprKind::OptionSome { + value: Box::new(value), + }, + }) +} + +fn check_option_none( + file: &str, + expr: &Expr, + payload_ty: &Type, + payload_ty_span: Span, +) -> Result { + check_option_payload_type(file, payload_ty, payload_ty_span)?; + + Ok(TExpr { + ty: Type::Option(Box::new(payload_ty.clone())), + span: expr.span, + kind: TExprKind::OptionNone, + }) +} + +fn check_result_ok( + file: &str, + expr: &Expr, + ok_ty: &Type, + ok_ty_span: Span, + err_ty: &Type, + err_ty_span: Span, + value: &Expr, + locals: &HashMap, + functions: &HashMap, + structs: &HashMap, + enums: &HashMap, + unsafe_context: bool, +) -> Result { + check_result_constructor_payload_types(file, ok_ty, ok_ty_span, err_ty, err_ty_span)?; + + let value = check_expr( + file, + value, + locals, + functions, + structs, + enums, + unsafe_context, + )?; + if value.ty != *ok_ty { + return Err( + Diagnostic::new(file, "TypeMismatch", "result `ok` value has the wrong type") + .with_span(value.span) + .expected(ok_ty.to_string()) + .found(value.ty.to_string()), + ); + } + + Ok(TExpr { + ty: Type::Result(Box::new(ok_ty.clone()), Box::new(err_ty.clone())), + span: expr.span, + kind: TExprKind::ResultOk { + value: Box::new(value), + }, + }) +} + +fn check_result_err( + file: &str, + expr: &Expr, + ok_ty: &Type, + ok_ty_span: Span, + err_ty: &Type, + err_ty_span: Span, + value: &Expr, + locals: &HashMap, + functions: &HashMap, + structs: &HashMap, + enums: &HashMap, + unsafe_context: bool, +) -> Result { + check_result_constructor_payload_types(file, ok_ty, ok_ty_span, err_ty, err_ty_span)?; + + let value = check_expr( + file, + value, + locals, + functions, + structs, + enums, + unsafe_context, + )?; + if value.ty != *err_ty { + return Err(Diagnostic::new( + file, + "TypeMismatch", + "result `err` value has the wrong type", + ) + .with_span(value.span) + .expected(err_ty.to_string()) + .found(value.ty.to_string())); + } + + Ok(TExpr { + ty: Type::Result(Box::new(ok_ty.clone()), Box::new(err_ty.clone())), + span: expr.span, + kind: TExprKind::ResultErr { + value: Box::new(value), + }, + }) +} + +fn check_option_observer( + file: &str, + expr: &Expr, + value: &Expr, + op: &str, + locals: &HashMap, + functions: &HashMap, + structs: &HashMap, + enums: &HashMap, + unsafe_context: bool, +) -> Result { + let value = check_expr( + file, + value, + locals, + functions, + structs, + enums, + unsafe_context, + )?; + if !matches!(value.ty, Type::Option(ref inner) if option_payload_type_supported(inner)) { + return Err(Diagnostic::new( + file, + "OptionObservationTypeMismatch", + format!( + "`{}` requires an `(option i32)`, `(option i64)`, `(option f64)`, `(option bool)`, or `(option string)` value", + op + ), + ) + .with_span(value.span) + .expected("(option i32), (option i64), (option f64), (option bool), or (option string)") + .found(value.ty.to_string())); + } + + let kind = match op { + "is_some" => TExprKind::OptionIsSome { + value: Box::new(value), + }, + "is_none" => TExprKind::OptionIsNone { + value: Box::new(value), + }, + _ => unreachable!("unknown option observer"), + }; + + Ok(TExpr { + ty: Type::Bool, + span: expr.span, + kind, + }) +} + +fn check_result_observer( + file: &str, + expr: &Expr, + value: &Expr, + op: &str, + locals: &HashMap, + functions: &HashMap, + structs: &HashMap, + enums: &HashMap, + unsafe_context: bool, +) -> Result { + let value = check_expr( + file, + value, + locals, + functions, + structs, + enums, + unsafe_context, + )?; + if !matches!(value.ty, Type::Result(ref ok, ref err) if result_payload_types_supported(ok, err)) + { + return Err(Diagnostic::new( + file, + "ResultObservationTypeMismatch", + format!( + "`{}` requires a `(result i32 i32)`, `(result i64 i32)`, `(result f64 i32)`, `(result bool i32)`, or `(result string i32)` value", + op + ), + ) + .with_span(value.span) + .expected("(result i32 i32), (result i64 i32), (result f64 i32), (result bool i32), or (result string i32)") + .found(value.ty.to_string())); + } + + let kind = match op { + "is_ok" | "std.result.is_ok" => TExprKind::ResultIsOk { + source_name: op.to_string(), + value: Box::new(value), + }, + "is_err" | "std.result.is_err" => TExprKind::ResultIsErr { + source_name: op.to_string(), + value: Box::new(value), + }, + _ => unreachable!("unknown result observer"), + }; + + Ok(TExpr { + ty: Type::Bool, + span: expr.span, + kind, + }) +} + +fn check_option_unwrap( + file: &str, + expr: &Expr, + value: &Expr, + locals: &HashMap, + functions: &HashMap, + structs: &HashMap, + enums: &HashMap, + unsafe_context: bool, +) -> Result { + let value = check_expr( + file, + value, + locals, + functions, + structs, + enums, + unsafe_context, + )?; + let Type::Option(inner) = &value.ty else { + return Err(Diagnostic::new( + file, + "OptionUnwrapTypeMismatch", + "`unwrap_some` requires an `(option i32)`, `(option i64)`, `(option f64)`, `(option bool)`, or `(option string)` value", + ) + .with_span(value.span) + .expected("(option i32), (option i64), (option f64), (option bool), or (option string)") + .found(value.ty.to_string())); + }; + if !option_payload_type_supported(inner) { + return Err(Diagnostic::new( + file, + "OptionUnwrapTypeMismatch", + "`unwrap_some` requires an `(option i32)`, `(option i64)`, `(option f64)`, `(option bool)`, or `(option string)` value", + ) + .with_span(value.span) + .expected("(option i32), (option i64), (option f64), (option bool), or (option string)") + .found(value.ty.to_string())); + } + + Ok(TExpr { + ty: (**inner).clone(), + span: expr.span, + kind: TExprKind::OptionUnwrapSome { + value: Box::new(value), + }, + }) +} + +fn check_result_unwrap( + file: &str, + expr: &Expr, + value: &Expr, + op: &str, + locals: &HashMap, + functions: &HashMap, + structs: &HashMap, + enums: &HashMap, + unsafe_context: bool, +) -> Result { + let value = check_expr( + file, + value, + locals, + functions, + structs, + enums, + unsafe_context, + )?; + let (ok_ty, err_ty) = match &value.ty { + Type::Result(ok, err) if result_payload_types_supported(ok, err) => { + ((**ok).clone(), (**err).clone()) + } + _ => { + return Err(Diagnostic::new( + file, + "ResultUnwrapTypeMismatch", + format!( + "`{}` requires a `(result i32 i32)`, `(result i64 i32)`, `(result f64 i32)`, `(result bool i32)`, or `(result string i32)` value", + op + ), + ) + .with_span(value.span) + .expected("(result i32 i32), (result i64 i32), (result f64 i32), (result bool i32), or (result string i32)") + .found(value.ty.to_string())); + } + }; + + let result_ty = match op { + "unwrap_ok" | "std.result.unwrap_ok" => ok_ty, + "unwrap_err" | "std.result.unwrap_err" => err_ty, + _ => unreachable!("unknown result payload access"), + }; + + let kind = match op { + "unwrap_ok" | "std.result.unwrap_ok" => TExprKind::ResultUnwrapOk { + source_name: op.to_string(), + value: Box::new(value), + }, + "unwrap_err" | "std.result.unwrap_err" => TExprKind::ResultUnwrapErr { + source_name: op.to_string(), + value: Box::new(value), + }, + _ => unreachable!("unknown result payload access"), + }; + + Ok(TExpr { + ty: result_ty, + span: expr.span, + kind, + }) +} + +fn check_option_result_type(file: &str, ty: &Type, span: Span) -> Result<(), Diagnostic> { + match ty { + Type::Option(inner) => check_option_payload_type(file, inner, span), + Type::Result(ok, err) => check_result_payload_types(file, ok, span, err, span), + _ => Ok(()), + } +} + +fn check_option_payload_type(file: &str, ty: &Type, span: Span) -> Result<(), Diagnostic> { + if option_payload_type_supported(ty) { + Ok(()) + } else { + Err(unsupported_option_payload_type(file, span, ty)) + } +} + +fn option_payload_type_supported(ty: &Type) -> bool { + matches!( + ty, + Type::I32 | Type::I64 | Type::U32 | Type::U64 | Type::F64 | Type::Bool | Type::String + ) +} + +fn check_result_payload_types( + file: &str, + ok_ty: &Type, + ok_ty_span: Span, + err_ty: &Type, + err_ty_span: Span, +) -> Result<(), Diagnostic> { + if result_payload_types_supported(ok_ty, err_ty) { + return Ok(()); + } + + if err_ty != &Type::I32 { + return Err(unsupported_result_payload_type(file, err_ty_span, err_ty)); + } + + Err(unsupported_result_payload_type(file, ok_ty_span, ok_ty)) +} + +fn result_payload_types_supported(ok_ty: &Type, err_ty: &Type) -> bool { + matches!( + (ok_ty, err_ty), + (Type::I32, Type::I32) + | (Type::I64, Type::I32) + | (Type::U32, Type::I32) + | (Type::U64, Type::I32) + | (Type::F64, Type::I32) + | (Type::Bool, Type::I32) + | (Type::String, Type::I32) + ) +} + +fn result_constructor_payload_types_supported(ok_ty: &Type, err_ty: &Type) -> bool { + result_payload_types_supported(ok_ty, err_ty) +} + +fn result_match_payload_types_supported(ok_ty: &Type, err_ty: &Type) -> bool { + result_payload_types_supported(ok_ty, err_ty) +} + +fn check_result_constructor_payload_types( + file: &str, + ok_ty: &Type, + ok_ty_span: Span, + err_ty: &Type, + err_ty_span: Span, +) -> Result<(), Diagnostic> { + if result_constructor_payload_types_supported(ok_ty, err_ty) { + return Ok(()); + } + + if err_ty != &Type::I32 { + return Err(unsupported_result_payload_type(file, err_ty_span, err_ty)); + } + + Err(unsupported_result_payload_type(file, ok_ty_span, ok_ty)) +} + +fn unsupported_option_payload_type(file: &str, span: Span, ty: &Type) -> Diagnostic { + Diagnostic::new( + file, + "UnsupportedOptionPayloadType", + "first-pass options support only `i32`, `i64`, `u32`, `u64`, `f64`, `bool`, and `string` payloads", + ) + .with_span(span) + .expected("i32, i64, u32, u64, f64, bool, or string") + .found(ty.to_string()) +} + +fn unsupported_result_payload_type(file: &str, span: Span, ty: &Type) -> Diagnostic { + Diagnostic::new( + file, + "UnsupportedResultPayloadType", + "results currently support only `(result i32 i32)`, `(result i64 i32)`, `(result u32 i32)`, `(result u64 i32)`, `(result f64 i32)`, `(result bool i32)`, and `(result string i32)`", + ) + .with_span(span) + .expected("i32 ok with i32 err, i64 ok with i32 err, u32 ok with i32 err, u64 ok with i32 err, f64 ok with i32 err, bool ok with i32 err, or string ok with i32 err") + .found(ty.to_string()) +} + +fn check_array_init( + file: &str, + expr: &Expr, + elem_ty: &Type, + elem_ty_span: Span, + elements: &[Expr], + locals: &HashMap, + functions: &HashMap, + structs: &HashMap, + enums: &HashMap, + unsafe_context: bool, +) -> Result { + if !array_element_type_supported_known(elem_ty, structs, enums) { + return Err(Diagnostic::new( + file, + "UnsupportedArrayElementType", + supported_array_element_message(), + ) + .with_span(elem_ty_span) + .expected(supported_array_element_expected()) + .found(elem_ty.to_string())); + } + + if elements.is_empty() { + return Err(Diagnostic::new( + file, + "EmptyArrayUnsupported", + "first-pass arrays must contain at least one element", + ) + .with_span(expr.span) + .hint("provide one or more supported direct scalar, string, enum, or struct values")); + } + + let mut checked_elements = Vec::new(); + for element in elements { + let checked = check_expr( + file, + element, + locals, + functions, + structs, + enums, + unsafe_context, + )?; + if checked.ty != *elem_ty { + return Err( + Diagnostic::new(file, "TypeMismatch", "array element has the wrong type") + .with_span(checked.span) + .expected(elem_ty.to_string()) + .found(checked.ty.to_string()), + ); + } + checked_elements.push(checked); + } + + Ok(TExpr { + ty: Type::Array(Box::new(elem_ty.clone()), checked_elements.len()), + span: expr.span, + kind: TExprKind::ArrayInit { + elements: checked_elements, + }, + }) +} + +fn check_index( + file: &str, + expr: &Expr, + array: &Expr, + index: &Expr, + locals: &HashMap, + functions: &HashMap, + structs: &HashMap, + enums: &HashMap, + unsafe_context: bool, +) -> Result { + let checked_array = check_expr( + file, + array, + locals, + functions, + structs, + enums, + unsafe_context, + )?; + let Type::Array(_, len) = &checked_array.ty else { + return Err( + Diagnostic::new(file, "IndexOnNonArray", "`index` requires an array value") + .with_span(checked_array.span) + .expected("(array i32 N), (array i64 N), (array u32 N), (array u64 N), (array f64 N), (array bool N), (array string N), `(array KnownEnum N)`, or `(array KnownStruct N)`") + .found(checked_array.ty.to_string()), + ); + }; + let len = *len; + check_array_type(file, &checked_array.ty, checked_array.span, structs, enums)?; + + let checked_index = check_expr( + file, + index, + locals, + functions, + structs, + enums, + unsafe_context, + )?; + if checked_index.ty != Type::I32 { + return Err( + Diagnostic::new(file, "ArrayIndexNotI32", "`index` offset must be i32") + .with_span(checked_index.span) + .expected(Type::I32.to_string()) + .found(checked_index.ty.to_string()), + ); + } + + if let TExprKind::Int(index_value) = &checked_index.kind { + if *index_value < 0 || *index_value as usize >= len { + return Err(Diagnostic::new( + file, + "ArrayIndexOutOfBounds", + "array index is outside the fixed array bounds", + ) + .with_span(checked_index.span) + .expected(array_index_range(len)) + .found(index_value.to_string())); + } + } + + let Type::Array(inner, _) = &checked_array.ty else { + unreachable!("checked array type disappeared"); + }; + + Ok(TExpr { + ty: inner.as_ref().clone(), + span: expr.span, + kind: TExprKind::Index { + array: Box::new(checked_array), + index: Box::new(checked_index), + }, + }) +} + +fn check_array_type_decl( + file: &str, + ty: &Type, + span: Span, + declared_struct_names: &HashMap, + enums: &HashMap, +) -> Result<(), Diagnostic> { + let Type::Array(inner, len) = ty else { + return Ok(()); + }; + + if *len == 0 { + return Err(Diagnostic::new( + file, + "ZeroLengthArrayUnsupported", + "first-pass arrays must have positive length", + ) + .with_span(span) + .hint("use one or more supported direct scalar, string, enum, or struct elements")); + } + + if !array_element_type_supported_decl(inner, declared_struct_names, enums) { + return Err(Diagnostic::new( + file, + "UnsupportedArrayElementType", + supported_array_element_message(), + ) + .with_span(span) + .expected(supported_array_element_expected()) + .found(inner.to_string())); + } + + Ok(()) +} + +fn check_array_type( + file: &str, + ty: &Type, + span: Span, + structs: &HashMap, + enums: &HashMap, +) -> Result<(), Diagnostic> { + let Type::Array(inner, len) = ty else { + return Ok(()); + }; + + if *len == 0 { + return Err(Diagnostic::new( + file, + "ZeroLengthArrayUnsupported", + "first-pass arrays must have positive length", + ) + .with_span(span) + .hint("use one or more supported direct scalar, string, enum, or struct elements")); + } + + if !array_element_type_supported_known(inner, structs, enums) { + return Err(Diagnostic::new( + file, + "UnsupportedArrayElementType", + supported_array_element_message(), + ) + .with_span(span) + .expected(supported_array_element_expected()) + .found(inner.to_string())); + } + + Ok(()) +} + +fn supported_array_element_message() -> &'static str { + "fixed arrays support direct scalar `i32`, `i64`, `u32`, `u64`, `f64`, `bool`, `string`, direct known enum, or current known non-recursive struct elements" +} + +fn supported_array_element_expected() -> &'static str { + "i32, i64, u32, u64, f64, bool, string, direct known enum, or current known non-recursive struct type" +} + +fn array_element_type_supported_decl( + ty: &Type, + declared_struct_names: &HashMap, + enums: &HashMap, +) -> bool { + matches!( + ty, + Type::I32 | Type::I64 | Type::U32 | Type::U64 | Type::F64 | Type::Bool | Type::String + ) || matches!(ty, Type::Named(name) if declared_struct_names.contains_key(name) || enums.contains_key(name)) +} + +fn array_element_type_supported_known( + ty: &Type, + structs: &HashMap, + enums: &HashMap, +) -> bool { + matches!( + ty, + Type::I32 | Type::I64 | Type::U32 | Type::U64 | Type::F64 | Type::Bool | Type::String + ) || is_known_struct_type(ty, structs) + || is_known_enum_type(ty, enums) +} + +fn check_vector_type(file: &str, ty: &Type, span: Span) -> Result<(), Diagnostic> { + let Type::Vec(inner) = ty else { + return Ok(()); + }; + + if **inner != Type::I32 + && **inner != Type::I64 + && **inner != Type::F64 + && **inner != Type::Bool + && **inner != Type::String + { + return Err(Diagnostic::new( + file, + "UnsupportedVectorElementType", + "vectors support only `i32`, `i64`, `f64`, `bool`, or `string` elements in the current concrete alpha slices", + ) + .with_span(span) + .expected("i32, i64, f64, bool, or string") + .found(inner.to_string()) + .hint("use exactly `(vec i32)`, `(vec i64)`, `(vec f64)`, `(vec bool)`, or `(vec string)` in the collections alpha slices")); + } + + Ok(()) +} + +fn array_index_range(len: usize) -> String { + if len == 1 { + "0".to_string() + } else { + format!("0..{}", len - 1) + } +} + +fn check_field_access( + file: &str, + expr: &Expr, + value: &Expr, + field: &str, + field_span: Span, + locals: &HashMap, + functions: &HashMap, + structs: &HashMap, + enums: &HashMap, + unsafe_context: bool, +) -> Result { + let value = check_expr( + file, + value, + locals, + functions, + structs, + enums, + unsafe_context, + )?; + let Type::Named(struct_name) = &value.ty else { + return Err(Diagnostic::new( + file, + "FieldAccessOnNonStruct", + "field access requires a struct value", + ) + .with_span(value.span) + .expected("struct") + .found(value.ty.to_string())); + }; + + let sig = structs.get(struct_name).ok_or_else(|| { + Diagnostic::new( + file, + "UnknownStruct", + format!("unknown struct `{}`", struct_name), + ) + .with_span(value.span) + })?; + + let Some(index) = sig.fields_by_name.get(field) else { + return Err(Diagnostic::new( + file, + "UnknownStructField", + format!("struct `{}` has no field `{}`", struct_name, field), + ) + .with_span(field_span)); + }; + + Ok(TExpr { + ty: sig.fields[*index].ty.clone(), + span: expr.span, + kind: TExprKind::FieldAccess { + value: Box::new(value), + field: field.to_string(), + }, + }) +} + +fn expect_type(file: &str, expr: &Expr, found: &Type, expected: &Type) -> Result<(), Diagnostic> { + if found == expected { + Ok(()) + } else { + Err(Diagnostic::new(file, "TypeMismatch", "type mismatch") + .with_span(expr.span) + .expected(expected.to_string()) + .found(found.to_string())) + } +} diff --git a/compiler/src/diag.rs b/compiler/src/diag.rs new file mode 100644 index 0000000..3de2b2d --- /dev/null +++ b/compiler/src/diag.rs @@ -0,0 +1,554 @@ +use crate::token::Span; + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Severity { + Error, +} + +impl Severity { + fn as_str(self) -> &'static str { + match self { + Self::Error => "error", + } + } +} + +#[derive(Debug, Clone)] +pub struct Diagnostic { + pub severity: Severity, + pub code: &'static str, + pub message: String, + pub span: Option, + pub expected: Option, + pub found: Option, + pub hint: Option, + pub related: Vec, + pub file: String, +} + +#[derive(Debug, Clone)] +pub struct RelatedSpan { + pub file: String, + pub label: String, + pub span: Span, +} + +impl Diagnostic { + pub fn new(file: impl Into, code: &'static str, message: impl Into) -> Self { + Self { + severity: Severity::Error, + file: file.into(), + code, + message: message.into(), + span: None, + expected: None, + found: None, + hint: None, + related: Vec::new(), + } + } + + pub fn with_span(mut self, span: Span) -> Self { + self.span = Some(span); + self + } + + pub fn expected(mut self, expected: impl Into) -> Self { + self.expected = Some(expected.into()); + self + } + + pub fn found(mut self, found: impl Into) -> Self { + self.found = Some(found.into()); + self + } + + pub fn hint(mut self, hint: impl Into) -> Self { + self.hint = Some(hint.into()); + self + } + + pub fn related(mut self, label: impl Into, span: Span) -> Self { + self.related.push(RelatedSpan { + file: self.file.clone(), + label: label.into(), + span, + }); + self + } + + pub fn related_in_file( + mut self, + file: impl Into, + label: impl Into, + span: Span, + ) -> Self { + self.related.push(RelatedSpan { + file: file.into(), + label: label.into(), + span, + }); + self + } + + pub fn render_human(&self, source: &str) -> String { + self.render_human_with_sources(|file| { + if file == self.file { + Some(source) + } else { + None + } + }) + } + + pub fn render_human_with_sources<'a>( + &self, + source_for: impl Fn(&str) -> Option<&'a str>, + ) -> String { + let mut out = String::new(); + let source = source_for(&self.file).unwrap_or(""); + + out.push_str(&format!( + "{}[{}]: {}\n", + self.severity.as_str(), + self.code, + self.message + )); + + if let Some(span) = self.span { + if let Some(range) = SourceRange::new(source, span) { + out.push_str(&format!( + "--> {}:{}:{}-{}:{}\n", + self.file, + range.start.line, + range.start.column, + range.end.line, + range.end.column + )); + } + + let excerpt = source_line_at(source, span.start) + .or_else(|| source.get(span.start..span.end)) + .unwrap_or(""); + out.push_str(&format!("\nAt:\n {}\n", excerpt)); + } + + if let Some(expected) = &self.expected { + out.push_str(&format!("\nExpected:\n {}\n", expected)); + } + + if let Some(found) = &self.found { + out.push_str(&format!("\nFound:\n {}\n", found)); + } + + if let Some(hint) = &self.hint { + out.push_str(&format!("\nHint:\n {}\n", hint)); + } + + for related in &self.related { + out.push_str(&format!("\nRelated:\n {}\n", related.label)); + let related_source = source_for(&related.file).unwrap_or(""); + if let Some(range) = SourceRange::new(related_source, related.span) { + out.push_str(&format!( + " --> {}:{}:{}-{}:{}\n", + related.file, + range.start.line, + range.start.column, + range.end.line, + range.end.column + )); + } + + let excerpt = source_line_at(related_source, related.span.start) + .or_else(|| related_source.get(related.span.start..related.span.end)) + .unwrap_or(""); + out.push_str(&format!(" {}\n", excerpt)); + } + + out + } + + pub fn render_machine(&self, source: &str) -> String { + self.render_machine_with_sources(|file| { + if file == self.file { + Some(source) + } else { + None + } + }) + } + + pub fn render_machine_with_sources<'a>( + &self, + source_for: impl Fn(&str) -> Option<&'a str>, + ) -> String { + let source = source_for(&self.file).unwrap_or(""); + let mut parts = vec![ + " (schema slovo.diagnostic)".to_string(), + " (version 1)".to_string(), + format!(" (severity {})", self.severity.as_str()), + format!(" (code {})", self.code), + format!(" (message {})", render_string(&self.message)), + format!(" (file {})", render_string(&self.file)), + ]; + + if let Some(span) = self.span { + parts.push(render_machine_span( + " ", + span, + SourceRange::new(source, span), + )); + } + + if let Some(expected) = &self.expected { + parts.push(format!(" (expected {})", render_string(expected))); + } + + if let Some(found) = &self.found { + parts.push(format!(" (found {})", render_string(found))); + } + + if let Some(hint) = &self.hint { + parts.push(format!(" (hint {})", render_string(hint))); + } + + for related in &self.related { + let related_source = source_for(&related.file).unwrap_or(""); + parts.push(render_machine_related( + " ", + &related.file, + related, + SourceRange::new(related_source, related.span), + )); + } + + format!("(diagnostic\n{}\n)", parts.join("\n")) + } + + pub fn render_json(&self, source: &str) -> String { + self.render_json_with_sources(|file| { + if file == self.file { + Some(source) + } else { + None + } + }) + } + + pub fn render_json_with_sources<'a>( + &self, + source_for: impl Fn(&str) -> Option<&'a str>, + ) -> String { + let source = source_for(&self.file).unwrap_or(""); + let file = Some(self.file.as_str()); + render_json_diagnostic( + self.severity.as_str(), + self.code, + &self.message, + file, + self.span.map(|span| (span, SourceRange::new(source, span))), + self.expected.as_deref(), + self.found.as_deref(), + self.hint.as_deref(), + self.related.iter().map(|related| JsonRelated { + file: &related.file, + span: related.span, + range: SourceRange::new(source_for(&related.file).unwrap_or(""), related.span), + message: Some(&related.label), + }), + ) + } +} + +pub fn render_json_message( + severity: &str, + code: &str, + message: &str, + hint: Option<&str>, +) -> String { + render_json_diagnostic( + severity, + code, + message, + None, + None, + None, + None, + hint, + std::iter::empty::>(), + ) +} + +pub fn render_string(value: &str) -> String { + let mut out = String::from("\""); + + for ch in value.chars() { + match ch { + '\\' => out.push_str("\\\\"), + '"' => out.push_str("\\\""), + '\n' => out.push_str("\\n"), + '\r' => out.push_str("\\r"), + '\t' => out.push_str("\\t"), + ch if ch.is_control() => out.push_str(&format!("\\u{{{:x}}}", ch as u32)), + ch => out.push(ch), + } + } + + out.push('"'); + out +} + +fn render_json_diagnostic<'a>( + severity: &str, + code: &str, + message: &str, + file: Option<&str>, + span: Option<(Span, Option)>, + expected: Option<&str>, + found: Option<&str>, + hint: Option<&str>, + related: impl Iterator>, +) -> String { + let mut fields = vec![ + "\"schema\":\"slovo.diagnostic\"".to_string(), + "\"version\":1".to_string(), + format!("\"severity\":{}", render_json_string(severity)), + format!("\"code\":{}", render_json_string(code)), + format!("\"message\":{}", render_json_string(message)), + format!("\"file\":{}", render_json_nullable_string(file)), + format!("\"span\":{}", render_json_span(span)), + ]; + + if let Some(expected) = expected { + fields.push(format!("\"expected\":{}", render_json_string(expected))); + } + + if let Some(found) = found { + fields.push(format!("\"found\":{}", render_json_string(found))); + } + + if let Some(hint) = hint { + fields.push(format!("\"hint\":{}", render_json_string(hint))); + } + + let related = related.map(render_json_related).collect::>(); + if !related.is_empty() { + fields.push(format!("\"related\":[{}]", related.join(","))); + } + + format!("{{{}}}", fields.join(",")) +} + +struct JsonRelated<'a> { + file: &'a str, + span: Span, + range: Option, + message: Option<&'a str>, +} + +fn render_json_related(related: JsonRelated<'_>) -> String { + let mut fields = vec![ + format!("\"file\":{}", render_json_string(related.file)), + format!( + "\"span\":{}", + render_json_span(Some((related.span, related.range))) + ), + ]; + + if let Some(message) = related.message { + fields.push(format!("\"message\":{}", render_json_string(message))); + } + + format!("{{{}}}", fields.join(",")) +} + +fn render_json_span(span: Option<(Span, Option)>) -> String { + let Some((span, range)) = span else { + return "null".to_string(); + }; + + let mut fields = vec![ + format!("\"byte_start\":{}", span.start), + format!("\"byte_end\":{}", span.end), + ]; + + if let Some(range) = range { + fields.push(format!("\"line_start\":{}", range.start.line)); + fields.push(format!("\"column_start\":{}", range.start.column)); + fields.push(format!("\"line_end\":{}", range.end.line)); + fields.push(format!("\"column_end\":{}", range.end.column)); + } + + format!("{{{}}}", fields.join(",")) +} + +fn render_json_nullable_string(value: Option<&str>) -> String { + value + .map(render_json_string) + .unwrap_or_else(|| "null".to_string()) +} + +fn render_json_string(value: &str) -> String { + let mut out = String::from("\""); + + for ch in value.chars() { + match ch { + '\\' => out.push_str("\\\\"), + '"' => out.push_str("\\\""), + '\n' => out.push_str("\\n"), + '\r' => out.push_str("\\r"), + '\t' => out.push_str("\\t"), + '\u{08}' => out.push_str("\\b"), + '\u{0c}' => out.push_str("\\f"), + ch if ch <= '\u{1f}' => out.push_str(&format!("\\u{:04x}", ch as u32)), + ch => out.push(ch), + } + } + + out.push('"'); + out +} + +fn render_machine_span(indent: &str, span: Span, range: Option) -> String { + let mut out = format!( + "{indent}(span\n{indent} (bytes {} {})", + span.start, span.end + ); + + if let Some(range) = range { + out.push_str(&format!( + "\n{indent} (range {} {} {} {})", + range.start.line, range.start.column, range.end.line, range.end.column + )); + } + + out.push_str(&format!("\n{indent})")); + out +} + +fn render_machine_related( + indent: &str, + file: &str, + related: &RelatedSpan, + range: Option, +) -> String { + let span_indent = format!("{indent} "); + let mut span_parts = vec![ + format!("{span_indent}(file {})", render_string(file)), + format!( + "{span_indent}(bytes {} {})", + related.span.start, related.span.end + ), + ]; + + if let Some(range) = range { + span_parts.push(format!( + "{span_indent}(range {} {} {} {})", + range.start.line, range.start.column, range.end.line, range.end.column + )); + } + + span_parts.push(format!( + "{span_indent}(message {})", + render_string(&related.label) + )); + + format!( + "{indent}(related\n{indent} (span\n{}\n{indent} )\n{indent})", + span_parts.join("\n") + ) +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +struct SourceRange { + start: SourcePosition, + end: SourcePosition, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +struct SourcePosition { + line: usize, + column: usize, +} + +impl SourceRange { + fn new(source: &str, span: Span) -> Option { + if span.start > span.end || span.end > source.len() { + return None; + } + + Some(Self { + start: source_position(source, span.start), + end: source_position(source, span.end), + }) + } +} + +fn source_position(source: &str, offset: usize) -> SourcePosition { + let mut line = 1; + let mut column = 1; + + for byte in source.as_bytes().iter().take(offset) { + if *byte == b'\n' { + line += 1; + column = 1; + } else { + column += 1; + } + } + + SourcePosition { line, column } +} + +fn source_line_at(source: &str, offset: usize) -> Option<&str> { + if offset > source.len() { + return None; + } + + let start = source[..offset].rfind('\n').map_or(0, |index| index + 1); + let end = source[offset..] + .find('\n') + .map_or(source.len(), |index| offset + index); + + source.get(start..end) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn source_range_uses_one_based_line_and_column_numbers() { + let source = "first\n second\nthird"; + + let range = SourceRange::new(source, Span::new(8, 14)).unwrap(); + + assert_eq!( + range, + SourceRange { + start: SourcePosition { line: 2, column: 3 }, + end: SourcePosition { line: 2, column: 9 }, + } + ); + } + + #[test] + fn source_range_columns_are_utf8_byte_columns() { + let source = "pre ž value"; + let start = source.find("value").unwrap(); + let end = start + "value".len(); + + let range = SourceRange::new(source, Span::new(start, end)).unwrap(); + + assert_eq!( + range, + SourceRange { + start: SourcePosition { line: 1, column: 8 }, + end: SourcePosition { + line: 1, + column: 13 + }, + } + ); + } +} diff --git a/compiler/src/docgen.rs b/compiler/src/docgen.rs new file mode 100644 index 0000000..f96fb23 --- /dev/null +++ b/compiler/src/docgen.rs @@ -0,0 +1,229 @@ +use std::{fs, path::Path}; + +use crate::{ + ast::Program, + diag::Diagnostic, + lexer, lower, + project::{self, SourceFile, ToolFailure}, + sexpr::{Atom, SExpr, SExprKind}, +}; + +pub fn generate(input: &str, output_dir: &str) -> Result<(), ToolFailure> { + let docs = if project::is_project_input(input) { + let loaded = project::load_project_sources_for_tools(input)?; + render_project(&loaded.artifact.project_name, &loaded.sources)? + } else { + let source = fs::read_to_string(input).map_err(|err| ToolFailure { + diagnostics: vec![Diagnostic::new( + input, + "InputReadFailed", + format!("cannot read `{}`: {}", input, err), + )], + sources: Vec::new(), + artifact: None, + })?; + let sources = vec![SourceFile { + path: input.to_string(), + source, + }]; + render_file(input, &sources)? + }; + + fs::create_dir_all(output_dir).map_err(|err| ToolFailure { + diagnostics: vec![Diagnostic::new( + output_dir, + "OutputWriteFailed", + format!( + "cannot create documentation directory `{}`: {}", + output_dir, err + ), + )], + sources: Vec::new(), + artifact: None, + })?; + fs::write(Path::new(output_dir).join("index.md"), docs).map_err(|err| ToolFailure { + diagnostics: vec![Diagnostic::new( + output_dir, + "OutputWriteFailed", + format!("cannot write documentation into `{}`: {}", output_dir, err), + )], + sources: Vec::new(), + artifact: None, + }) +} + +fn render_project(title: &str, sources: &[SourceFile]) -> Result { + let mut out = String::new(); + out.push_str("# Project "); + out.push_str(title); + out.push_str("\n\n"); + for source in sources { + let module = document_source(source)?; + render_module(&mut out, &module); + } + Ok(out) +} + +fn render_file(title: &str, sources: &[SourceFile]) -> Result { + let mut out = String::new(); + out.push_str("# File "); + out.push_str(title); + out.push_str("\n\n"); + let module = document_source(&sources[0])?; + render_module(&mut out, &module); + Ok(out) +} + +fn document_source(source: &SourceFile) -> Result { + let tokens = lexer::lex(&source.path, &source.source).map_err(|diagnostics| ToolFailure { + diagnostics, + sources: vec![source.clone()], + artifact: None, + })?; + let forms = crate::sexpr::parse(&source.path, &tokens).map_err(|diagnostics| ToolFailure { + diagnostics, + sources: vec![source.clone()], + artifact: None, + })?; + let program = lower::lower_program(&source.path, &forms).ok(); + Ok(module_from_forms(&source.path, &forms, program.as_ref())) +} + +struct DocModule { + title: String, + imports: Vec, + exports: Vec, + structs: Vec, + functions: Vec, + tests: Vec, +} + +fn module_from_forms(file: &str, forms: &[SExpr], program: Option<&Program>) -> DocModule { + let mut module = DocModule { + title: file.to_string(), + imports: Vec::new(), + exports: Vec::new(), + structs: Vec::new(), + functions: Vec::new(), + tests: Vec::new(), + }; + + for form in forms { + let Some(items) = list(form) else { + continue; + }; + match items.first().and_then(ident) { + Some("module") => { + if let Some(name) = items.get(1).and_then(ident) { + module.title = name.to_string(); + } + if let Some(exports) = items.get(2).and_then(list) { + if matches!(exports.first().and_then(ident), Some("export")) { + module + .exports + .extend(exports[1..].iter().filter_map(ident).map(str::to_string)); + } + } + } + Some("import") => { + if let Some(name) = items.get(1).and_then(ident) { + module.imports.push(name.to_string()); + } + } + Some("struct") => { + if let Some(name) = items.get(1).and_then(ident) { + module.structs.push(name.to_string()); + } + } + Some("fn") => { + if let Some(name) = items.get(1).and_then(ident) { + module.functions.push(name.to_string()); + } + } + Some("test") => { + if let Some(name) = items.get(1).and_then(string_atom) { + module.tests.push(name.to_string()); + } + } + _ => {} + } + } + + if let Some(program) = program { + module.title = program.module.clone(); + module.structs = program + .structs + .iter() + .map(|decl| decl.name.clone()) + .collect(); + module.functions = program + .functions + .iter() + .map(|function| { + let params = function + .params + .iter() + .map(|param| format!("{} {}", param.name, param.ty)) + .collect::>() + .join(", "); + format!("{}({}) -> {}", function.name, params, function.return_type) + }) + .collect(); + module.tests = program.tests.iter().map(|test| test.name.clone()).collect(); + } + + module.imports.sort(); + module.exports.sort(); + module.structs.sort(); + module.functions.sort(); + module.tests.sort(); + module +} + +fn render_module(out: &mut String, module: &DocModule) { + out.push_str("## Module "); + out.push_str(&module.title); + out.push_str("\n\n"); + render_list(out, "Imports", &module.imports); + render_list(out, "Exports", &module.exports); + render_list(out, "Structs", &module.structs); + render_list(out, "Functions", &module.functions); + render_list(out, "Tests", &module.tests); +} + +fn render_list(out: &mut String, title: &str, values: &[String]) { + out.push_str("### "); + out.push_str(title); + out.push('\n'); + if values.is_empty() { + out.push_str("\nNone.\n\n"); + return; + } + for value in values { + out.push_str("- `"); + out.push_str(&value.replace('`', "\\`")); + out.push_str("`\n"); + } + out.push('\n'); +} + +fn list(expr: &SExpr) -> Option<&[SExpr]> { + match &expr.kind { + SExprKind::List(items) => Some(items), + _ => None, + } +} + +fn ident(expr: &SExpr) -> Option<&str> { + match &expr.kind { + SExprKind::Atom(Atom::Ident(value)) => Some(value), + _ => None, + } +} + +fn string_atom(expr: &SExpr) -> Option<&str> { + match &expr.kind { + SExprKind::Atom(Atom::String(value)) => Some(value), + _ => None, + } +} diff --git a/compiler/src/driver.rs b/compiler/src/driver.rs new file mode 100644 index 0000000..fa7874e --- /dev/null +++ b/compiler/src/driver.rs @@ -0,0 +1,101 @@ +use crate::{ + check, diag::Diagnostic, formatter, lexer, llvm, lower, sexpr, test_runner, types::Type, +}; + +pub fn compile_to_llvm(file: &str, source: &str) -> Result> { + let checked = checked_program(file, source)?; + llvm::emit(file, &checked) +} + +pub fn check_source(file: &str, source: &str) -> Result> { + let checked = checked_program(file, source)?; + llvm::emit(file, &checked)?; + Ok(String::new()) +} + +pub fn print_parse_tree(file: &str, source: &str) -> Result> { + let tokens = lexer::lex(file, source)?; + let forms = sexpr::parse(file, &tokens)?; + Ok(sexpr::print_tree(&forms)) +} + +pub fn format_source(file: &str, source: &str) -> Result> { + let tokens = lexer::lex(file, source)?; + let forms = sexpr::parse(file, &tokens)?; + formatter::format(file, source, &forms) +} + +pub fn inspect_lowering_surface(file: &str, source: &str) -> Result> { + let tokens = lexer::lex(file, source)?; + let forms = sexpr::parse(file, &tokens)?; + let program = lower::lower_program(file, &forms)?; + Ok(lower::print_program(&program)) +} + +pub fn inspect_lowering_checked(file: &str, source: &str) -> Result> { + let checked = checked_program(file, source)?; + Ok(check::print_checked_program(&checked)) +} + +pub fn check_tests(file: &str, source: &str) -> Result> { + let checked = checked_program(file, source)?; + Ok(test_runner::check_output(&checked)) +} + +pub fn run_tests( + file: &str, + source: &str, + filter: Option<&str>, +) -> Result { + let checked = + checked_program(file, source).map_err(test_runner::TestRunFailure::before_execution)?; + test_runner::run(file, &checked, filter) +} + +fn checked_program(file: &str, source: &str) -> Result> { + let tokens = lexer::lex(file, source)?; + let forms = sexpr::parse(file, &tokens)?; + let program = lower::lower_program(file, &forms)?; + let checked = check::check_program(file, program)?; + validate_single_file_main_signature(file, &checked)?; + Ok(checked) +} + +fn validate_single_file_main_signature( + file: &str, + program: &check::CheckedProgram, +) -> Result<(), Vec> { + let Some(main) = program + .functions + .iter() + .find(|function| function.name == "main") + else { + return Ok(()); + }; + + if main.params.is_empty() && main.return_type == Type::I32 { + return Ok(()); + } + + let found = format!( + "fn main ({}) -> {}", + main.params + .iter() + .map(|(_, ty)| ty.to_string()) + .collect::>() + .join(" "), + main.return_type + ); + + Err(vec![Diagnostic::new( + file, + "SingleFileMainSignature", + "single-file `main` must have no parameters and return `i32`", + ) + .with_span(main.span) + .expected("fn main () -> i32") + .found(found) + .hint( + "return an i32 exit status from main; use helper functions for wider values", + )]) +} diff --git a/compiler/src/formatter.rs b/compiler/src/formatter.rs new file mode 100644 index 0000000..800dfb9 --- /dev/null +++ b/compiler/src/formatter.rs @@ -0,0 +1,2717 @@ +use std::collections::{HashMap, HashSet}; + +use crate::{ + diag::Diagnostic, + sexpr::{Atom, SExpr, SExprKind}, + std_runtime, + token::Span, +}; + +pub fn format(file: &str, source: &str, forms: &[SExpr]) -> Result> { + let (comments, comment_errors) = collect_comments(source) + .into_iter() + .partition::, _>(|comment| comment.full_line); + let mut formatter = Formatter { + file, + comments, + next_comment: 0, + output: String::new(), + function_names: collect_function_names(forms), + struct_names: collect_struct_names(forms), + enum_names: collect_enum_names(forms), + errors: comment_errors + .into_iter() + .map(|comment| unsupported_non_full_line_comment(file, comment.span)) + .collect(), + }; + + formatter.write_forms(forms); + + if formatter.errors.is_empty() { + Ok(formatter.output) + } else { + Err(formatter.errors) + } +} + +struct Formatter<'a> { + file: &'a str, + comments: Vec, + next_comment: usize, + output: String, + function_names: HashSet, + struct_names: HashSet, + enum_names: HashSet, + errors: Vec, +} + +impl Formatter<'_> { + fn write_forms(&mut self, forms: &[SExpr]) { + for (index, form) in forms.iter().enumerate() { + let before = self.take_comments_before(form.span.start); + + if index > 0 { + self.output.push('\n'); + } + + self.write_comments(&before, ""); + + if !before.is_empty() { + self.output.push('\n'); + } + + match list_head(form) { + Some("module") => self.write_module(form), + Some("import") => self.write_import(form), + Some("import_c") => self.write_c_import(form), + Some("enum") => self.write_enum(form), + Some("struct") => self.write_struct(form), + Some("fn") => self.write_function(form), + Some("test") => self.write_test(form), + Some(other) => self.errors.push( + Diagnostic::new( + self.file, + "UnsupportedFormatterForm", + format!("formatter does not support top-level form `{}`", other), + ) + .with_span(form.span) + .hint("current formatter syntax is limited to module/import declarations, C imports, strict `struct` forms, strict `fn` forms, and strict top-level `test` forms"), + ), + None => self.errors.push( + Diagnostic::new( + self.file, + "UnsupportedFormatterForm", + "formatter expected a top-level form", + ) + .with_span(form.span), + ), + } + + self.output.push('\n'); + } + + let trailing = self.take_remaining_comments(); + if !trailing.is_empty() { + if !self.output.is_empty() { + self.output.push('\n'); + } + self.write_comments(&trailing, ""); + } + } + + fn write_module(&mut self, form: &SExpr) { + let Some(items) = expect_list(form) else { + self.errors.push( + Diagnostic::new(self.file, "InvalidModule", "module form must be a list") + .with_span(form.span), + ); + return; + }; + + if items.len() != 2 && items.len() != 3 { + self.errors.push( + Diagnostic::new( + self.file, + "InvalidModule", + "module form must be `(module name)` or `(module name (export ...))`", + ) + .with_span(form.span), + ); + return; + } + + let Some(name) = expect_ident(&items[1]) else { + self.errors.push( + Diagnostic::new( + self.file, + "InvalidModuleName", + "module name must be an identifier", + ) + .with_span(items[1].span), + ); + return; + }; + + self.output.push_str("(module "); + self.output.push_str(name); + if let Some(export_form) = items.get(2) { + let Some(exports) = expect_list(export_form) else { + self.errors.push( + Diagnostic::new( + self.file, + "InvalidExport", + "export list must be `(export name...)`", + ) + .with_span(export_form.span), + ); + return; + }; + if !matches!(exports.first().and_then(expect_ident), Some("export")) { + self.errors.push( + Diagnostic::new( + self.file, + "InvalidExport", + "module option must be an export list", + ) + .with_span(export_form.span), + ); + return; + } + self.output.push_str(" (export"); + for export in &exports[1..] { + let Some(name) = expect_ident(export) else { + self.errors.push( + Diagnostic::new( + self.file, + "InvalidExport", + "exported name must be an identifier", + ) + .with_span(export.span), + ); + continue; + }; + self.output.push(' '); + self.output.push_str(name); + } + self.output.push(')'); + } + self.reject_comments_before( + form.span.end, + "formatter does not support comments inside module forms", + ); + self.output.push(')'); + } + + fn write_import(&mut self, form: &SExpr) { + let Some(items) = expect_list(form) else { + self.errors.push( + Diagnostic::new(self.file, "InvalidImport", "import form must be a list") + .with_span(form.span), + ); + return; + }; + if items.len() != 3 { + self.errors.push( + Diagnostic::new( + self.file, + "InvalidImport", + "import form must be `(import module (name...))`", + ) + .with_span(form.span), + ); + return; + } + let Some(module) = expect_ident(&items[1]) else { + self.errors.push( + Diagnostic::new( + self.file, + "InvalidImport", + "import module must be an identifier", + ) + .with_span(items[1].span), + ); + return; + }; + let Some(names) = expect_list(&items[2]) else { + self.errors.push( + Diagnostic::new( + self.file, + "InvalidImport", + "import list must contain imported names", + ) + .with_span(items[2].span), + ); + return; + }; + + self.output.push_str("(import "); + self.output.push_str(module); + self.output.push_str(" ("); + for (index, name) in names.iter().enumerate() { + let Some(name) = expect_ident(name) else { + self.errors.push( + Diagnostic::new( + self.file, + "InvalidImport", + "imported name must be an identifier", + ) + .with_span(name.span), + ); + continue; + }; + if index > 0 { + self.output.push(' '); + } + self.output.push_str(name); + } + self.output.push_str("))"); + self.reject_comments_before( + form.span.end, + "formatter does not support comments inside import forms", + ); + } + + fn write_struct(&mut self, form: &SExpr) { + let Some(items) = expect_list(form) else { + self.errors.push( + Diagnostic::new( + self.file, + "MalformedStructForm", + "struct form must be a list", + ) + .with_span(form.span), + ); + return; + }; + + if items.len() < 2 { + self.errors.push( + Diagnostic::new( + self.file, + "MalformedStructForm", + "struct form must be `(struct Name (field type)...)`", + ) + .with_span(form.span), + ); + return; + } + + let Some(name) = expect_ident(&items[1]) else { + self.errors.push( + Diagnostic::new( + self.file, + "InvalidStructName", + "struct name must be an identifier", + ) + .with_span(items[1].span), + ); + return; + }; + + let mut fields = Vec::new(); + for item in &items[2..] { + let Some(pair) = expect_list(item) else { + self.errors.push( + Diagnostic::new( + self.file, + "InvalidStructField", + "struct field must be `(name type)`", + ) + .with_span(item.span), + ); + continue; + }; + + if pair.len() != 2 { + self.errors.push( + Diagnostic::new( + self.file, + "InvalidStructField", + "struct field must be `(name type)`", + ) + .with_span(item.span), + ); + continue; + } + + let Some(field_name) = expect_ident(&pair[0]) else { + self.errors.push( + Diagnostic::new( + self.file, + "InvalidStructFieldName", + "struct field name must be an identifier", + ) + .with_span(pair[0].span), + ); + continue; + }; + + let Some(field_ty) = self.render_struct_field_type(&pair[1]) else { + continue; + }; + + fields.push((field_name, field_ty)); + } + + self.reject_comments_before( + form.span.end, + "formatter does not support comments inside struct forms", + ); + + self.output.push_str("(struct "); + self.output.push_str(name); + for (field, field_ty) in fields { + self.output.push('\n'); + self.output.push_str(" ("); + self.output.push_str(field); + self.output.push(' '); + self.output.push_str(&field_ty); + self.output.push(')'); + } + self.output.push(')'); + } + + fn write_c_import(&mut self, form: &SExpr) { + let Some(items) = expect_list(form) else { + self.errors.push( + Diagnostic::new( + self.file, + "MalformedCImport", + "`import_c` form must be a list", + ) + .with_span(form.span), + ); + return; + }; + if items.len() != 5 { + self.errors.push( + Diagnostic::new( + self.file, + "MalformedCImport", + "`import_c` form must be `(import_c name ((arg i32)...) -> ReturnType)`", + ) + .with_span(form.span), + ); + return; + } + let Some(name) = expect_ident(&items[1]) else { + self.errors.push( + Diagnostic::new( + self.file, + "MalformedCImport", + "C import name must be an identifier", + ) + .with_span(items[1].span), + ); + return; + }; + if !is_c_symbol_name(name) { + self.errors.push( + Diagnostic::new( + self.file, + "MalformedCImport", + "C import name must be a C symbol identifier", + ) + .with_span(items[1].span) + .expected("ASCII letter or `_`, followed by ASCII letters, digits, or `_`") + .found(name.to_string()), + ); + return; + } + if !matches!(items[3].kind, SExprKind::Atom(Atom::Arrow)) { + self.errors.push( + Diagnostic::new( + self.file, + "MalformedCImport", + "expected `->` in C import signature", + ) + .with_span(items[3].span), + ); + return; + } + let Some(params) = self.params(&items[2]) else { + return; + }; + let Some(return_type) = self.render_c_import_return_type(&items[4]) else { + return; + }; + + self.output.push_str("(import_c "); + self.output.push_str(name); + self.output.push(' '); + self.write_params(¶ms); + self.output.push_str(" -> "); + self.output.push_str(&return_type); + self.output.push(')'); + } + + fn write_enum(&mut self, form: &SExpr) { + let Some(items) = expect_list(form) else { + self.errors.push( + Diagnostic::new(self.file, "MalformedEnumForm", "enum form must be a list") + .with_span(form.span), + ); + return; + }; + + if items.len() < 2 { + self.errors.push( + Diagnostic::new( + self.file, + "MalformedEnumForm", + "enum form must be `(enum Name Variant...)`", + ) + .with_span(form.span), + ); + return; + } + + let Some(name) = expect_ident(&items[1]) else { + self.errors.push( + Diagnostic::new( + self.file, + "InvalidEnumName", + "enum name must be an identifier", + ) + .with_span(items[1].span), + ); + return; + }; + + let mut variants = Vec::new(); + let mut has_payload_variant = false; + let mut enum_payload_ty: Option<(String, Span)> = None; + for item in &items[2..] { + if let Some(variant) = expect_ident(item) { + variants.push(variant.to_string()); + continue; + } + + let Some(variant_items) = expect_list(item) else { + self.errors.push( + Diagnostic::new( + self.file, + "InvalidEnumVariant", + "enum variants must be identifiers or unary payload forms", + ) + .with_span(item.span), + ); + continue; + }; + if variant_items.len() != 2 { + self.errors.push( + Diagnostic::new( + self.file, + "InvalidEnumVariant", + "enum payload variants must be unary forms", + ) + .with_span(item.span) + .expected("(Variant i32), (Variant i64), (Variant f64), (Variant bool), (Variant string), or (Variant KnownStruct)"), + ); + continue; + } + let Some(variant) = expect_ident(&variant_items[0]) else { + self.errors.push( + Diagnostic::new( + self.file, + "InvalidEnumVariant", + "enum variant name must be an identifier", + ) + .with_span(variant_items[0].span), + ); + continue; + }; + let payload_ty = if is_ident(&variant_items[1], "i32") { + "i32".to_string() + } else if is_ident(&variant_items[1], "i64") { + "i64".to_string() + } else if is_ident(&variant_items[1], "f64") { + "f64".to_string() + } else if is_ident(&variant_items[1], "bool") { + "bool".to_string() + } else if is_ident(&variant_items[1], "string") { + "string".to_string() + } else if let Some(name) = expect_ident(&variant_items[1]) { + if self.struct_names.contains(name) { + name.to_string() + } else { + self.errors.push( + Diagnostic::new( + self.file, + "UnsupportedFormatterForm", + "formatter supports only unary direct i32, i64, f64, bool, string, and known non-recursive struct enum payload variants", + ) + .with_span(variant_items[1].span) + .expected("i32, i64, f64, bool, string, or known non-recursive struct type"), + ); + continue; + } + } else { + self.errors.push( + Diagnostic::new( + self.file, + "UnsupportedFormatterForm", + "formatter supports only unary direct i32, i64, f64, bool, string, and known non-recursive struct enum payload variants", + ) + .with_span(variant_items[1].span) + .expected("i32, i64, f64, bool, string, or known non-recursive struct type"), + ); + continue; + }; + if let Some((expected_ty, expected_span)) = &enum_payload_ty { + if payload_ty != *expected_ty { + self.errors.push( + Diagnostic::new( + self.file, + "UnsupportedFormatterForm", + "formatter requires all payload variants in one enum to share the same payload type", + ) + .with_span(variant_items[1].span) + .related("first payload type in this enum", *expected_span) + .expected(expected_ty) + .found(&payload_ty) + .hint("split mixed payload kinds into separate enums"), + ); + continue; + } + } else { + enum_payload_ty = Some((payload_ty.clone(), variant_items[1].span)); + } + has_payload_variant = true; + variants.push(format!("({} {})", variant, payload_ty)); + } + + self.reject_comments_before( + form.span.end, + "formatter does not support comments inside enum forms", + ); + + self.output.push_str("(enum "); + self.output.push_str(name); + if has_payload_variant { + for variant in variants { + self.output.push('\n'); + self.output.push_str(" "); + self.output.push_str(&variant); + } + } else { + for variant in variants { + self.output.push(' '); + self.output.push_str(&variant); + } + } + self.output.push(')'); + } + + fn write_function(&mut self, form: &SExpr) { + let Some(items) = expect_list(form) else { + self.errors.push( + Diagnostic::new(self.file, "InvalidFunction", "function form must be a list") + .with_span(form.span), + ); + return; + }; + + if items.len() < 6 { + self.errors.push( + Diagnostic::new(self.file, "InvalidFunction", "function form is incomplete") + .with_span(form.span) + .hint("expected `(fn name ((arg i32) ...) -> i32 body...)`"), + ); + return; + } + + let Some(name) = expect_ident(&items[1]) else { + self.errors.push( + Diagnostic::new( + self.file, + "InvalidFunctionName", + "function name must be an identifier", + ) + .with_span(items[1].span), + ); + return; + }; + + if !matches!(items[3].kind, SExprKind::Atom(Atom::Arrow)) { + self.errors.push( + Diagnostic::new( + self.file, + "ExpectedArrow", + "expected `->` in function signature", + ) + .with_span(items[3].span), + ); + return; + } + + let Some(return_type) = self.render_return_type(&items[4]) else { + return; + }; + + let Some(params) = self.params(&items[2]) else { + return; + }; + + self.reject_comments_before( + items[4].span.end, + "formatter does not support comments inside function signatures", + ); + + let mut env = HashSet::new(); + for (param, _) in ¶ms { + env.insert((*param).to_string()); + } + + self.output.push_str("(fn "); + self.output.push_str(name); + self.output.push(' '); + self.write_params(¶ms); + self.output.push_str(" -> "); + self.output.push_str(&return_type); + + for expr in &items[5..] { + let before = self.take_comments_before(expr.span.start); + for comment in &before { + self.output.push('\n'); + self.output.push_str(" "); + self.output.push_str(&comment.text); + } + + let rendered = self.render_body_expr(expr, &mut env); + self.reject_comments_before( + expr.span.end, + "formatter does not support comments inside expression forms", + ); + + let Some(rendered) = rendered else { + continue; + }; + self.output.push('\n'); + self.write_indented_rendered(" ", &rendered); + } + + let trailing = self.take_comments_before(form.span.end); + if trailing.is_empty() { + self.output.push(')'); + } else { + for comment in &trailing { + self.output.push('\n'); + self.output.push_str(" "); + self.output.push_str(&comment.text); + } + self.output.push('\n'); + self.output.push(')'); + } + } + + fn write_test(&mut self, form: &SExpr) { + let Some(items) = expect_list(form) else { + self.errors.push( + Diagnostic::new(self.file, "MalformedTestForm", "test form must be a list") + .with_span(form.span) + .expected(r#"(test "name" body... final-expression)"#), + ); + return; + }; + + if items.len() < 3 { + self.errors.push( + Diagnostic::new( + self.file, + "MalformedTestForm", + r#"test form must be `(test "name" body...)`"#, + ) + .with_span(form.span) + .expected(r#"(test "name" body... final-expression)"#), + ); + return; + } + + let Some(name) = expect_string(&items[1]) else { + self.errors.push( + Diagnostic::new( + self.file, + "MalformedTestForm", + "test name must be a string literal", + ) + .with_span(items[1].span) + .expected("string literal"), + ); + return; + }; + + if !is_valid_test_name(name) { + self.errors.push( + Diagnostic::new( + self.file, + "InvalidTestName", + "test name must be non-empty printable ASCII without quotes, backslashes, or newlines", + ) + .with_span(items[1].span), + ); + return; + } + + self.reject_comments_before( + items[1].span.end, + "formatter does not support comments inside test headers", + ); + + self.output.push_str("(test "); + self.output.push_str(&render_string_literal(name)); + + let mut env = HashSet::new(); + for (index, expr) in items[2..].iter().enumerate() { + let before = self.take_comments_before(expr.span.start); + for comment in &before { + self.output.push('\n'); + self.output.push_str(" "); + self.output.push_str(&comment.text); + } + + if index + 1 < items[2..].len() && !is_sequential_test_body_form(expr) { + self.errors.push( + Diagnostic::new( + self.file, + "MalformedTestForm", + "test body forms before the final expression must be local declarations, assignments, or while loops", + ) + .with_span(expr.span) + .hint("use `(let name i32 expr)`, `(var name i32 expr)`, `(set name expr)`, or `(while condition body...)` before the final bool expression"), + ); + continue; + } + + let rendered = self.render_body_expr(expr, &mut env); + self.reject_comments_before( + expr.span.end, + "formatter does not support comments inside expression forms", + ); + + let Some(rendered) = rendered else { + continue; + }; + self.output.push('\n'); + self.write_indented_rendered(" ", &rendered); + } + + let trailing = self.take_comments_before(form.span.end); + if trailing.is_empty() { + self.output.push(')'); + } else { + for comment in &trailing { + self.output.push('\n'); + self.output.push_str(" "); + self.output.push_str(&comment.text); + } + self.output.push('\n'); + self.output.push(')'); + } + } + + fn params<'a>(&mut self, form: &'a SExpr) -> Option> { + let Some(items) = expect_list(form) else { + self.errors.push( + Diagnostic::new(self.file, "InvalidParams", "parameters must be a list") + .with_span(form.span), + ); + return None; + }; + + let mut params = Vec::new(); + + let starting_error_count = self.errors.len(); + + for item in items { + let Some(pair) = expect_list(item) else { + self.errors.push( + Diagnostic::new(self.file, "InvalidParam", "parameter must be `(name i32)`") + .with_span(item.span), + ); + continue; + }; + + if pair.len() != 2 { + self.errors.push( + Diagnostic::new(self.file, "InvalidParam", "parameter must be `(name i32)`") + .with_span(item.span), + ); + continue; + } + + let Some(name) = expect_ident(&pair[0]) else { + self.errors.push( + Diagnostic::new( + self.file, + "InvalidParamName", + "parameter name must be an identifier", + ) + .with_span(pair[0].span), + ); + continue; + }; + + let ty = if is_ident(&pair[1], "i32") { + "i32".to_string() + } else if is_ident(&pair[1], "i64") { + "i64".to_string() + } else if is_ident(&pair[1], "u32") { + "u32".to_string() + } else if is_ident(&pair[1], "u64") { + "u64".to_string() + } else if is_ident(&pair[1], "f64") { + "f64".to_string() + } else if is_ident(&pair[1], "bool") { + "bool".to_string() + } else if is_ident(&pair[1], "string") { + "string".to_string() + } else if let Some(name) = expect_ident(&pair[1]) { + if self.struct_names.contains(name) || self.enum_names.contains(name) { + name.to_string() + } else { + self.errors.push( + Diagnostic::new( + self.file, + "UnsupportedFormatterForm", + "formatter supports only `i32`, `i64`, `u32`, `u64`, `f64`, `bool`, `string`, known struct types, `(option i32)`, `(option i64)`, `(option u32)`, `(option u64)`, `(option f64)`, `(option bool)`, `(option string)`, `(result i32 i32)`, `(result i64 i32)`, `(result u32 i32)`, `(result u64 i32)`, `(result f64 i32)`, `(result bool i32)`, `(result string i32)`, `(array i32 N)`, `(array i64 N)`, `(array u32 N)`, `(array u64 N)`, `(array f64 N)`, `(array bool N)`, `(array string N)`, `(vec i32)`, `(vec i64)`, `(vec f64)`, `(vec bool)`, and `(vec string)` parameters", + ) + .with_span(pair[1].span), + ); + continue; + } + } else if let Some(items) = expect_list(&pair[1]) { + if let Some(text) = render_option_result_type(items) { + text + } else if let Some(text) = + render_supported_array_type(items, &self.struct_names, &self.enum_names) + { + text + } else if let Some(text) = render_supported_vec_type(items) { + text + } else { + self.errors.push( + Diagnostic::new( + self.file, + "UnsupportedFormatterForm", + "formatter supports only `i32`, `i64`, `u32`, `u64`, `f64`, `bool`, `string`, known struct types, `(option i32)`, `(option i64)`, `(option u32)`, `(option u64)`, `(option f64)`, `(option bool)`, `(option string)`, `(result i32 i32)`, `(result i64 i32)`, `(result u32 i32)`, `(result u64 i32)`, `(result f64 i32)`, `(result bool i32)`, `(result string i32)`, `(array i32 N)`, `(array i64 N)`, `(array u32 N)`, `(array u64 N)`, `(array f64 N)`, `(array bool N)`, `(array string N)`, `(vec i32)`, `(vec i64)`, `(vec f64)`, `(vec bool)`, and `(vec string)` parameters", + ) + .with_span(pair[1].span), + ); + continue; + } + } else { + self.errors.push( + Diagnostic::new( + self.file, + "UnsupportedFormatterForm", + "formatter supports only `i32`, `i64`, `u32`, `u64`, `f64`, `bool`, `string`, known struct types, `(option i32)`, `(option i64)`, `(option u32)`, `(option u64)`, `(option f64)`, `(option bool)`, `(option string)`, `(result i32 i32)`, `(result i64 i32)`, `(result u32 i32)`, `(result u64 i32)`, `(result f64 i32)`, `(result bool i32)`, `(result string i32)`, `(array i32 N)`, `(array i64 N)`, `(array u32 N)`, `(array u64 N)`, `(array f64 N)`, `(array bool N)`, `(array string N)`, `(vec i32)`, `(vec i64)`, `(vec f64)`, `(vec bool)`, and `(vec string)` parameters", + ) + .with_span(pair[1].span), + ); + continue; + }; + + params.push((name, ty)); + } + + if self.errors.len() == starting_error_count { + Some(params) + } else { + None + } + } + + fn write_params(&mut self, params: &[(&str, String)]) { + if params.is_empty() { + self.output.push_str("()"); + return; + } + + self.output.push('('); + for (index, (name, ty)) in params.iter().enumerate() { + if index > 0 { + self.output.push(' '); + } + self.output.push('('); + self.output.push_str(name); + self.output.push(' '); + self.output.push_str(ty); + self.output.push(')'); + } + self.output.push(')'); + } + + fn render_return_type(&mut self, form: &SExpr) -> Option { + if is_ident(form, "i32") { + return Some("i32".to_string()); + } + if is_ident(form, "i64") { + return Some("i64".to_string()); + } + if is_ident(form, "u32") { + return Some("u32".to_string()); + } + if is_ident(form, "u64") { + return Some("u64".to_string()); + } + if is_ident(form, "f64") { + return Some("f64".to_string()); + } + if is_ident(form, "bool") { + return Some("bool".to_string()); + } + if is_ident(form, "string") { + return Some("string".to_string()); + } + + if let Some(name) = expect_ident(form) { + if self.struct_names.contains(name) || self.enum_names.contains(name) { + return Some(name.to_string()); + } + } + + let Some(items) = expect_list(form) else { + self.errors.push( + Diagnostic::new( + self.file, + "UnsupportedFormatterForm", + "formatter supports only `i32`, `i64`, `u32`, `u64`, `f64`, `bool`, `string`, known struct types, `(option i32)`, `(option i64)`, `(option u32)`, `(option u64)`, `(option f64)`, `(option bool)`, `(option string)`, `(result i32 i32)`, `(result i64 i32)`, `(result u32 i32)`, `(result u64 i32)`, `(result f64 i32)`, `(result bool i32)`, `(result string i32)`, `(array i32 N)`, `(array i64 N)`, `(array u32 N)`, `(array u64 N)`, `(array f64 N)`, `(array bool N)`, `(array string N)`, `(vec i32)`, `(vec i64)`, `(vec f64)`, `(vec bool)`, and `(vec string)` function returns", + ) + .with_span(form.span), + ); + return None; + }; + + if let Some(text) = render_option_result_type(items) { + return Some(text); + } + + if let Some(text) = render_supported_array_type(items, &self.struct_names, &self.enum_names) + { + return Some(text); + } + + if let Some(text) = render_supported_vec_type(items) { + return Some(text); + } + + self.errors.push( + Diagnostic::new( + self.file, + "UnsupportedFormatterForm", + "formatter supports only `i32`, `i64`, `u32`, `u64`, `f64`, `bool`, `string`, known struct types, `(option i32)`, `(option i64)`, `(option u32)`, `(option u64)`, `(option f64)`, `(option bool)`, `(option string)`, `(result i32 i32)`, `(result i64 i32)`, `(result u32 i32)`, `(result u64 i32)`, `(result f64 i32)`, `(result bool i32)`, `(result string i32)`, `(array i32 N)`, `(array i64 N)`, `(array u32 N)`, `(array u64 N)`, `(array f64 N)`, `(array bool N)`, `(array string N)`, `(vec i32)`, `(vec i64)`, `(vec f64)`, `(vec bool)`, and `(vec string)` function returns", + ) + .with_span(form.span), + ); + None + } + + fn render_c_import_return_type(&mut self, form: &SExpr) -> Option { + if is_ident(form, "i32") { + return Some("i32".to_string()); + } + if is_ident(form, "unit") { + return Some("unit".to_string()); + } + self.errors.push( + Diagnostic::new( + self.file, + "UnsupportedCImportType", + "C import return type must be `i32` or `unit` in exp-6", + ) + .with_span(form.span) + .expected("i32 or unit"), + ); + None + } + + fn render_body_expr(&mut self, expr: &SExpr, env: &mut HashSet) -> Option { + if let SExprKind::List(items) = &expr.kind { + if let Some(head) = items.first().and_then(expect_ident) { + match head { + "let" | "var" => return self.render_local(expr.span, head, items, env), + "set" => return self.render_set(expr.span, items, env), + "while" => return self.render_while(expr.span, items, env), + _ => {} + } + } + } + + self.render_expr(expr, env) + } + + fn render_expr(&mut self, expr: &SExpr, env: &mut HashSet) -> Option { + match &expr.kind { + SExprKind::Atom(Atom::Int(value)) => { + if i32::try_from(*value).is_err() { + self.errors.push( + Diagnostic::new( + self.file, + "IntegerOutOfRange", + "integer literal is outside the supported i32 range", + ) + .with_span(expr.span) + .expected("i32") + .found(value.to_string()), + ); + None + } else { + Some(value.to_string()) + } + } + SExprKind::Atom(Atom::I64(value)) => Some(format!("{}i64", value)), + SExprKind::Atom(Atom::U32(value)) => Some(format!("{}u32", value)), + SExprKind::Atom(Atom::U64(value)) => Some(format!("{}u64", value)), + SExprKind::Atom(Atom::Float(value)) => { + if !value.is_finite() { + self.errors.push( + Diagnostic::new( + self.file, + "UnsupportedFloatLiteral", + "f64 literals must be finite in exp-20", + ) + .with_span(expr.span) + .expected("finite f64 literal") + .found(value.to_string()) + .hint("NaN and infinity semantics remain deferred"), + ); + None + } else { + Some(render_float_literal(*value)) + } + } + SExprKind::Atom(Atom::Ident(name)) if name == "true" || name == "false" => { + Some(name.to_string()) + } + SExprKind::Atom(Atom::String(value)) => Some(render_string_literal(value)), + SExprKind::Atom(Atom::Ident(name)) if env.contains(name.as_str()) => { + Some(name.to_string()) + } + SExprKind::Atom(Atom::Ident(name)) => { + self.errors.push( + Diagnostic::new( + self.file, + "UnsupportedFormatterForm", + format!( + "formatter does not support expression identifier `{}`", + name + ), + ) + .with_span(expr.span) + .hint("current formatter identifiers must be function parameters or locals"), + ); + None + } + SExprKind::List(items) if items.is_empty() => { + self.errors.push( + Diagnostic::new( + self.file, + "UnsupportedFormatterForm", + "empty forms are unsupported", + ) + .with_span(expr.span), + ); + None + } + SExprKind::List(items) => { + let Some(head) = expect_ident(&items[0]) else { + self.errors.push( + Diagnostic::new( + self.file, + "UnsupportedFormatterForm", + "expression form head must be an identifier", + ) + .with_span(items[0].span), + ); + return None; + }; + + match head { + "+" | "-" | "*" | "/" | "%" | "bit_and" | "bit_or" | "bit_xor" | "=" | "<" + | ">" | "<=" | ">=" => self.render_call(expr.span, head, &items[1..], env, 2), + "and" | "or" => self.render_call(expr.span, head, &items[1..], env, 2), + "not" => self.render_call(expr.span, head, &items[1..], env, 1), + "." => self.render_field_access(expr.span, items, env), + "array" => self.render_array_constructor(expr.span, items, env), + "some" | "none" => self.render_option_constructor(expr.span, head, items, env), + "ok" | "err" => self.render_result_constructor(expr.span, head, items, env), + "is_some" | "is_none" | "is_ok" | "is_err" | "std.result.is_ok" + | "std.result.is_err" => self.render_call(expr.span, head, &items[1..], env, 1), + "unwrap_some" + | "unwrap_ok" + | "unwrap_err" + | "std.result.unwrap_ok" + | "std.result.unwrap_err" => { + self.render_call(expr.span, head, &items[1..], env, 1) + } + "index" => self.render_call(expr.span, head, &items[1..], env, 2), + "if" => self.render_if(expr.span, items, env), + "match" => self.render_match(expr.span, items, env), + "unsafe" => self.render_unsafe(expr.span, items, env), + name if std_runtime::function(name).is_some() => { + let function = std_runtime::function(name).expect("runtime function"); + self.render_call(expr.span, head, &items[1..], env, function.params.len()) + } + name if self.struct_names.contains(name) => { + self.render_struct_constructor(expr.span, name, &items[1..], env) + } + name if qualified_enum_name(name) + .map(|(enum_name, _)| self.enum_names.contains(enum_name)) + .unwrap_or(false) => + { + if items.len() > 2 { + self.errors.push( + Diagnostic::new( + self.file, + "VariantConstructorArity", + "enum constructors support zero or one argument", + ) + .with_span(expr.span) + .expected("0 or 1") + .found((items.len() - 1).to_string()), + ); + None + } else if items.len() == 2 { + let rendered = self.render_expr(&items[1], env)?; + Some(format!("({} {})", name, rendered)) + } else { + Some(format!("({})", name)) + } + } + other if std_runtime::is_standard_path(other) => { + self.errors + .push(std_runtime::unsupported_standard_library_call( + self.file, expr.span, other, + )); + None + } + name if self.function_names.contains(name) => { + self.render_call(expr.span, name, &items[1..], env, usize::MAX) + } + other => { + self.errors.push( + Diagnostic::new( + self.file, + "UnsupportedFormatterForm", + format!("formatter does not support expression form `{}`", other), + ) + .with_span(expr.span) + .hint("current formatter syntax supports integer, bool, and string literals, numeric and bitwise binary heads, boolean logic heads, comparisons, `if`, `match`, `while`, `unsafe`, arrays, structs, option/result observers and unwraps, standard-runtime calls, legacy runtime aliases, and user-defined calls"), + ); + None + } + } + } + _ => { + self.errors.push( + Diagnostic::new( + self.file, + "UnsupportedFormatterForm", + "formatter does not support this expression", + ) + .with_span(expr.span), + ); + None + } + } + } + + fn render_local( + &mut self, + span: Span, + keyword: &str, + items: &[SExpr], + env: &mut HashSet, + ) -> Option { + if items.len() != 4 { + self.errors.push( + Diagnostic::new( + self.file, + "UnsupportedFormatterForm", + format!( + "formatter expected `{}` to have name, type, and initializer", + keyword + ), + ) + .with_span(span), + ); + return None; + } + + let Some(name) = expect_ident(&items[1]) else { + self.errors.push( + Diagnostic::new( + self.file, + "InvalidLocalName", + "local name must be an identifier", + ) + .with_span(items[1].span), + ); + return None; + }; + + let Some(ty) = self.render_local_type(&items[2]) else { + return None; + }; + + if keyword == "var" && ty.is_array { + self.errors.push( + Diagnostic::new( + self.file, + "UnsupportedFormatterForm", + "formatter does not support mutable array locals", + ) + .with_span(items[2].span), + ); + return None; + } + + let initializer = self.render_expr(&items[3], env)?; + env.insert(name.to_string()); + Some(format!( + "({} {} {} {})", + keyword, name, ty.text, initializer + )) + } + + fn render_local_type(&mut self, form: &SExpr) -> Option { + if is_ident(form, "i32") { + return Some(RenderedType { + text: "i32".to_string(), + is_array: false, + }); + } + + if is_ident(form, "i64") { + return Some(RenderedType { + text: "i64".to_string(), + is_array: false, + }); + } + + if is_ident(form, "u32") { + return Some(RenderedType { + text: "u32".to_string(), + is_array: false, + }); + } + + if is_ident(form, "u64") { + return Some(RenderedType { + text: "u64".to_string(), + is_array: false, + }); + } + + if is_ident(form, "f64") { + return Some(RenderedType { + text: "f64".to_string(), + is_array: false, + }); + } + + if is_ident(form, "bool") { + return Some(RenderedType { + text: "bool".to_string(), + is_array: false, + }); + } + + if is_ident(form, "string") { + return Some(RenderedType { + text: "string".to_string(), + is_array: false, + }); + } + + if let Some(name) = expect_ident(form) { + if self.struct_names.contains(name) { + return Some(RenderedType { + text: name.to_string(), + is_array: false, + }); + } + if self.enum_names.contains(name) { + return Some(RenderedType { + text: name.to_string(), + is_array: false, + }); + } + } + + let Some(items) = expect_list(form) else { + self.errors.push( + Diagnostic::new( + self.file, + "UnsupportedFormatterForm", + "formatter supports only `i32`, `i64`, `u32`, `u64`, `f64`, `bool`, `string`, known struct and enum types, `(option i32)`, `(option i64)`, `(option u32)`, `(option u64)`, `(option f64)`, `(option bool)`, `(option string)`, `(result i32 i32)`, `(result i64 i32)`, `(result u32 i32)`, `(result u64 i32)`, `(result f64 i32)`, `(result bool i32)`, `(result string i32)`, `(array i32 N)`, `(array i64 N)`, `(array u32 N)`, `(array u64 N)`, `(array f64 N)`, `(array bool N)`, `(array string N)`, `(vec i32)`, `(vec i64)`, `(vec f64)`, `(vec bool)`, and `(vec string)` locals", + ) + .with_span(form.span), + ); + return None; + }; + + if let Some(text) = render_option_result_type(items) { + return Some(RenderedType { + text, + is_array: false, + }); + } + + if let Some(text) = render_supported_vec_type(items) { + return Some(RenderedType { + text, + is_array: false, + }); + } + + if items.len() != 3 || !is_ident(&items[0], "array") { + self.errors.push( + Diagnostic::new( + self.file, + "UnsupportedFormatterForm", + "formatter supports only `i32`, `i64`, `u32`, `u64`, `f64`, `bool`, `string`, known struct and enum types, `(option i32)`, `(option i64)`, `(option u32)`, `(option u64)`, `(option f64)`, `(option bool)`, `(option string)`, `(result i32 i32)`, `(result i64 i32)`, `(result u32 i32)`, `(result u64 i32)`, `(result f64 i32)`, `(result bool i32)`, `(result string i32)`, `(array i32 N)`, `(array i64 N)`, `(array u32 N)`, `(array u64 N)`, `(array f64 N)`, `(array bool N)`, `(array string N)`, `(vec i32)`, `(vec i64)`, `(vec f64)`, `(vec bool)`, and `(vec string)` locals", + ) + .with_span(form.span), + ); + return None; + } + + let Some(elem_ty) = render_supported_array_constructor_type( + &items[1], + &self.struct_names, + &self.enum_names, + ) else { + self.errors.push( + Diagnostic::new( + self.file, + "UnsupportedFormatterForm", + "formatter supports only `i32`, `i64`, `u32`, `u64`, `f64`, `bool`, `string`, known struct and enum types, `(option i32)`, `(option i64)`, `(option u32)`, `(option u64)`, `(option f64)`, `(option bool)`, `(option string)`, `(result i32 i32)`, `(result i64 i32)`, `(result u32 i32)`, `(result u64 i32)`, `(result f64 i32)`, `(result bool i32)`, `(result string i32)`, `(array i32 N)`, `(array i64 N)`, `(array u32 N)`, `(array u64 N)`, `(array f64 N)`, `(array bool N)`, `(array string N)`, `(vec i32)`, `(vec i64)`, `(vec f64)`, `(vec bool)`, and `(vec string)` locals", + ) + .with_span(form.span), + ); + return None; + }; + + let Some(len) = expect_int(&items[2]) else { + self.errors.push( + Diagnostic::new( + self.file, + "UnsupportedFormatterForm", + "formatter expected array local length to be an integer literal", + ) + .with_span(items[2].span), + ); + return None; + }; + + if len <= 0 { + self.errors.push( + Diagnostic::new( + self.file, + "UnsupportedFormatterForm", + "formatter supports only positive-length array locals", + ) + .with_span(items[2].span), + ); + return None; + } + + Some(RenderedType { + text: format!("(array {} {})", elem_ty, len), + is_array: true, + }) + } + + fn render_struct_field_type(&mut self, form: &SExpr) -> Option { + if is_ident(form, "i32") { + return Some("i32".to_string()); + } + if is_ident(form, "i64") { + return Some("i64".to_string()); + } + if is_ident(form, "f64") { + return Some("f64".to_string()); + } + if is_ident(form, "bool") { + return Some("bool".to_string()); + } + if is_ident(form, "string") { + return Some("string".to_string()); + } + + if let Some(name) = expect_ident(form) { + if self.struct_names.contains(name) || self.enum_names.contains(name) { + return Some(name.to_string()); + } + } + + let Some(items) = expect_list(form) else { + self.errors.push( + Diagnostic::new( + self.file, + "UnsupportedFormatterForm", + "formatter supports direct scalar/string/enum fields, direct fixed-array fields over current released element families, current concrete vec/option/result fields, and current non-recursive struct fields", + ) + .with_span(form.span), + ); + return None; + }; + + if let Some(text) = render_option_result_type(items) { + return Some(text); + } + + if let Some(text) = render_supported_array_type(items, &self.struct_names, &self.enum_names) + { + return Some(text); + } + + if let Some(text) = render_supported_vec_type(items) { + return Some(text); + } + + self.errors.push( + Diagnostic::new( + self.file, + "UnsupportedFormatterForm", + "formatter supports direct scalar/string/enum fields, direct fixed-array fields over current released element families, current concrete vec/option/result fields, and current non-recursive struct fields", + ) + .with_span(form.span), + ); + None + } + + fn render_set( + &mut self, + span: Span, + items: &[SExpr], + env: &mut HashSet, + ) -> Option { + if items.len() != 3 { + self.errors.push( + Diagnostic::new( + self.file, + "UnsupportedFormatterForm", + "formatter expected `set` to have a name and value", + ) + .with_span(span), + ); + return None; + } + + let Some(name) = expect_ident(&items[1]) else { + self.errors.push( + Diagnostic::new( + self.file, + "InvalidSetTarget", + "`set` target must be an identifier", + ) + .with_span(items[1].span), + ); + return None; + }; + + if !env.contains(name) { + self.errors.push( + Diagnostic::new( + self.file, + "UnsupportedFormatterForm", + format!("formatter does not know local `{}`", name), + ) + .with_span(items[1].span), + ); + return None; + } + + let value = self.render_expr(&items[2], env)?; + Some(format!("(set {} {})", name, value)) + } + + fn render_while( + &mut self, + span: Span, + items: &[SExpr], + env: &mut HashSet, + ) -> Option { + if items.len() < 3 { + self.errors.push( + Diagnostic::new( + self.file, + "UnsupportedFormatterForm", + "formatter expected `while` to have a condition and body", + ) + .with_span(span), + ); + return None; + } + + let condition = self.render_expr(&items[1], env)?; + let mut output = format!("(while {}", condition); + + for item in &items[2..] { + if is_local_body_form(item) { + self.errors.push( + Diagnostic::new( + self.file, + "UnsupportedFormatterForm", + "formatter does not support local declarations inside `while` bodies", + ) + .with_span(item.span) + .hint("declare loop locals before the `while` form"), + ); + return None; + } + + if matches!(list_head(item), Some("while")) { + self.errors.push( + Diagnostic::new( + self.file, + "UnsupportedFormatterForm", + "formatter does not support nested `while` loops", + ) + .with_span(item.span) + .hint("keep first-pass loop bodies flat"), + ); + return None; + } + + let rendered = self.render_body_expr(item, env)?; + output.push('\n'); + push_indented(&mut output, " ", &rendered); + } + + output.push(')'); + Some(output) + } + + fn render_if( + &mut self, + span: Span, + items: &[SExpr], + env: &mut HashSet, + ) -> Option { + if items.len() != 4 { + self.errors.push( + Diagnostic::new( + self.file, + "UnsupportedFormatterForm", + "formatter expected `if` to have condition, then expression, and else expression", + ) + .with_span(span), + ); + return None; + } + + let condition = self.render_expr(&items[1], env)?; + let then_expr = self.render_expr(&items[2], env)?; + let else_expr = self.render_expr(&items[3], env)?; + let mut output = format!("(if {}", condition); + output.push('\n'); + push_indented(&mut output, " ", &then_expr); + output.push('\n'); + push_indented(&mut output, " ", &else_expr); + output.push(')'); + Some(output) + } + + fn render_match( + &mut self, + span: Span, + items: &[SExpr], + env: &mut HashSet, + ) -> Option { + if items.len() < 2 { + self.errors.push( + Diagnostic::new( + self.file, + "MalformedMatchPattern", + "`match` expects a subject", + ) + .with_span(span) + .expected("(match subject (pattern body...) (pattern body...))"), + ); + return None; + } + + let subject = self.render_expr(&items[1], env)?; + let mut output = format!("(match {}", subject); + let mut seen = HashMap::new(); + + for arm in &items[2..] { + let Some(arm_items) = expect_list(arm) else { + self.errors.push( + Diagnostic::new( + self.file, + "MalformedMatchPattern", + "match arm must be a list containing a pattern and body", + ) + .with_span(arm.span), + ); + return None; + }; + + if arm_items.len() < 2 { + self.errors.push( + Diagnostic::new( + self.file, + "MalformedMatchPattern", + "match arm must contain a pattern and at least one body expression", + ) + .with_span(arm.span), + ); + return None; + } + + let pattern = self.render_match_pattern(&arm_items[0])?; + if let Some(original) = seen.insert(pattern.kind, arm_items[0].span) { + self.errors.push( + Diagnostic::new( + self.file, + "DuplicateMatchArm", + format!("duplicate `{}` match arm", pattern.kind), + ) + .with_span(arm_items[0].span) + .related("original match arm", original), + ); + return None; + } + + let mut arm_env = env.clone(); + if let Some(binding) = pattern.binding { + arm_env.insert(binding.to_string()); + } + + let before_arm = self.take_comments_before(arm.span.start); + for comment in &before_arm { + output.push('\n'); + output.push_str(" "); + output.push_str(&comment.text); + } + + output.push('\n'); + output.push_str(" ("); + output.push_str(&pattern.text); + + for body_expr in &arm_items[1..] { + let before_expr = self.take_comments_before(body_expr.span.start); + for comment in &before_expr { + output.push('\n'); + output.push_str(" "); + output.push_str(&comment.text); + } + + let rendered = self.render_body_expr(body_expr, &mut arm_env)?; + self.reject_comments_before( + body_expr.span.end, + "formatter does not support comments inside expression forms", + ); + + output.push('\n'); + push_indented(&mut output, " ", &rendered); + } + + let trailing = self.take_comments_before(arm.span.end); + for comment in &trailing { + output.push('\n'); + output.push_str(" "); + output.push_str(&comment.text); + } + + output.push(')'); + } + + let has_some = seen.contains_key("some"); + let has_none = seen.contains_key("none"); + let has_ok = seen.contains_key("ok"); + let has_err = seen.contains_key("err"); + let option_family = has_some || has_none; + let result_family = has_ok || has_err; + + if option_family && result_family { + self.errors.push( + Diagnostic::new( + self.file, + "MalformedMatchPattern", + "match arms must belong to one pattern family", + ) + .with_span(span), + ); + return None; + } + + if option_family && (!has_some || !has_none) { + self.errors.push( + Diagnostic::new( + self.file, + "NonExhaustiveMatch", + "option match must contain `some` and `none` arms", + ) + .with_span(span) + .expected("some and none"), + ); + return None; + } + + if result_family && (!has_ok || !has_err) { + self.errors.push( + Diagnostic::new( + self.file, + "NonExhaustiveMatch", + "result match must contain `ok` and `err` arms", + ) + .with_span(span) + .expected("ok and err"), + ); + return None; + } + + let trailing = self.take_comments_before(span.end); + for comment in &trailing { + output.push('\n'); + output.push_str(" "); + output.push_str(&comment.text); + } + + output.push(')'); + Some(output) + } + + fn render_match_pattern<'a>(&mut self, form: &'a SExpr) -> Option> { + let Some(items) = expect_list(form) else { + self.errors.push( + Diagnostic::new( + self.file, + "MalformedMatchPattern", + "match arm pattern must be a list", + ) + .with_span(form.span) + .expected("(some binding), (none), (ok binding), or (err binding)"), + ); + return None; + }; + + let Some(kind) = items.first().and_then(expect_ident) else { + self.errors.push( + Diagnostic::new( + self.file, + "MalformedMatchPattern", + "match arm pattern head must be an identifier", + ) + .with_span(form.span), + ); + return None; + }; + + match kind { + "some" | "ok" | "err" => { + if items.len() != 2 { + self.errors.push( + Diagnostic::new( + self.file, + "MalformedMatchPattern", + format!("`{}` match pattern requires one payload binding", kind), + ) + .with_span(form.span), + ); + return None; + } + + let Some(binding) = expect_ident(&items[1]) else { + self.errors.push( + Diagnostic::new( + self.file, + "MalformedMatchPattern", + "match payload binding must be an identifier", + ) + .with_span(items[1].span), + ); + return None; + }; + + Some(RenderedMatchPattern { + kind, + binding: Some(binding), + text: format!("({} {})", kind, binding), + }) + } + "none" => { + if items.len() != 1 { + self.errors.push( + Diagnostic::new( + self.file, + "MalformedMatchPattern", + "`none` match pattern does not take a payload binding", + ) + .with_span(form.span), + ); + return None; + } + + Some(RenderedMatchPattern { + kind, + binding: None, + text: "(none)".to_string(), + }) + } + name if qualified_enum_name(name) + .map(|(enum_name, _)| self.enum_names.contains(enum_name)) + .unwrap_or(false) => + { + if items.len() > 2 { + self.errors.push( + Diagnostic::new( + self.file, + "InvalidEnumMatchArm", + "enum match patterns support at most one payload binding", + ) + .with_span(form.span) + .expected("(Name.Variant) or (Name.Variant binding)"), + ); + return None; + } + + let binding = if items.len() == 2 { + let Some(binding) = expect_ident(&items[1]) else { + self.errors.push( + Diagnostic::new( + self.file, + "MalformedMatchPattern", + "match payload binding must be an identifier", + ) + .with_span(items[1].span), + ); + return None; + }; + Some(binding) + } else { + None + }; + + Some(RenderedMatchPattern { + kind: name, + binding, + text: binding + .map(|binding| format!("({} {})", name, binding)) + .unwrap_or_else(|| format!("({})", name)), + }) + } + _ => { + self.errors.push( + Diagnostic::new( + self.file, + "MalformedMatchPattern", + format!("unsupported match pattern `{}`", kind), + ) + .with_span(form.span), + ); + None + } + } + } + + fn render_unsafe( + &mut self, + span: Span, + items: &[SExpr], + env: &mut HashSet, + ) -> Option { + if items.len() < 2 { + self.errors.push( + Diagnostic::new( + self.file, + "MalformedUnsafeForm", + "`unsafe` block must contain a final expression", + ) + .with_span(span) + .expected("(unsafe body-form... final-expression)"), + ); + return None; + } + + let mut scoped_env = env.clone(); + let mut output = "(unsafe".to_string(); + + for item in &items[1..] { + let rendered = self.render_body_expr(item, &mut scoped_env)?; + output.push('\n'); + push_indented(&mut output, " ", &rendered); + } + + output.push(')'); + Some(output) + } + + fn render_field_access( + &mut self, + span: Span, + items: &[SExpr], + env: &mut HashSet, + ) -> Option { + if items.len() != 3 { + self.errors.push( + Diagnostic::new( + self.file, + "UnsupportedFormatterForm", + "formatter expected field access to be `(. value field)`", + ) + .with_span(span), + ); + return None; + } + + let value = self.render_expr(&items[1], env)?; + let Some(field) = expect_ident(&items[2]) else { + self.errors.push( + Diagnostic::new( + self.file, + "InvalidFieldName", + "field name must be an identifier", + ) + .with_span(items[2].span), + ); + return None; + }; + + Some(format!("(. {} {})", value, field)) + } + + fn render_struct_constructor( + &mut self, + span: Span, + name: &str, + fields: &[SExpr], + env: &mut HashSet, + ) -> Option { + let mut output = String::new(); + output.push('('); + output.push_str(name); + + for field in fields { + let Some(pair) = expect_list(field) else { + self.errors.push( + Diagnostic::new( + self.file, + "UnsupportedFormatterForm", + "formatter expected struct constructor fields to be `(field value)`", + ) + .with_span(field.span), + ); + return None; + }; + + if pair.len() != 2 { + self.errors.push( + Diagnostic::new( + self.file, + "UnsupportedFormatterForm", + "formatter expected struct constructor fields to be `(field value)`", + ) + .with_span(field.span), + ); + return None; + } + + let Some(field_name) = expect_ident(&pair[0]) else { + self.errors.push( + Diagnostic::new( + self.file, + "InvalidStructConstructorField", + "struct constructor field name must be an identifier", + ) + .with_span(pair[0].span), + ); + return None; + }; + + let value = self.render_expr(&pair[1], env)?; + output.push_str(" ("); + output.push_str(field_name); + output.push(' '); + output.push_str(&value); + output.push(')'); + } + + if fields.is_empty() { + self.errors.push( + Diagnostic::new( + self.file, + "UnsupportedFormatterForm", + "formatter expected at least one struct constructor field", + ) + .with_span(span), + ); + return None; + } + + output.push(')'); + Some(output) + } + + fn render_array_constructor( + &mut self, + span: Span, + items: &[SExpr], + env: &mut HashSet, + ) -> Option { + if items.len() < 2 { + self.errors.push( + Diagnostic::new( + self.file, + "UnsupportedFormatterForm", + "formatter expected array constructor to be `(array TYPE value...)`", + ) + .with_span(span), + ); + return None; + } + + let Some(elem_ty) = render_supported_array_constructor_type( + &items[1], + &self.struct_names, + &self.enum_names, + ) else { + self.errors.push( + Diagnostic::new( + self.file, + "UnsupportedFormatterForm", + "formatter supports only direct scalar `i32`, `i64`, `f64`, `bool`, or `string` array constructors", + ) + .with_span(items[1].span), + ); + return None; + }; + + if items.len() == 2 { + self.errors.push( + Diagnostic::new( + self.file, + "UnsupportedFormatterForm", + "formatter expected at least one array element", + ) + .with_span(span), + ); + return None; + } + + let mut output = format!("(array {}", elem_ty); + for item in &items[2..] { + let rendered = self.render_expr(item, env)?; + output.push(' '); + output.push_str(&rendered); + } + output.push(')'); + Some(output) + } + + fn render_option_constructor( + &mut self, + span: Span, + name: &str, + items: &[SExpr], + env: &mut HashSet, + ) -> Option { + let expected_len = if name == "some" { 3 } else { 2 }; + if items.len() != expected_len { + self.errors.push( + Diagnostic::new( + self.file, + "UnsupportedFormatterForm", + format!( + "formatter expected `{}` to be `{}`", + name, + if name == "some" { + "(some i32 value), (some i64 value), (some u32 value), (some u64 value), (some f64 value), (some bool value), or (some string value)" + } else { + "(none i32), (none i64), (none u32), (none u64), (none f64), (none bool), or (none string)" + } + ), + ) + .with_span(span), + ); + return None; + } + + let payload_type = if is_ident(&items[1], "i32") { + "i32" + } else if is_ident(&items[1], "i64") { + "i64" + } else if is_ident(&items[1], "u32") { + "u32" + } else if is_ident(&items[1], "u64") { + "u64" + } else if is_ident(&items[1], "f64") { + "f64" + } else if is_ident(&items[1], "bool") { + "bool" + } else if is_ident(&items[1], "string") { + "string" + } else { + self.errors.push( + Diagnostic::new( + self.file, + "UnsupportedFormatterForm", + "formatter supports only `i32`, `i64`, `u32`, `u64`, `f64`, `bool`, and `string` option payloads", + ) + .with_span(items[1].span), + ); + return None; + }; + + if name == "none" { + return Some(format!("(none {})", payload_type)); + } + + let value = self.render_expr(&items[2], env)?; + Some(format!("(some {} {})", payload_type, value)) + } + + fn render_result_constructor( + &mut self, + span: Span, + name: &str, + items: &[SExpr], + env: &mut HashSet, + ) -> Option { + if items.len() != 4 { + self.errors.push( + Diagnostic::new( + self.file, + "UnsupportedFormatterForm", + format!( + "formatter expected `{}` to be `({} i32 i32 value)`, `({} i64 i32 value)`, `({} u32 i32 value)`, `({} u64 i32 value)`, `({} f64 i32 value)`, `({} bool i32 value)`, or `({} string i32 value)`", + name, name, name, name, name, name, name, name + ), + ) + .with_span(span), + ); + return None; + } + + let result_type = if is_ident(&items[1], "i32") && is_ident(&items[2], "i32") { + "i32 i32" + } else if is_ident(&items[1], "i64") && is_ident(&items[2], "i32") { + "i64 i32" + } else if is_ident(&items[1], "u32") && is_ident(&items[2], "i32") { + "u32 i32" + } else if is_ident(&items[1], "u64") && is_ident(&items[2], "i32") { + "u64 i32" + } else if is_ident(&items[1], "f64") && is_ident(&items[2], "i32") { + "f64 i32" + } else if is_ident(&items[1], "bool") && is_ident(&items[2], "i32") { + "bool i32" + } else if is_ident(&items[1], "string") && is_ident(&items[2], "i32") { + "string i32" + } else { + self.errors.push( + Diagnostic::new( + self.file, + "UnsupportedFormatterForm", + "formatter supports only `(result i32 i32)`, `(result i64 i32)`, `(result u32 i32)`, `(result u64 i32)`, `(result f64 i32)`, `(result bool i32)`, and `(result string i32)` constructors", + ) + .with_span(span), + ); + return None; + }; + + let value = self.render_expr(&items[3], env)?; + Some(format!("({} {} {})", name, result_type, value)) + } + + fn render_call( + &mut self, + span: Span, + name: &str, + args: &[SExpr], + env: &mut HashSet, + exact_arity: usize, + ) -> Option { + if exact_arity != usize::MAX && args.len() != exact_arity { + self.errors.push( + Diagnostic::new( + self.file, + "UnsupportedFormatterForm", + format!( + "formatter expected `{}` to have {} argument(s)", + name, exact_arity + ), + ) + .with_span(span), + ); + return None; + } + + let mut output = String::new(); + output.push('('); + output.push_str(name); + for arg in args { + let rendered = self.render_expr(arg, env)?; + output.push(' '); + output.push_str(&rendered); + } + output.push(')'); + Some(output) + } + + fn write_indented_rendered(&mut self, indent: &str, rendered: &str) { + push_indented(&mut self.output, indent, rendered); + } + + fn take_comments_before(&mut self, offset: usize) -> Vec { + let start = self.next_comment; + while self + .comments + .get(self.next_comment) + .is_some_and(|comment| comment.start < offset) + { + self.next_comment += 1; + } + self.comments[start..self.next_comment].to_vec() + } + + fn take_remaining_comments(&mut self) -> Vec { + let comments = self.comments[self.next_comment..].to_vec(); + self.next_comment = self.comments.len(); + comments + } + + fn write_comments(&mut self, comments: &[LineComment], indent: &str) { + for comment in comments { + self.output.push_str(indent); + self.output.push_str(&comment.text); + self.output.push('\n'); + } + } + + fn reject_comments_before(&mut self, offset: usize, message: &'static str) { + let comments = self.take_comments_before(offset); + for comment in comments { + self.errors.push( + Diagnostic::new(self.file, "UnsupportedFormatterComment", message) + .with_span(comment.span) + .hint( + "move the full-line comment between body expressions or outside the form", + ), + ); + } + } +} + +#[derive(Clone, Debug)] +struct LineComment { + start: usize, + span: Span, + text: String, + full_line: bool, +} + +struct RenderedType { + text: String, + is_array: bool, +} + +struct RenderedMatchPattern<'a> { + kind: &'a str, + binding: Option<&'a str>, + text: String, +} + +fn collect_function_names(forms: &[SExpr]) -> HashSet { + let mut names = HashSet::new(); + for form in forms { + let Some(items) = expect_list(form) else { + continue; + }; + match items.first() { + Some(head) if is_ident(head, "fn") => { + if let Some(name) = items.get(1).and_then(expect_ident) { + names.insert(name.to_string()); + } + } + Some(head) if is_ident(head, "import_c") => { + if let Some(name) = items.get(1).and_then(expect_ident) { + names.insert(name.to_string()); + } + } + Some(head) if is_ident(head, "import") => { + if let Some(imports) = items.get(2).and_then(expect_list) { + for import in imports { + if let Some(name) = expect_ident(import) { + names.insert(name.to_string()); + } + } + } + } + _ => {} + } + } + names +} + +fn collect_struct_names(forms: &[SExpr]) -> HashSet { + forms + .iter() + .filter_map(|form| { + let items = expect_list(form)?; + if !is_ident(items.first()?, "struct") { + return None; + } + expect_ident(items.get(1)?).map(str::to_string) + }) + .collect() +} + +fn collect_enum_names(forms: &[SExpr]) -> HashSet { + forms + .iter() + .filter_map(|form| { + let items = expect_list(form)?; + if !is_ident(items.first()?, "enum") { + return None; + } + expect_ident(items.get(1)?).map(str::to_string) + }) + .collect() +} + +fn qualified_enum_name(name: &str) -> Option<(&str, &str)> { + let (enum_name, variant) = name.split_once('.')?; + if enum_name.is_empty() || variant.is_empty() || variant.contains('.') { + return None; + } + Some((enum_name, variant)) +} + +fn collect_comments(source: &str) -> Vec { + let mut comments = Vec::new(); + let bytes = source.as_bytes(); + let mut line_start = 0; + let mut i = 0; + let mut in_string = false; + let mut escaped = false; + + while i < bytes.len() { + let byte = bytes[i]; + + if in_string { + if escaped { + escaped = false; + i += 1; + continue; + } + + match byte { + b'\\' => { + escaped = true; + i += 1; + } + b'"' => { + in_string = false; + i += 1; + } + b'\n' => { + line_start = i + 1; + i += 1; + } + _ => i += 1, + } + continue; + } + + match byte { + b'"' => { + in_string = true; + i += 1; + } + b'\n' => { + line_start = i + 1; + i += 1; + } + b';' => { + let comment_start = i; + let mut comment_end = i; + while comment_end < bytes.len() && bytes[comment_end] != b'\n' { + comment_end += 1; + } + + let trimmed_end = trim_ascii_horizontal_end(source, comment_start, comment_end); + let full_line = source[line_start..comment_start] + .bytes() + .all(|prefix| matches!(prefix, b' ' | b'\t')); + let text = if full_line { + source[comment_start..trimmed_end].to_string() + } else { + String::new() + }; + + comments.push(LineComment { + start: comment_start, + span: Span::new(comment_start, trimmed_end), + text, + full_line, + }); + + i = comment_end; + } + _ => i += 1, + } + } + + comments +} + +fn trim_ascii_horizontal_end(source: &str, start: usize, mut end: usize) -> usize { + let bytes = source.as_bytes(); + while end > start && matches!(bytes[end - 1], b' ' | b'\t') { + end -= 1; + } + end +} + +fn unsupported_non_full_line_comment(file: &str, span: Span) -> Diagnostic { + Diagnostic::new( + file, + "UnsupportedFormatterComment", + "formatter supports comments only as full-line comments in stable positions", + ) + .with_span(span) + .hint( + "move the comment to its own line before a top-level form, between body expressions, or after the final top-level form", + ) +} + +fn list_head(form: &SExpr) -> Option<&str> { + let items = expect_list(form)?; + let first = items.first()?; + expect_ident(first) +} + +fn render_option_result_type(items: &[SExpr]) -> Option { + if items.len() == 2 && is_ident(&items[0], "option") && is_ident(&items[1], "i32") { + return Some("(option i32)".to_string()); + } + + if items.len() == 2 && is_ident(&items[0], "option") && is_ident(&items[1], "i64") { + return Some("(option i64)".to_string()); + } + + if items.len() == 2 && is_ident(&items[0], "option") && is_ident(&items[1], "u32") { + return Some("(option u32)".to_string()); + } + + if items.len() == 2 && is_ident(&items[0], "option") && is_ident(&items[1], "u64") { + return Some("(option u64)".to_string()); + } + + if items.len() == 2 && is_ident(&items[0], "option") && is_ident(&items[1], "f64") { + return Some("(option f64)".to_string()); + } + + if items.len() == 2 && is_ident(&items[0], "option") && is_ident(&items[1], "bool") { + return Some("(option bool)".to_string()); + } + + if items.len() == 2 && is_ident(&items[0], "option") && is_ident(&items[1], "string") { + return Some("(option string)".to_string()); + } + + if items.len() == 3 + && is_ident(&items[0], "result") + && is_ident(&items[1], "i32") + && is_ident(&items[2], "i32") + { + return Some("(result i32 i32)".to_string()); + } + + if items.len() == 3 + && is_ident(&items[0], "result") + && is_ident(&items[1], "i64") + && is_ident(&items[2], "i32") + { + return Some("(result i64 i32)".to_string()); + } + + if items.len() == 3 + && is_ident(&items[0], "result") + && is_ident(&items[1], "u32") + && is_ident(&items[2], "i32") + { + return Some("(result u32 i32)".to_string()); + } + + if items.len() == 3 + && is_ident(&items[0], "result") + && is_ident(&items[1], "u64") + && is_ident(&items[2], "i32") + { + return Some("(result u64 i32)".to_string()); + } + + if items.len() == 3 + && is_ident(&items[0], "result") + && is_ident(&items[1], "f64") + && is_ident(&items[2], "i32") + { + return Some("(result f64 i32)".to_string()); + } + + if items.len() == 3 + && is_ident(&items[0], "result") + && is_ident(&items[1], "bool") + && is_ident(&items[2], "i32") + { + return Some("(result bool i32)".to_string()); + } + + if items.len() == 3 + && is_ident(&items[0], "result") + && is_ident(&items[1], "string") + && is_ident(&items[2], "i32") + { + return Some("(result string i32)".to_string()); + } + + None +} + +fn render_supported_array_constructor_type( + form: &SExpr, + struct_names: &HashSet, + enum_names: &HashSet, +) -> Option { + if is_ident(form, "i32") { + Some("i32".to_string()) + } else if is_ident(form, "i64") { + Some("i64".to_string()) + } else if is_ident(form, "u32") { + Some("u32".to_string()) + } else if is_ident(form, "u64") { + Some("u64".to_string()) + } else if is_ident(form, "f64") { + Some("f64".to_string()) + } else if is_ident(form, "bool") { + Some("bool".to_string()) + } else if is_ident(form, "string") { + Some("string".to_string()) + } else if let Some(name) = expect_ident(form) { + if struct_names.contains(name) || enum_names.contains(name) { + Some(name.to_string()) + } else { + None + } + } else { + None + } +} + +fn render_supported_array_type( + items: &[SExpr], + struct_names: &HashSet, + enum_names: &HashSet, +) -> Option { + if items.len() != 3 || !is_ident(&items[0], "array") { + return None; + } + + let elem_ty = render_supported_array_constructor_type(&items[1], struct_names, enum_names)?; + let len = expect_int(&items[2])?; + if len <= 0 { + return None; + } + + Some(format!("(array {} {})", elem_ty, len)) +} + +fn render_supported_vec_type(items: &[SExpr]) -> Option { + if items.len() == 2 && is_ident(&items[0], "vec") { + if is_ident(&items[1], "i32") { + return Some("(vec i32)".to_string()); + } + if is_ident(&items[1], "i64") { + return Some("(vec i64)".to_string()); + } + if is_ident(&items[1], "f64") { + return Some("(vec f64)".to_string()); + } + if is_ident(&items[1], "bool") { + return Some("(vec bool)".to_string()); + } + if is_ident(&items[1], "string") { + return Some("(vec string)".to_string()); + } + } + + None +} + +fn expect_list(form: &SExpr) -> Option<&[SExpr]> { + match &form.kind { + SExprKind::List(items) => Some(items), + _ => None, + } +} + +fn expect_ident(form: &SExpr) -> Option<&str> { + match &form.kind { + SExprKind::Atom(Atom::Ident(name)) => Some(name.as_str()), + _ => None, + } +} + +fn expect_int(form: &SExpr) -> Option { + match &form.kind { + SExprKind::Atom(Atom::Int(value)) => Some(*value), + _ => None, + } +} + +fn expect_string(form: &SExpr) -> Option<&str> { + match &form.kind { + SExprKind::Atom(Atom::String(value)) => Some(value.as_str()), + _ => None, + } +} + +fn is_ident(form: &SExpr, expected: &str) -> bool { + expect_ident(form).is_some_and(|actual| actual == expected) +} + +fn is_c_symbol_name(value: &str) -> bool { + let mut chars = value.chars(); + let Some(first) = chars.next() else { + return false; + }; + (first == '_' || first.is_ascii_alphabetic()) + && chars.all(|ch| ch == '_' || ch.is_ascii_alphanumeric()) +} + +fn is_local_body_form(form: &SExpr) -> bool { + matches!(list_head(form), Some("let" | "var")) +} + +fn is_sequential_test_body_form(form: &SExpr) -> bool { + matches!(list_head(form), Some("let" | "var" | "set" | "while")) +} + +fn push_indented(output: &mut String, indent: &str, rendered: &str) { + for (index, line) in rendered.split('\n').enumerate() { + if index > 0 { + output.push('\n'); + } + output.push_str(indent); + output.push_str(line); + } +} + +fn is_valid_test_name(name: &str) -> bool { + !name.is_empty() + && name + .bytes() + .all(|byte| matches!(byte, 0x20..=0x7e) && byte != b'"' && byte != b'\\') +} + +fn render_string_literal(value: &str) -> String { + let mut output = String::from("\""); + for ch in value.chars() { + match ch { + '"' => output.push_str("\\\""), + '\\' => output.push_str("\\\\"), + '\n' => output.push_str("\\n"), + '\t' => output.push_str("\\t"), + ch if ch.is_control() => output.extend(ch.escape_default()), + ch => output.push(ch), + } + } + output.push('"'); + output +} + +fn render_float_literal(value: f64) -> String { + let rendered = value.to_string(); + if rendered.contains('.') || rendered.contains('e') || rendered.contains('E') { + rendered + } else { + format!("{}.0", rendered) + } +} diff --git a/compiler/src/lexer.rs b/compiler/src/lexer.rs new file mode 100644 index 0000000..3f7de04 --- /dev/null +++ b/compiler/src/lexer.rs @@ -0,0 +1,349 @@ +use crate::{ + diag::Diagnostic, + token::{Span, Token, TokenKind}, +}; + +pub fn lex(file: &str, source: &str) -> Result, Vec> { + let bytes = source.as_bytes(); + let mut tokens = Vec::new(); + let mut errors = Vec::new(); + let mut i = 0; + + while i < bytes.len() { + let c = bytes[i] as char; + + match c { + '(' => { + tokens.push(Token { + kind: TokenKind::LParen, + span: Span::new(i, i + 1), + }); + i += 1; + } + ')' => { + tokens.push(Token { + kind: TokenKind::RParen, + span: Span::new(i, i + 1), + }); + i += 1; + } + ';' => { + while i < bytes.len() && bytes[i] as char != '\n' { + i += 1; + } + } + '"' => { + let start = i; + i += 1; + let mut value = String::new(); + + while i < bytes.len() { + let ch = bytes[i] as char; + if ch == '"' { + i += 1; + tokens.push(Token { + kind: TokenKind::String(value), + span: Span::new(start, i), + }); + break; + } + + if ch == '\\' { + let escape_start = i; + i += 1; + if i >= bytes.len() { + errors.push( + Diagnostic::new(file, "UnterminatedString", "unterminated string") + .with_span(Span::new(start, bytes.len())), + ); + break; + } + + let escaped = bytes[i] as char; + match escaped { + 'n' => value.push('\n'), + 't' => value.push('\t'), + '"' => value.push('"'), + '\\' => value.push('\\'), + other => errors.push( + Diagnostic::new( + file, + "UnsupportedStringEscape", + "string literal uses an unsupported escape", + ) + .with_span(Span::new(escape_start, i + 1)) + .expected("\\n, \\t, \\\", or \\\\") + .found(format!("\\{}", other)) + .hint( + "the first string runtime slice supports only newline, tab, quote, and backslash escapes", + ), + ), + } + i += 1; + } else { + let literal_ch = source[i..].chars().next().expect("valid source slice"); + let next_i = i + literal_ch.len_utf8(); + + if !literal_ch.is_ascii() || literal_ch.is_control() { + errors.push( + Diagnostic::new( + file, + "UnsupportedStringLiteral", + "string literal contains bytes outside the first runtime string slice", + ) + .with_span(Span::new(i, i + 1)) + .expected("printable ASCII string literal") + .found("unsupported string literal") + .hint( + "use printable ASCII text plus the current \\n, \\t, \\\", and \\\\ escapes", + ), + ); + i = next_i; + continue; + } + value.push(literal_ch); + i = next_i; + } + } + + if i >= bytes.len() + && !matches!(tokens.last().map(|t| &t.kind), Some(TokenKind::String(_))) + { + errors.push( + Diagnostic::new(file, "UnterminatedString", "unterminated string") + .with_span(Span::new(start, bytes.len())), + ); + } + } + '-' if i + 1 < bytes.len() && bytes[i + 1] as char == '>' => { + tokens.push(Token { + kind: TokenKind::Arrow, + span: Span::new(i, i + 2), + }); + i += 2; + } + c if c.is_whitespace() => { + i += 1; + } + _ => { + let start = i; + while i < bytes.len() { + let ch = bytes[i] as char; + if ch.is_whitespace() || ch == '(' || ch == ')' || ch == ';' { + break; + } + i += 1; + } + + let text = &source[start..i]; + let span = Span::new(start, i); + let kind = if let Some(result) = parse_i64_suffix_atom(text) { + match result { + Ok(value) => TokenKind::I64(value), + Err(error) => { + errors.push(error.to_diagnostic(file, text, span)); + continue; + } + } + } else if let Some(result) = parse_u32_suffix_atom(text) { + match result { + Ok(value) => TokenKind::U32(value), + Err(error) => { + errors.push(error.to_diagnostic(file, text, span)); + continue; + } + } + } else if let Some(result) = parse_u64_suffix_atom(text) { + match result { + Ok(value) => TokenKind::U64(value), + Err(error) => { + errors.push(error.to_diagnostic(file, text, span)); + continue; + } + } + } else if let Ok(value) = text.parse::() { + TokenKind::Int(value) + } else if let Ok(value) = text.parse::() { + TokenKind::Float(value) + } else { + TokenKind::Ident(text.to_string()) + }; + + tokens.push(Token { kind, span }); + } + } + } + + if errors.is_empty() { + Ok(tokens) + } else { + Err(errors) + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +enum I64SuffixError { + InvalidDigits, + OutOfRange, +} + +impl I64SuffixError { + fn to_diagnostic(self, file: &str, text: &str, span: Span) -> Diagnostic { + match self { + Self::InvalidDigits => Diagnostic::new( + file, + "InvalidI64Literal", + "i64 literal must use signed decimal digits before the `i64` suffix", + ) + .with_span(span) + .expected("signed decimal digits followed by `i64`") + .found(text) + .hint("use forms like `42i64` or `-7i64`; other i64 literal forms remain deferred"), + Self::OutOfRange => Diagnostic::new( + file, + "I64LiteralOutOfRange", + "i64 literal is outside the supported signed 64-bit range", + ) + .with_span(span) + .expected("signed 64-bit integer literal with `i64` suffix") + .found(text) + .hint("use a value between -9223372036854775808i64 and 9223372036854775807i64"), + } + } +} + +fn parse_i64_suffix_atom(text: &str) -> Option> { + let digits = text.strip_suffix("i64")?; + if digits.is_empty() { + return None; + } + if digits == "-" || digits == "+" || digits.starts_with('+') { + return Some(Err(I64SuffixError::InvalidDigits)); + } + + let rest = digits.strip_prefix('-').unwrap_or(digits); + if rest.is_empty() { + return Some(Err(I64SuffixError::InvalidDigits)); + } + + if !rest.as_bytes()[0].is_ascii_digit() { + if digits.starts_with('-') { + return Some(Err(I64SuffixError::InvalidDigits)); + } + return None; + } + + if !rest.bytes().all(|byte| byte.is_ascii_digit()) { + return Some(Err(I64SuffixError::InvalidDigits)); + } + + Some( + digits + .parse::() + .map_err(|_| I64SuffixError::OutOfRange), + ) +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +enum U32SuffixError { + InvalidDigits, + OutOfRange, +} + +impl U32SuffixError { + fn to_diagnostic(self, file: &str, text: &str, span: Span) -> Diagnostic { + match self { + Self::InvalidDigits => Diagnostic::new( + file, + "InvalidU32Literal", + "u32 literal must use unsigned decimal digits before the `u32` suffix", + ) + .with_span(span) + .expected("unsigned decimal digits followed by `u32`") + .found(text) + .hint("use forms like `42u32`; signed or non-decimal u32 literals remain deferred"), + Self::OutOfRange => Diagnostic::new( + file, + "U32LiteralOutOfRange", + "u32 literal is outside the supported unsigned 32-bit range", + ) + .with_span(span) + .expected("unsigned 32-bit integer literal with `u32` suffix") + .found(text) + .hint("use a value between 0u32 and 4294967295u32"), + } + } +} + +fn parse_u32_suffix_atom(text: &str) -> Option> { + let digits = text.strip_suffix("u32")?; + if digits.is_empty() { + return None; + } + if digits.starts_with('-') || digits.starts_with('+') { + return Some(Err(U32SuffixError::InvalidDigits)); + } + if !digits.as_bytes()[0].is_ascii_digit() { + return None; + } + if !digits.bytes().all(|byte| byte.is_ascii_digit()) { + return Some(Err(U32SuffixError::InvalidDigits)); + } + Some( + digits + .parse::() + .map_err(|_| U32SuffixError::OutOfRange), + ) +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +enum U64SuffixError { + InvalidDigits, + OutOfRange, +} + +impl U64SuffixError { + fn to_diagnostic(self, file: &str, text: &str, span: Span) -> Diagnostic { + match self { + Self::InvalidDigits => Diagnostic::new( + file, + "InvalidU64Literal", + "u64 literal must use unsigned decimal digits before the `u64` suffix", + ) + .with_span(span) + .expected("unsigned decimal digits followed by `u64`") + .found(text) + .hint("use forms like `42u64`; signed or non-decimal u64 literals remain deferred"), + Self::OutOfRange => Diagnostic::new( + file, + "U64LiteralOutOfRange", + "u64 literal is outside the supported unsigned 64-bit range", + ) + .with_span(span) + .expected("unsigned 64-bit integer literal with `u64` suffix") + .found(text) + .hint("use a value between 0u64 and 18446744073709551615u64"), + } + } +} + +fn parse_u64_suffix_atom(text: &str) -> Option> { + let digits = text.strip_suffix("u64")?; + if digits.is_empty() { + return None; + } + if digits.starts_with('-') || digits.starts_with('+') { + return Some(Err(U64SuffixError::InvalidDigits)); + } + if !digits.as_bytes()[0].is_ascii_digit() { + return None; + } + if !digits.bytes().all(|byte| byte.is_ascii_digit()) { + return Some(Err(U64SuffixError::InvalidDigits)); + } + Some( + digits + .parse::() + .map_err(|_| U64SuffixError::OutOfRange), + ) +} diff --git a/compiler/src/llvm.rs b/compiler/src/llvm.rs new file mode 100644 index 0000000..a45a19c --- /dev/null +++ b/compiler/src/llvm.rs @@ -0,0 +1,2746 @@ +use std::collections::{HashMap, HashSet}; + +use crate::{ + ast::{BinaryOp, MatchPatternKind}, + check::{CheckedFunction, CheckedProgram, TExpr, TExprKind, TMatchArm}, + diag::Diagnostic, + std_runtime, + types::Type, +}; + +pub fn emit(_file: &str, program: &CheckedProgram) -> Result> { + let mut out = String::new(); + let layouts = struct_layouts(program); + let enum_layouts = enum_layouts(program); + let string_globals = StringGlobals::collect(program); + let needs_process_runtime = needs_process_runtime(program); + + out.push_str("; Module generated by glagol\n"); + out.push_str(&format!("; Slovo module: {}\n\n", program.module)); + out.push_str("declare void @print_i32(i32)\n\n"); + out.push_str("declare void @print_i64(i64)\n\n"); + out.push_str("declare void @print_u32(i32)\n\n"); + out.push_str("declare void @print_u64(i64)\n\n"); + out.push_str("declare void @print_f64(double)\n\n"); + out.push_str("declare void @print_string(ptr)\n\n"); + out.push_str("declare void @print_bool(i1)\n\n"); + out.push_str("declare i32 @string_len(ptr)\n\n"); + out.push_str("declare ptr @__glagol_string_concat(ptr, ptr)\n\n"); + out.push_str("declare i64 @__glagol_string_parse_i32_result(ptr)\n\n"); + out.push_str("declare i32 @__glagol_string_parse_i64_result(ptr, ptr)\n\n"); + out.push_str("declare i64 @__glagol_string_parse_u32_result(ptr)\n\n"); + out.push_str("declare i32 @__glagol_string_parse_u64_result(ptr, ptr)\n\n"); + out.push_str("declare i32 @__glagol_string_parse_f64_result(ptr, ptr)\n\n"); + out.push_str("declare i32 @__glagol_string_parse_bool_result(ptr, ptr)\n\n"); + out.push_str("declare ptr @__glagol_num_i32_to_string(i32)\n\n"); + out.push_str("declare ptr @__glagol_num_i64_to_string(i64)\n\n"); + out.push_str("declare ptr @__glagol_num_u32_to_string(i32)\n\n"); + out.push_str("declare ptr @__glagol_num_u64_to_string(i64)\n\n"); + out.push_str("declare ptr @__glagol_num_f64_to_string(double)\n\n"); + out.push_str("declare void @__glagol_io_eprint(ptr)\n\n"); + out.push_str("declare ptr @__glagol_io_read_stdin_result()\n\n"); + out.push_str("declare void @__glagol_process_init(i32, ptr)\n\n"); + out.push_str("declare i32 @__glagol_process_argc()\n\n"); + out.push_str("declare ptr @__glagol_process_arg(i32)\n\n"); + out.push_str("declare ptr @__glagol_process_arg_result(i32)\n\n"); + out.push_str("declare ptr @__glagol_env_get(ptr)\n\n"); + out.push_str("declare ptr @__glagol_env_get_result(ptr)\n\n"); + out.push_str("declare ptr @__glagol_fs_read_text(ptr)\n\n"); + out.push_str("declare ptr @__glagol_fs_read_text_result(ptr)\n\n"); + out.push_str("declare i32 @__glagol_fs_write_text(ptr, ptr)\n\n"); + out.push_str("declare i32 @__glagol_fs_write_text_result(ptr, ptr)\n\n"); + out.push_str("declare i1 @__glagol_string_eq(ptr, ptr)\n\n"); + out.push_str("declare ptr @__glagol_vec_i32_empty()\n\n"); + out.push_str("declare ptr @__glagol_vec_i32_append(ptr, i32)\n\n"); + out.push_str("declare i32 @__glagol_vec_i32_len(ptr)\n\n"); + out.push_str("declare i32 @__glagol_vec_i32_index(ptr, i32)\n\n"); + out.push_str("declare i1 @__glagol_vec_i32_eq(ptr, ptr)\n\n"); + out.push_str("declare ptr @__glagol_vec_i64_empty()\n\n"); + out.push_str("declare ptr @__glagol_vec_i64_append(ptr, i64)\n\n"); + out.push_str("declare i32 @__glagol_vec_i64_len(ptr)\n\n"); + out.push_str("declare i64 @__glagol_vec_i64_index(ptr, i32)\n\n"); + out.push_str("declare i1 @__glagol_vec_i64_eq(ptr, ptr)\n\n"); + out.push_str("declare ptr @__glagol_vec_f64_empty()\n\n"); + out.push_str("declare ptr @__glagol_vec_f64_append(ptr, double)\n\n"); + out.push_str("declare i32 @__glagol_vec_f64_len(ptr)\n\n"); + out.push_str("declare double @__glagol_vec_f64_index(ptr, i32)\n\n"); + out.push_str("declare i1 @__glagol_vec_f64_eq(ptr, ptr)\n\n"); + out.push_str("declare ptr @__glagol_vec_bool_empty()\n\n"); + out.push_str("declare ptr @__glagol_vec_bool_append(ptr, i1)\n\n"); + out.push_str("declare i32 @__glagol_vec_bool_len(ptr)\n\n"); + out.push_str("declare i1 @__glagol_vec_bool_index(ptr, i32)\n\n"); + out.push_str("declare i1 @__glagol_vec_bool_eq(ptr, ptr)\n\n"); + out.push_str("declare ptr @__glagol_vec_string_empty()\n\n"); + out.push_str("declare ptr @__glagol_vec_string_append(ptr, ptr)\n\n"); + out.push_str("declare i32 @__glagol_vec_string_len(ptr)\n\n"); + out.push_str("declare ptr @__glagol_vec_string_index(ptr, i32)\n\n"); + out.push_str("declare i1 @__glagol_vec_string_eq(ptr, ptr)\n\n"); + out.push_str("declare i32 @__glagol_time_monotonic_ms()\n\n"); + out.push_str("declare void @__glagol_time_sleep_ms(i32)\n\n"); + out.push_str("declare i32 @__glagol_random_i32()\n\n"); + out.push_str("declare void @__glagol_array_bounds_trap()\n\n"); + out.push_str("declare void @__glagol_unwrap_some_trap()\n\n"); + out.push_str("declare void @__glagol_unwrap_ok_trap()\n\n"); + out.push_str("declare void @__glagol_unwrap_err_trap()\n\n"); + + let mut declared_c_imports = HashSet::new(); + for import in &program.c_imports { + if !declared_c_imports.insert(import.name.as_str()) { + continue; + } + let params = import + .params + .iter() + .map(|(_, ty)| ty.llvm()) + .collect::>() + .join(", "); + out.push_str(&format!( + "declare {} @{}({})\n\n", + import.return_type.llvm(), + import.name, + params + )); + } + + if !string_globals.definitions.is_empty() { + out.push_str(&string_globals.emit_definitions()); + out.push('\n'); + } + + for function in &program.functions { + out.push_str(&emit_function( + function.file.as_str(), + function, + &layouts, + &enum_layouts, + &string_globals, + needs_process_runtime, + )?); + out.push('\n'); + } + + Ok(out) +} + +#[derive(Clone)] +struct StructLayout { + fields: Vec<(String, Type)>, +} + +type StructLayouts = HashMap; + +#[derive(Clone)] +struct EnumLayout { + payload_ty: Option, +} + +type EnumLayouts = HashMap; + +struct StringGlobals { + names: HashMap, + definitions: Vec, +} + +struct StringGlobal { + name: String, + value: String, +} + +impl StringGlobals { + fn collect(program: &CheckedProgram) -> Self { + let mut globals = Self { + names: HashMap::new(), + definitions: Vec::new(), + }; + + for function in &program.functions { + for expr in &function.body { + globals.collect_expr(expr); + } + } + + globals + } + + fn collect_expr(&mut self, expr: &TExpr) { + match &expr.kind { + TExprKind::String(value) => { + self.intern(value); + } + TExprKind::EnumVariant { payload, .. } => { + if let Some(payload) = payload { + self.collect_expr(payload); + } + } + TExprKind::StructInit { fields, .. } => { + for (_, value) in fields { + self.collect_expr(value); + } + } + TExprKind::ArrayInit { elements } => { + for element in elements { + self.collect_expr(element); + } + } + TExprKind::OptionSome { value } + | TExprKind::ResultOk { value } + | TExprKind::ResultErr { value } + | TExprKind::OptionIsSome { value } + | TExprKind::OptionIsNone { value } + | TExprKind::OptionUnwrapSome { value } + | TExprKind::ResultIsOk { value, .. } + | TExprKind::ResultIsErr { value, .. } + | TExprKind::ResultUnwrapOk { value, .. } + | TExprKind::ResultUnwrapErr { value, .. } + | TExprKind::FieldAccess { value, .. } => self.collect_expr(value), + TExprKind::Index { array, index } => { + self.collect_expr(array); + self.collect_expr(index); + } + TExprKind::Local { initializer, .. } => self.collect_expr(initializer), + TExprKind::Set { expr, .. } => self.collect_expr(expr), + TExprKind::Binary { left, right, .. } => { + self.collect_expr(left); + self.collect_expr(right); + } + TExprKind::If { + condition, + then_expr, + else_expr, + } => { + self.collect_expr(condition); + self.collect_expr(then_expr); + self.collect_expr(else_expr); + } + TExprKind::Match { subject, arms } => { + self.collect_expr(subject); + for arm in arms { + for expr in &arm.body { + self.collect_expr(expr); + } + } + } + TExprKind::While { condition, body } => { + self.collect_expr(condition); + for expr in body { + self.collect_expr(expr); + } + } + TExprKind::Unsafe { body } => { + for expr in body { + self.collect_expr(expr); + } + } + TExprKind::Call { args, .. } => { + for arg in args { + self.collect_expr(arg); + } + } + TExprKind::Int(_) + | TExprKind::Int64(_) + | TExprKind::UInt32(_) + | TExprKind::UInt64(_) + | TExprKind::Float(_) + | TExprKind::Bool(_) + | TExprKind::Var(_) + | TExprKind::OptionNone => {} + } + } + + fn intern(&mut self, value: &str) { + if self.names.contains_key(value) { + return; + } + + let name = format!(".str.{}", self.definitions.len()); + self.names.insert(value.to_string(), name.clone()); + self.definitions.push(StringGlobal { + name, + value: value.to_string(), + }); + } + + fn global_name(&self, value: &str) -> Option<&str> { + self.names.get(value).map(String::as_str) + } + + fn emit_definitions(&self) -> String { + let mut out = String::new(); + + for global in &self.definitions { + out.push_str(&format!( + "@{} = private unnamed_addr constant [{} x i8] c\"{}\", align 1\n", + global.name, + global.value.len() + 1, + llvm_c_string(&global.value) + )); + } + + out + } +} + +fn llvm_c_string(value: &str) -> String { + let mut out = String::new(); + + for byte in value.bytes().chain(std::iter::once(0)) { + match byte { + b'"' => out.push_str("\\22"), + b'\\' => out.push_str("\\5C"), + b'\n' => out.push_str("\\0A"), + b'\r' => out.push_str("\\0D"), + b'\t' => out.push_str("\\09"), + 0x20..=0x7e => out.push(byte as char), + _ => out.push_str(&format!("\\{:02X}", byte)), + } + } + + out +} + +fn struct_layouts(program: &CheckedProgram) -> StructLayouts { + program + .structs + .iter() + .map(|decl| { + ( + decl.name.clone(), + StructLayout { + fields: decl.fields.clone(), + }, + ) + }) + .collect() +} + +fn enum_layouts(program: &CheckedProgram) -> EnumLayouts { + program + .enums + .iter() + .map(|decl| { + ( + decl.name.clone(), + EnumLayout { + payload_ty: decl + .variants + .iter() + .find_map(|variant| variant.payload_ty.clone()), + }, + ) + }) + .collect() +} + +fn needs_process_runtime(program: &CheckedProgram) -> bool { + program + .functions + .iter() + .flat_map(|function| function.body.iter()) + .any(expr_needs_process_runtime) +} + +fn expr_needs_process_runtime(expr: &TExpr) -> bool { + match &expr.kind { + TExprKind::StructInit { fields, .. } => fields + .iter() + .any(|(_, value)| expr_needs_process_runtime(value)), + TExprKind::ArrayInit { elements } => elements.iter().any(expr_needs_process_runtime), + TExprKind::OptionSome { value } + | TExprKind::ResultOk { value } + | TExprKind::ResultErr { value } + | TExprKind::OptionIsSome { value } + | TExprKind::OptionIsNone { value } + | TExprKind::OptionUnwrapSome { value } + | TExprKind::ResultIsOk { value, .. } + | TExprKind::ResultIsErr { value, .. } + | TExprKind::ResultUnwrapOk { value, .. } + | TExprKind::ResultUnwrapErr { value, .. } + | TExprKind::FieldAccess { value, .. } => expr_needs_process_runtime(value), + TExprKind::Index { array, index } => { + expr_needs_process_runtime(array) || expr_needs_process_runtime(index) + } + TExprKind::Local { initializer, .. } => expr_needs_process_runtime(initializer), + TExprKind::Set { expr, .. } => expr_needs_process_runtime(expr), + TExprKind::Binary { left, right, .. } => { + expr_needs_process_runtime(left) || expr_needs_process_runtime(right) + } + TExprKind::If { + condition, + then_expr, + else_expr, + } => { + expr_needs_process_runtime(condition) + || expr_needs_process_runtime(then_expr) + || expr_needs_process_runtime(else_expr) + } + TExprKind::Match { subject, arms } => { + expr_needs_process_runtime(subject) + || arms + .iter() + .flat_map(|arm| arm.body.iter()) + .any(expr_needs_process_runtime) + } + TExprKind::While { condition, body } => { + expr_needs_process_runtime(condition) || body.iter().any(expr_needs_process_runtime) + } + TExprKind::Unsafe { body } => body.iter().any(expr_needs_process_runtime), + TExprKind::Call { name, args } => { + matches!( + std_runtime::runtime_symbol(name), + Some( + "__glagol_process_argc" + | "__glagol_process_arg" + | "__glagol_process_arg_result" + ) + ) || args.iter().any(expr_needs_process_runtime) + } + TExprKind::Int(_) + | TExprKind::Int64(_) + | TExprKind::UInt32(_) + | TExprKind::UInt64(_) + | TExprKind::Float(_) + | TExprKind::Bool(_) + | TExprKind::String(_) + | TExprKind::Var(_) + | TExprKind::OptionNone => false, + TExprKind::EnumVariant { payload, .. } => payload + .as_deref() + .map(expr_needs_process_runtime) + .unwrap_or(false), + } +} + +fn emit_function( + file: &str, + function: &CheckedFunction, + layouts: &StructLayouts, + enum_layouts: &EnumLayouts, + string_globals: &StringGlobals, + needs_process_runtime: bool, +) -> Result> { + validate_function_signature(file, function, layouts, enum_layouts)?; + + let mut gen = FunctionGen { + next_reg: 0, + next_block: 0, + local_alloc_counts: HashMap::new(), + current_block: "entry".to_string(), + locals: HashMap::new(), + lines: Vec::new(), + file: file.to_string(), + layouts, + enum_layouts, + string_globals, + }; + + for (name, ty) in &function.params { + match ty { + Type::Array(inner, len) + if llvm_fixed_array_type(layouts, enum_layouts, inner, *len).is_some() => + { + let llvm_ty = llvm_type(layouts, enum_layouts, ty).expect("validated array type"); + let ptr = gen.local_addr(name); + gen.lines.push(format!("{} = alloca {}", ptr, llvm_ty)); + gen.lines + .push(format!("store {} %{}, ptr {}", llvm_ty, name, ptr)); + gen.locals.insert( + name.clone(), + LocalValue::Array { + ptr, + ty: ty.clone(), + }, + ); + } + _ => { + gen.locals + .insert(name.clone(), LocalValue::Value(format!("%{}", name))); + } + } + } + + let is_entry_main = + needs_process_runtime && function.name == "main" && function.params.is_empty(); + let params = if is_entry_main { + "i32 %__glagol_argc, ptr %__glagol_argv".to_string() + } else { + function + .params + .iter() + .map(|(name, ty)| { + format!( + "{} %{}", + llvm_type(layouts, enum_layouts, ty).expect("validated type"), + name + ) + }) + .collect::>() + .join(", ") + }; + let return_ty = + llvm_type(layouts, enum_layouts, &function.return_type).expect("validated return type"); + + let mut out = String::new(); + out.push_str(&format!( + "define {} @{}({}) {{\n", + return_ty, function.name, params + )); + out.push_str("entry:\n"); + if is_entry_main { + gen.lines.push( + "call void @__glagol_process_init(i32 %__glagol_argc, ptr %__glagol_argv)".to_string(), + ); + } + + let mut last = None; + for expr in &function.body { + match gen.emit_expr(expr) { + Ok(value) => last = Some(value), + Err(err) => return Err(vec![err]), + } + } + + for line in gen.lines { + if !line.ends_with(':') { + out.push_str(" "); + } + out.push_str(&line); + out.push('\n'); + } + + match function.return_type { + Type::Unit => out.push_str(" ret void\n"), + _ => { + let value = last.unwrap_or_else(|| "0".to_string()); + let return_ty = llvm_type(layouts, enum_layouts, &function.return_type) + .expect("validated return type"); + out.push_str(&format!(" ret {} {}\n", return_ty, value)); + } + } + + out.push_str("}\n"); + Ok(out) +} + +fn validate_function_signature( + file: &str, + function: &CheckedFunction, + layouts: &StructLayouts, + enum_layouts: &EnumLayouts, +) -> Result<(), Vec> { + let mut errors = Vec::new(); + let mut reported_types = HashSet::new(); + + for (_, ty) in &function.params { + if !is_backend_param_type_supported(ty, layouts, enum_layouts) + && reported_types.insert(ty.to_string()) + { + errors.push(unsupported_type(file, function, ty)); + } + } + + if !is_backend_return_type_supported(&function.return_type, layouts, enum_layouts) + && reported_types.insert(function.return_type.to_string()) + { + errors.push(unsupported_type(file, function, &function.return_type)); + } + + if errors.is_empty() { + Ok(()) + } else { + Err(errors) + } +} + +fn is_backend_param_type_supported( + ty: &Type, + layouts: &StructLayouts, + enum_layouts: &EnumLayouts, +) -> bool { + match ty { + Type::I32 + | Type::I64 + | Type::U32 + | Type::U64 + | Type::F64 + | Type::Bool + | Type::Unit + | Type::String => true, + Type::Named(_) => true, + Type::Array(inner, len) => { + llvm_fixed_array_type(layouts, enum_layouts, inner, *len).is_some() + } + Type::Vec(inner) => { + **inner == Type::I32 + || **inner == Type::I64 + || **inner == Type::F64 + || **inner == Type::Bool + || **inner == Type::String + } + Type::Option(inner) => { + **inner == Type::I32 + || **inner == Type::I64 + || **inner == Type::U32 + || **inner == Type::U64 + || **inner == Type::F64 + || **inner == Type::Bool + || **inner == Type::String + } + Type::Result(ok, err) => result_type_supported(ok, err), + _ => false, + } +} + +fn is_backend_return_type_supported( + ty: &Type, + layouts: &StructLayouts, + enum_layouts: &EnumLayouts, +) -> bool { + match ty { + Type::I32 + | Type::I64 + | Type::U32 + | Type::U64 + | Type::F64 + | Type::Bool + | Type::Unit + | Type::String => true, + Type::Named(_) => true, + Type::Array(inner, len) => { + llvm_fixed_array_type(layouts, enum_layouts, inner, *len).is_some() + } + Type::Vec(inner) => { + **inner == Type::I32 + || **inner == Type::I64 + || **inner == Type::F64 + || **inner == Type::Bool + || **inner == Type::String + } + Type::Option(inner) => { + **inner == Type::I32 + || **inner == Type::I64 + || **inner == Type::U32 + || **inner == Type::U64 + || **inner == Type::F64 + || **inner == Type::Bool + || **inner == Type::String + } + Type::Result(ok, err) => result_type_supported(ok, err), + _ => false, + } +} + +fn llvm_type(layouts: &StructLayouts, enum_layouts: &EnumLayouts, ty: &Type) -> Option { + match ty { + Type::Array(inner, len) => llvm_fixed_array_type(layouts, enum_layouts, inner, *len), + Type::Named(name) => { + if let Some(layout) = enum_layouts.get(name) { + return Some(if let Some(payload_ty) = &layout.payload_ty { + format!( + "{{ i32, {} }}", + llvm_type(layouts, enum_layouts, payload_ty)? + ) + } else { + "i32".to_string() + }); + } + let Some(layout) = layouts.get(name) else { + return Some("i32".to_string()); + }; + let fields = layout + .fields + .iter() + .map(|(_, ty)| llvm_type(layouts, enum_layouts, ty)) + .collect::>>()? + .join(", "); + Some(format!("{{ {} }}", fields)) + } + _ => Some(ty.llvm().to_string()), + } +} + +fn llvm_fixed_array_type( + layouts: &StructLayouts, + enum_layouts: &EnumLayouts, + inner: &Type, + len: usize, +) -> Option { + if len == 0 { + return None; + } + let inner_ty = match inner { + Type::I32 | Type::I64 | Type::U32 | Type::U64 | Type::F64 | Type::Bool | Type::String => { + inner.llvm().to_string() + } + Type::Named(_) => llvm_type(layouts, enum_layouts, inner)?, + _ => return None, + }; + Some(format!("[{} x {}]", len, inner_ty)) +} + +fn format_f64_literal(value: f64) -> String { + format!("{:.17}", value) +} + +fn is_vec_i32_type(ty: &Type) -> bool { + matches!(ty, Type::Vec(inner) if **inner == Type::I32) +} + +fn is_vec_i64_type(ty: &Type) -> bool { + matches!(ty, Type::Vec(inner) if **inner == Type::I64) +} + +fn is_vec_f64_type(ty: &Type) -> bool { + matches!(ty, Type::Vec(inner) if **inner == Type::F64) +} + +fn is_vec_bool_type(ty: &Type) -> bool { + matches!(ty, Type::Vec(inner) if **inner == Type::Bool) +} + +fn is_vec_string_type(ty: &Type) -> bool { + matches!(ty, Type::Vec(inner) if **inner == Type::String) +} + +fn vec_eq_runtime_symbol(ty: &Type) -> Option<&'static str> { + if is_vec_i32_type(ty) { + Some("__glagol_vec_i32_eq") + } else if is_vec_i64_type(ty) { + Some("__glagol_vec_i64_eq") + } else if is_vec_f64_type(ty) { + Some("__glagol_vec_f64_eq") + } else if is_vec_bool_type(ty) { + Some("__glagol_vec_bool_eq") + } else if is_vec_string_type(ty) { + Some("__glagol_vec_string_eq") + } else { + None + } +} + +fn result_type_supported(ok: &Type, err: &Type) -> bool { + matches!( + (ok, err), + (Type::I32, Type::I32) + | (Type::I64, Type::I32) + | (Type::U32, Type::I32) + | (Type::U64, Type::I32) + | (Type::F64, Type::I32) + | (Type::Bool, Type::I32) + | (Type::String, Type::I32) + ) +} + +fn zero_value_for_type(ty: &Type) -> Option { + match ty { + Type::I32 => Some("0".to_string()), + Type::I64 => Some("0".to_string()), + Type::U32 => Some("0".to_string()), + Type::U64 => Some("0".to_string()), + Type::F64 => Some("0.0".to_string()), + Type::Bool => Some("0".to_string()), + Type::String => Some("null".to_string()), + Type::Array(_, _) | Type::Named(_) => Some("zeroinitializer".to_string()), + _ => None, + } +} + +fn result_payload_index(ty: &Type, ok_payload: bool) -> Option { + match ty { + Type::Option(inner) + if (**inner == Type::I32 + || **inner == Type::I64 + || **inner == Type::U32 + || **inner == Type::U64 + || **inner == Type::F64 + || **inner == Type::Bool + || **inner == Type::String) + && ok_payload => + { + Some(1) + } + Type::Result(ok, err) if **ok == Type::I32 && **err == Type::I32 => Some(1), + Type::Result(ok, err) if **ok == Type::I64 && **err == Type::I32 => { + Some(if ok_payload { 1 } else { 2 }) + } + Type::Result(ok, err) if **ok == Type::U32 && **err == Type::I32 => Some(1), + Type::Result(ok, err) if **ok == Type::U64 && **err == Type::I32 => { + Some(if ok_payload { 1 } else { 2 }) + } + Type::Result(ok, err) if **ok == Type::F64 && **err == Type::I32 => { + Some(if ok_payload { 1 } else { 2 }) + } + Type::Result(ok, err) if **ok == Type::Bool && **err == Type::I32 => { + Some(if ok_payload { 1 } else { 2 }) + } + Type::Result(ok, err) if **ok == Type::String && **err == Type::I32 => { + Some(if ok_payload { 1 } else { 2 }) + } + _ => None, + } +} + +fn unsupported_type(file: &str, function: &CheckedFunction, ty: &Type) -> Diagnostic { + Diagnostic::new( + file, + "UnsupportedBackendFeature", + format!( + "backend does not support `{}` in function `{}` signature", + ty, function.name + ), + ) + .with_span(function.span) + .hint( + "keep this type in speculative examples until layout and LLVM ABI behavior are implemented", + ) +} + +struct FunctionGen<'a> { + next_reg: usize, + next_block: usize, + local_alloc_counts: HashMap, + current_block: String, + locals: HashMap, + lines: Vec, + file: String, + layouts: &'a StructLayouts, + enum_layouts: &'a EnumLayouts, + string_globals: &'a StringGlobals, +} + +#[derive(Clone)] +enum LocalValue { + Value(String), + Ptr { ptr: String, ty: Type }, + Array { ptr: String, ty: Type }, +} + +impl FunctionGen<'_> { + fn reg(&mut self) -> String { + let reg = format!("%{}", self.next_reg); + self.next_reg += 1; + reg + } + + fn block(&mut self, prefix: &str) -> String { + let block = format!("{}.{}", prefix, self.next_block); + self.next_block += 1; + block + } + + fn local_addr(&mut self, name: &str) -> String { + let count = self.local_alloc_counts.entry(name.to_string()).or_insert(0); + let ptr = if *count == 0 { + format!("%{}.addr", name) + } else { + format!("%{}.addr.{}", name, count) + }; + *count += 1; + ptr + } + + fn label(&mut self, block: &str) { + self.lines.push(format!("{}:", block)); + self.current_block = block.to_string(); + } + + fn unsupported(&self, expr: &TExpr, feature: &str) -> Diagnostic { + Diagnostic::new( + &self.file, + "UnsupportedBackendFeature", + format!("backend does not support {}", feature), + ) + .with_span(expr.span) + .hint("keep this form in speculative examples until LLVM lowering is implemented") + } + + fn llvm_type(&self, expr: &TExpr, ty: &Type) -> Result { + llvm_type(self.layouts, self.enum_layouts, ty).ok_or_else(|| { + self.unsupported(expr, &format!("LLVM representation for type `{}`", ty)) + }) + } + + fn emit_numeric_widening_conversion( + &mut self, + expr: &TExpr, + args: &[TExpr], + instruction: &str, + from_ty: &str, + to_ty: &str, + ) -> Result { + let [arg] = args else { + return Err(self.unsupported(expr, "malformed numeric widening conversion")); + }; + let value = self.emit_expr(arg)?; + let reg = self.reg(); + self.lines.push(format!( + "{} = {} {} {} to {}", + reg, instruction, from_ty, value, to_ty + )); + Ok(reg) + } + + fn emit_i64_to_i32_result_conversion( + &mut self, + expr: &TExpr, + args: &[TExpr], + ) -> Result { + let [arg] = args else { + return Err(self.unsupported(expr, "malformed checked i64-to-i32 conversion")); + }; + let value = self.emit_expr(arg)?; + let above_min = self.reg(); + self.lines.push(format!( + "{} = icmp sge i64 {}, -2147483648", + above_min, value + )); + let below_max = self.reg(); + self.lines.push(format!( + "{} = icmp sle i64 {}, 2147483647", + below_max, value + )); + let in_range = self.reg(); + self.lines.push(format!( + "{} = and i1 {}, {}", + in_range, above_min, below_max + )); + let narrowed = self.reg(); + self.lines + .push(format!("{} = trunc i64 {} to i32", narrowed, value)); + let payload = self.reg(); + self.lines.push(format!( + "{} = select i1 {}, i32 {}, i32 1", + payload, in_range, narrowed + )); + self.emit_tagged_i32_aggregate_from_values(expr, &in_range, &payload) + } + + fn emit_f64_to_i32_result_conversion( + &mut self, + expr: &TExpr, + args: &[TExpr], + ) -> Result { + let [arg] = args else { + return Err(self.unsupported(expr, "malformed checked f64-to-i32 conversion")); + }; + let value = self.emit_expr(arg)?; + let above_min = self.reg(); + self.lines.push(format!( + "{} = fcmp oge double {}, -2147483648.0", + above_min, value + )); + let below_max = self.reg(); + self.lines.push(format!( + "{} = fcmp ole double {}, 2147483647.0", + below_max, value + )); + let in_range = self.reg(); + self.lines.push(format!( + "{} = and i1 {}, {}", + in_range, above_min, below_max + )); + + let integral_block = self.block("f64.to_i32.integral"); + let exponent_block = self.block("f64.to_i32.exponent"); + let fraction_block = self.block("f64.to_i32.fraction"); + let convert_block = self.block("f64.to_i32.convert"); + let err_block = self.block("f64.to_i32.err"); + let merge_block = self.block("f64.to_i32.end"); + self.lines.push(format!( + "br i1 {}, label %{}, label %{}", + in_range, integral_block, err_block + )); + + self.label(&integral_block); + let bits = self.reg(); + self.lines + .push(format!("{} = bitcast double {} to i64", bits, value)); + let abs_bits = self.reg(); + self.lines.push(format!( + "{} = and i64 {}, 9223372036854775807", + abs_bits, bits + )); + let is_zero = self.reg(); + self.lines + .push(format!("{} = icmp eq i64 {}, 0", is_zero, abs_bits)); + self.lines.push(format!( + "br i1 {}, label %{}, label %{}", + is_zero, convert_block, exponent_block + )); + + self.label(&exponent_block); + let exponent = self.reg(); + self.lines + .push(format!("{} = lshr i64 {}, 52", exponent, abs_bits)); + let exponent_ge_bias = self.reg(); + self.lines.push(format!( + "{} = icmp uge i64 {}, 1023", + exponent_ge_bias, exponent + )); + self.lines.push(format!( + "br i1 {}, label %{}, label %{}", + exponent_ge_bias, fraction_block, err_block + )); + + self.label(&fraction_block); + let unbiased_exponent = self.reg(); + self.lines.push(format!( + "{} = sub i64 {}, 1023", + unbiased_exponent, exponent + )); + let fractional_width = self.reg(); + self.lines.push(format!( + "{} = sub i64 52, {}", + fractional_width, unbiased_exponent + )); + let fractional_one = self.reg(); + self.lines.push(format!( + "{} = shl i64 1, {}", + fractional_one, fractional_width + )); + let fractional_mask = self.reg(); + self.lines.push(format!( + "{} = sub i64 {}, 1", + fractional_mask, fractional_one + )); + let fractional_bits = self.reg(); + self.lines.push(format!( + "{} = and i64 {}, {}", + fractional_bits, abs_bits, fractional_mask + )); + let integral = self.reg(); + self.lines + .push(format!("{} = icmp eq i64 {}, 0", integral, fractional_bits)); + self.lines.push(format!( + "br i1 {}, label %{}, label %{}", + integral, convert_block, err_block + )); + + self.label(&convert_block); + let narrowed = self.reg(); + self.lines + .push(format!("{} = fptosi double {} to i32", narrowed, value)); + let ok_result = self.emit_tagged_i32_aggregate_from_values(expr, "true", &narrowed)?; + let ok_pred = self.current_block.clone(); + self.lines.push(format!("br label %{}", merge_block)); + + self.label(&err_block); + let err_result = self.emit_tagged_i32_aggregate_from_values(expr, "false", "1")?; + let err_pred = self.current_block.clone(); + self.lines.push(format!("br label %{}", merge_block)); + + self.label(&merge_block); + let aggregate_ty = self.llvm_type(expr, &expr.ty)?; + let result = self.reg(); + self.lines.push(format!( + "{} = phi {} [ {}, %{} ], [ {}, %{} ]", + result, aggregate_ty, ok_result, ok_pred, err_result, err_pred + )); + Ok(result) + } + + fn emit_f64_to_i64_result_conversion( + &mut self, + expr: &TExpr, + args: &[TExpr], + ) -> Result { + let [arg] = args else { + return Err(self.unsupported(expr, "malformed checked f64-to-i64 conversion")); + }; + let value = self.emit_expr(arg)?; + let above_min = self.reg(); + self.lines.push(format!( + "{} = fcmp oge double {}, -9223372036854775808.0", + above_min, value + )); + let below_max = self.reg(); + self.lines.push(format!( + "{} = fcmp olt double {}, 9223372036854775808.0", + below_max, value + )); + let in_range = self.reg(); + self.lines.push(format!( + "{} = and i1 {}, {}", + in_range, above_min, below_max + )); + + let integral_block = self.block("f64.to_i64.integral"); + let exponent_block = self.block("f64.to_i64.exponent"); + let mantissa_block = self.block("f64.to_i64.mantissa"); + let fraction_block = self.block("f64.to_i64.fraction"); + let convert_block = self.block("f64.to_i64.convert"); + let err_block = self.block("f64.to_i64.err"); + let merge_block = self.block("f64.to_i64.end"); + self.lines.push(format!( + "br i1 {}, label %{}, label %{}", + in_range, integral_block, err_block + )); + + self.label(&integral_block); + let bits = self.reg(); + self.lines + .push(format!("{} = bitcast double {} to i64", bits, value)); + let abs_bits = self.reg(); + self.lines.push(format!( + "{} = and i64 {}, 9223372036854775807", + abs_bits, bits + )); + let is_zero = self.reg(); + self.lines + .push(format!("{} = icmp eq i64 {}, 0", is_zero, abs_bits)); + self.lines.push(format!( + "br i1 {}, label %{}, label %{}", + is_zero, convert_block, exponent_block + )); + + self.label(&exponent_block); + let exponent = self.reg(); + self.lines + .push(format!("{} = lshr i64 {}, 52", exponent, abs_bits)); + let exponent_ge_bias = self.reg(); + self.lines.push(format!( + "{} = icmp uge i64 {}, 1023", + exponent_ge_bias, exponent + )); + self.lines.push(format!( + "br i1 {}, label %{}, label %{}", + exponent_ge_bias, mantissa_block, err_block + )); + + self.label(&mantissa_block); + let unbiased_exponent = self.reg(); + self.lines.push(format!( + "{} = sub i64 {}, 1023", + unbiased_exponent, exponent + )); + let no_fractional_bits = self.reg(); + self.lines.push(format!( + "{} = icmp uge i64 {}, 52", + no_fractional_bits, unbiased_exponent + )); + self.lines.push(format!( + "br i1 {}, label %{}, label %{}", + no_fractional_bits, convert_block, fraction_block + )); + + self.label(&fraction_block); + let fractional_width = self.reg(); + self.lines.push(format!( + "{} = sub i64 52, {}", + fractional_width, unbiased_exponent + )); + let fractional_one = self.reg(); + self.lines.push(format!( + "{} = shl i64 1, {}", + fractional_one, fractional_width + )); + let fractional_mask = self.reg(); + self.lines.push(format!( + "{} = sub i64 {}, 1", + fractional_mask, fractional_one + )); + let fractional_bits = self.reg(); + self.lines.push(format!( + "{} = and i64 {}, {}", + fractional_bits, abs_bits, fractional_mask + )); + let integral = self.reg(); + self.lines + .push(format!("{} = icmp eq i64 {}, 0", integral, fractional_bits)); + self.lines.push(format!( + "br i1 {}, label %{}, label %{}", + integral, convert_block, err_block + )); + + self.label(&convert_block); + let narrowed = self.reg(); + self.lines + .push(format!("{} = fptosi double {} to i64", narrowed, value)); + let ok_result = self.emit_i64_result_aggregate(expr, "true", &narrowed, "0")?; + let ok_pred = self.current_block.clone(); + self.lines.push(format!("br label %{}", merge_block)); + + self.label(&err_block); + let err_result = self.emit_i64_result_aggregate(expr, "false", "0", "1")?; + let err_pred = self.current_block.clone(); + self.lines.push(format!("br label %{}", merge_block)); + + self.label(&merge_block); + let aggregate_ty = self.llvm_type(expr, &expr.ty)?; + let result = self.reg(); + self.lines.push(format!( + "{} = phi {} [ {}, %{} ], [ {}, %{} ]", + result, aggregate_ty, ok_result, ok_pred, err_result, err_pred + )); + Ok(result) + } + + fn is_payload_enum_type(&self, ty: &Type) -> bool { + matches!( + ty, + Type::Named(name) + if self + .enum_layouts + .get(name) + .and_then(|layout| layout.payload_ty.as_ref()) + .is_some() + ) + } + + fn enum_payload_type<'b>(&'b self, ty: &Type) -> Option<&'b Type> { + match ty { + Type::Named(name) => self + .enum_layouts + .get(name) + .and_then(|layout| layout.payload_ty.as_ref()), + _ => None, + } + } + + fn emit_equality_for_type( + &mut self, + ty: &Type, + left: &str, + right: &str, + ) -> Result { + let reg = self.reg(); + match ty { + Type::String => self.lines.push(format!( + "{} = call i1 @__glagol_string_eq(ptr {}, ptr {})", + reg, left, right + )), + Type::F64 => self + .lines + .push(format!("{} = fcmp oeq double {}, {}", reg, left, right)), + Type::Bool => self + .lines + .push(format!("{} = icmp eq i1 {}, {}", reg, left, right)), + Type::U64 => self + .lines + .push(format!("{} = icmp eq i64 {}, {}", reg, left, right)), + Type::I64 => self + .lines + .push(format!("{} = icmp eq i64 {}, {}", reg, left, right)), + Type::U32 => self + .lines + .push(format!("{} = icmp eq i32 {}, {}", reg, left, right)), + Type::I32 => self + .lines + .push(format!("{} = icmp eq i32 {}, {}", reg, left, right)), + _ => { + return Err(Diagnostic::new( + &self.file, + "UnsupportedBackendFeature", + format!( + "backend does not support equality for enum payload type `{}`", + ty + ), + )) + } + } + Ok(reg) + } + + fn enum_payload_zero_value(&self, ty: &Type) -> Result { + zero_value_for_type(ty).ok_or_else(|| { + Diagnostic::new( + &self.file, + "UnsupportedBackendFeature", + format!("backend does not support enum payload type `{}`", ty), + ) + }) + } + + fn enum_payload_llvm_type(&self, ty: &Type) -> Result { + llvm_type(self.layouts, self.enum_layouts, ty).ok_or_else(|| { + Diagnostic::new( + &self.file, + "UnsupportedBackendFeature", + format!("backend does not support enum payload type `{}`", ty), + ) + }) + } + + fn emit_payload_enum_equality( + &mut self, + reg: &str, + ty: &Type, + left_aggregate_ty: &str, + left_value: &str, + right_value: &str, + ) -> Result<(), Diagnostic> { + let left_tag = self.reg(); + self.lines.push(format!( + "{} = extractvalue {} {}, 0", + left_tag, left_aggregate_ty, left_value + )); + let right_tag = self.reg(); + self.lines.push(format!( + "{} = extractvalue {} {}, 0", + right_tag, left_aggregate_ty, right_value + )); + let tags_equal = self.reg(); + self.lines.push(format!( + "{} = icmp eq i32 {}, {}", + tags_equal, left_tag, right_tag + )); + let left_payload = self.reg(); + self.lines.push(format!( + "{} = extractvalue {} {}, 1", + left_payload, left_aggregate_ty, left_value + )); + let right_payload = self.reg(); + self.lines.push(format!( + "{} = extractvalue {} {}, 1", + right_payload, left_aggregate_ty, right_value + )); + let payloads_equal = self.emit_equality_for_type(ty, &left_payload, &right_payload)?; + self.lines.push(format!( + "{} = and i1 {}, {}", + reg, tags_equal, payloads_equal + )); + Ok(()) + } + + fn emit_expr(&mut self, expr: &TExpr) -> Result { + match &expr.kind { + TExprKind::Int(value) => Ok(value.to_string()), + TExprKind::Int64(value) => Ok(value.to_string()), + TExprKind::UInt32(value) => Ok(value.to_string()), + TExprKind::UInt64(value) => Ok(value.to_string()), + TExprKind::Float(value) => Ok(format_f64_literal(*value)), + TExprKind::Bool(value) => Ok(if *value { + "1".to_string() + } else { + "0".to_string() + }), + TExprKind::String(value) => self + .string_globals + .global_name(value) + .map(|name| format!("@{}", name)) + .ok_or_else(|| self.unsupported(expr, "string literal storage")), + TExprKind::EnumVariant { + enum_name, + discriminant, + payload, + .. + } => self.emit_enum_variant(expr, enum_name, *discriminant, payload.as_deref()), + TExprKind::StructInit { name, fields } => self.emit_struct_init(expr, name, fields), + TExprKind::ArrayInit { elements } => self.emit_array_init(expr, elements), + TExprKind::OptionSome { value } => self.emit_option_aggregate(expr, true, Some(value)), + TExprKind::OptionNone => self.emit_option_aggregate(expr, false, None), + TExprKind::ResultOk { value } => self.emit_result_aggregate(expr, true, value), + TExprKind::ResultErr { value } => self.emit_result_aggregate(expr, false, value), + TExprKind::OptionIsSome { value } => self.emit_tag_observer(expr, value, false), + TExprKind::OptionIsNone { value } => self.emit_tag_observer(expr, value, true), + TExprKind::OptionUnwrapSome { value } => { + self.emit_tag_checked_payload_access(expr, value, true, "__glagol_unwrap_some_trap") + } + TExprKind::ResultIsOk { value, .. } => self.emit_tag_observer(expr, value, false), + TExprKind::ResultIsErr { value, .. } => self.emit_tag_observer(expr, value, true), + TExprKind::ResultUnwrapOk { value, .. } => { + self.emit_tag_checked_payload_access(expr, value, true, "__glagol_unwrap_ok_trap") + } + TExprKind::ResultUnwrapErr { value, .. } => { + self.emit_tag_checked_payload_access(expr, value, false, "__glagol_unwrap_err_trap") + } + TExprKind::FieldAccess { value, field } => self.emit_field_access(expr, value, field), + TExprKind::Index { array, index } => self.emit_index(expr, array, index), + TExprKind::Var(name) => match self.locals.get(name).cloned() { + Some(LocalValue::Value(value)) => Ok(value), + Some(LocalValue::Ptr { ptr, ty }) => { + let reg = self.reg(); + let llvm_ty = self.llvm_type(expr, &ty)?; + self.lines + .push(format!("{} = load {}, ptr {}", reg, llvm_ty, ptr)); + Ok(reg) + } + Some(LocalValue::Array { ptr, ty }) => { + let reg = self.reg(); + let llvm_ty = self.llvm_type(expr, &ty)?; + self.lines + .push(format!("{} = load {}, ptr {}", reg, llvm_ty, ptr)); + Ok(reg) + } + None => Ok(format!("%{}", name)), + }, + TExprKind::Local { + mutable, + name, + initializer, + } => match &initializer.ty { + Type::I32 | Type::Bool | Type::I64 | Type::U32 | Type::U64 | Type::F64 + if !*mutable => + { + self.emit_ssa_local(name, initializer) + } + Type::I32 | Type::Bool | Type::I64 | Type::U32 | Type::U64 | Type::F64 => { + self.emit_value_local(name, initializer) + } + Type::String => self.emit_value_local(name, initializer), + Type::Vec(inner) + if (**inner == Type::I32 + || **inner == Type::I64 + || **inner == Type::F64 + || **inner == Type::Bool + || **inner == Type::String) => + { + self.emit_value_local(name, initializer) + } + Type::Option(inner) + if (**inner == Type::I32 + || **inner == Type::I64 + || **inner == Type::U32 + || **inner == Type::U64 + || **inner == Type::F64 + || **inner == Type::Bool + || **inner == Type::String) => + { + self.emit_value_local(name, initializer) + } + Type::Result(ok, err) if result_type_supported(ok, err) => { + self.emit_value_local(name, initializer) + } + Type::Named(struct_name) if self.layouts.contains_key(struct_name) => { + self.emit_value_local(name, initializer) + } + Type::Named(_) => self.emit_value_local(name, initializer), + Type::Array(_, _) if !*mutable => self.emit_array_local(expr, name, initializer), + _ => Err(self.unsupported(expr, "unsupported local variables")), + }, + TExprKind::Set { name, expr: value } => { + let rhs = self.emit_expr(value)?; + let Some(LocalValue::Ptr { ptr, ty }) = self.locals.get(name).cloned() else { + return Err(self.unsupported(expr, "assignment to non-local values")); + }; + let llvm_ty = self.llvm_type(expr, &ty)?; + self.lines + .push(format!("store {} {}, ptr {}", llvm_ty, rhs, ptr)); + Ok("0".to_string()) + } + TExprKind::Binary { op, left, right } => { + let l = self.emit_expr(left)?; + let r = self.emit_expr(right)?; + let reg = self.reg(); + + match op { + BinaryOp::Add if left.ty == Type::F64 => self + .lines + .push(format!("{} = fadd double {}, {}", reg, l, r)), + BinaryOp::Sub if left.ty == Type::F64 => self + .lines + .push(format!("{} = fsub double {}, {}", reg, l, r)), + BinaryOp::Mul if left.ty == Type::F64 => self + .lines + .push(format!("{} = fmul double {}, {}", reg, l, r)), + BinaryOp::Div if left.ty == Type::F64 => self + .lines + .push(format!("{} = fdiv double {}, {}", reg, l, r)), + BinaryOp::Rem if left.ty == Type::F64 => { + return Err(self.unsupported(expr, "f64 remainder")) + } + BinaryOp::BitAnd | BinaryOp::BitOr | BinaryOp::BitXor + if left.ty == Type::F64 => + { + return Err(self.unsupported(expr, "f64 bitwise operation")) + } + BinaryOp::Add if left.ty == Type::I64 => { + self.lines.push(format!("{} = add i64 {}, {}", reg, l, r)) + } + BinaryOp::Add if left.ty == Type::U64 => { + self.lines.push(format!("{} = add i64 {}, {}", reg, l, r)) + } + BinaryOp::Sub if left.ty == Type::I64 => { + self.lines.push(format!("{} = sub i64 {}, {}", reg, l, r)) + } + BinaryOp::Sub if left.ty == Type::U64 => { + self.lines.push(format!("{} = sub i64 {}, {}", reg, l, r)) + } + BinaryOp::Mul if left.ty == Type::I64 => { + self.lines.push(format!("{} = mul i64 {}, {}", reg, l, r)) + } + BinaryOp::Mul if left.ty == Type::U64 => { + self.lines.push(format!("{} = mul i64 {}, {}", reg, l, r)) + } + BinaryOp::Div if left.ty == Type::I64 => { + self.lines.push(format!("{} = sdiv i64 {}, {}", reg, l, r)) + } + BinaryOp::Div if left.ty == Type::U64 => { + self.lines.push(format!("{} = udiv i64 {}, {}", reg, l, r)) + } + BinaryOp::Rem if left.ty == Type::I64 => { + self.lines.push(format!("{} = srem i64 {}, {}", reg, l, r)) + } + BinaryOp::Rem if left.ty == Type::U64 => { + self.lines.push(format!("{} = urem i64 {}, {}", reg, l, r)) + } + BinaryOp::BitAnd if left.ty == Type::I64 => { + self.lines.push(format!("{} = and i64 {}, {}", reg, l, r)) + } + BinaryOp::BitAnd if left.ty == Type::U64 => { + self.lines.push(format!("{} = and i64 {}, {}", reg, l, r)) + } + BinaryOp::BitOr if left.ty == Type::I64 => { + self.lines.push(format!("{} = or i64 {}, {}", reg, l, r)) + } + BinaryOp::BitOr if left.ty == Type::U64 => { + self.lines.push(format!("{} = or i64 {}, {}", reg, l, r)) + } + BinaryOp::BitXor if left.ty == Type::I64 => { + self.lines.push(format!("{} = xor i64 {}, {}", reg, l, r)) + } + BinaryOp::BitXor if left.ty == Type::U64 => { + self.lines.push(format!("{} = xor i64 {}, {}", reg, l, r)) + } + BinaryOp::Add => self.lines.push(format!("{} = add i32 {}, {}", reg, l, r)), + BinaryOp::Sub => self.lines.push(format!("{} = sub i32 {}, {}", reg, l, r)), + BinaryOp::Mul => self.lines.push(format!("{} = mul i32 {}, {}", reg, l, r)), + BinaryOp::Div if left.ty == Type::U32 => { + self.lines.push(format!("{} = udiv i32 {}, {}", reg, l, r)) + } + BinaryOp::Div => self.lines.push(format!("{} = sdiv i32 {}, {}", reg, l, r)), + BinaryOp::Rem if left.ty == Type::U32 => { + self.lines.push(format!("{} = urem i32 {}, {}", reg, l, r)) + } + BinaryOp::Rem => self.lines.push(format!("{} = srem i32 {}, {}", reg, l, r)), + BinaryOp::BitAnd => self.lines.push(format!("{} = and i32 {}, {}", reg, l, r)), + BinaryOp::BitOr => self.lines.push(format!("{} = or i32 {}, {}", reg, l, r)), + BinaryOp::BitXor => self.lines.push(format!("{} = xor i32 {}, {}", reg, l, r)), + BinaryOp::Eq if left.ty == Type::String && right.ty == Type::String => { + self.lines.push(format!( + "{} = call i1 @__glagol_string_eq(ptr {}, ptr {})", + reg, l, r + )) + } + BinaryOp::Eq + if vec_eq_runtime_symbol(&left.ty).is_some() && left.ty == right.ty => + { + let symbol = + vec_eq_runtime_symbol(&left.ty).expect("vector equality symbol"); + self.lines.push(format!( + "{} = call i1 @{}(ptr {}, ptr {})", + reg, symbol, l, r + )) + } + BinaryOp::Eq if self.is_payload_enum_type(&left.ty) => { + let left_ty = self.llvm_type(left, &left.ty)?; + let payload_ty = self + .enum_payload_type(&left.ty) + .ok_or_else(|| self.unsupported(expr, "enum payload metadata"))? + .clone(); + self.emit_payload_enum_equality(®, &payload_ty, &left_ty, &l, &r)?; + } + BinaryOp::Eq if left.ty == Type::F64 => self + .lines + .push(format!("{} = fcmp oeq double {}, {}", reg, l, r)), + BinaryOp::Eq if left.ty == Type::Bool => self + .lines + .push(format!("{} = icmp eq i1 {}, {}", reg, l, r)), + BinaryOp::Eq if left.ty == Type::I64 => self + .lines + .push(format!("{} = icmp eq i64 {}, {}", reg, l, r)), + BinaryOp::Eq if left.ty == Type::U64 => self + .lines + .push(format!("{} = icmp eq i64 {}, {}", reg, l, r)), + BinaryOp::Eq => self + .lines + .push(format!("{} = icmp eq i32 {}, {}", reg, l, r)), + BinaryOp::Lt if left.ty == Type::F64 => self + .lines + .push(format!("{} = fcmp olt double {}, {}", reg, l, r)), + BinaryOp::Lt if left.ty == Type::I64 => self + .lines + .push(format!("{} = icmp slt i64 {}, {}", reg, l, r)), + BinaryOp::Lt if left.ty == Type::U64 => self + .lines + .push(format!("{} = icmp ult i64 {}, {}", reg, l, r)), + BinaryOp::Lt if left.ty == Type::U32 => self + .lines + .push(format!("{} = icmp ult i32 {}, {}", reg, l, r)), + BinaryOp::Lt => self + .lines + .push(format!("{} = icmp slt i32 {}, {}", reg, l, r)), + BinaryOp::Gt if left.ty == Type::F64 => self + .lines + .push(format!("{} = fcmp ogt double {}, {}", reg, l, r)), + BinaryOp::Gt if left.ty == Type::I64 => self + .lines + .push(format!("{} = icmp sgt i64 {}, {}", reg, l, r)), + BinaryOp::Gt if left.ty == Type::U64 => self + .lines + .push(format!("{} = icmp ugt i64 {}, {}", reg, l, r)), + BinaryOp::Gt if left.ty == Type::U32 => self + .lines + .push(format!("{} = icmp ugt i32 {}, {}", reg, l, r)), + BinaryOp::Gt => self + .lines + .push(format!("{} = icmp sgt i32 {}, {}", reg, l, r)), + BinaryOp::Le if left.ty == Type::F64 => self + .lines + .push(format!("{} = fcmp ole double {}, {}", reg, l, r)), + BinaryOp::Le if left.ty == Type::I64 => self + .lines + .push(format!("{} = icmp sle i64 {}, {}", reg, l, r)), + BinaryOp::Le if left.ty == Type::U64 => self + .lines + .push(format!("{} = icmp ule i64 {}, {}", reg, l, r)), + BinaryOp::Le if left.ty == Type::U32 => self + .lines + .push(format!("{} = icmp ule i32 {}, {}", reg, l, r)), + BinaryOp::Le => self + .lines + .push(format!("{} = icmp sle i32 {}, {}", reg, l, r)), + BinaryOp::Ge if left.ty == Type::F64 => self + .lines + .push(format!("{} = fcmp oge double {}, {}", reg, l, r)), + BinaryOp::Ge if left.ty == Type::I64 => self + .lines + .push(format!("{} = icmp sge i64 {}, {}", reg, l, r)), + BinaryOp::Ge if left.ty == Type::U64 => self + .lines + .push(format!("{} = icmp uge i64 {}, {}", reg, l, r)), + BinaryOp::Ge if left.ty == Type::U32 => self + .lines + .push(format!("{} = icmp uge i32 {}, {}", reg, l, r)), + BinaryOp::Ge => self + .lines + .push(format!("{} = icmp sge i32 {}, {}", reg, l, r)), + } + + Ok(reg) + } + TExprKind::Call { name, args } => { + match name.as_str() { + "std.num.i32_to_i64" => { + return self + .emit_numeric_widening_conversion(expr, args, "sext", "i32", "i64"); + } + "std.num.i32_to_f64" => { + return self.emit_numeric_widening_conversion( + expr, args, "sitofp", "i32", "double", + ); + } + "std.num.i64_to_f64" => { + return self.emit_numeric_widening_conversion( + expr, args, "sitofp", "i64", "double", + ); + } + "std.num.i64_to_i32_result" => { + return self.emit_i64_to_i32_result_conversion(expr, args); + } + "std.num.f64_to_i32_result" => { + return self.emit_f64_to_i32_result_conversion(expr, args); + } + "std.num.f64_to_i64_result" => { + return self.emit_f64_to_i64_result_conversion(expr, args); + } + _ => {} + } + + let mut arg_values = Vec::new(); + + for arg in args { + let value = self.emit_expr(arg)?; + let arg_ty = self.llvm_type(arg, &arg.ty)?; + arg_values.push(format!("{} {}", arg_ty, value)); + } + + let arg_values = arg_values.join(", "); + let callee = std_runtime::runtime_symbol(name).unwrap_or(name); + + if matches!( + callee, + "__glagol_process_arg_result" + | "__glagol_env_get_result" + | "__glagol_fs_read_text_result" + | "__glagol_io_read_stdin_result" + ) { + return self.emit_string_result_host_call(expr, callee, &arg_values); + } + + if callee == "__glagol_fs_write_text_result" { + return self.emit_i32_result_status_call(expr, callee, &arg_values); + } + + if callee == "__glagol_string_parse_i32_result" { + return self.emit_i32_result_encoded_i64_call(expr, callee, &arg_values); + } + + if callee == "__glagol_string_parse_u32_result" { + return self.emit_i32_result_encoded_i64_call(expr, callee, &arg_values); + } + + if callee == "__glagol_string_parse_i64_result" { + return self.emit_i64_result_out_param_call(expr, callee, &arg_values); + } + + if callee == "__glagol_string_parse_u64_result" { + return self.emit_i64_result_out_param_call(expr, callee, &arg_values); + } + + if callee == "__glagol_string_parse_f64_result" { + return self.emit_f64_result_out_param_call(expr, callee, &arg_values); + } + + if callee == "__glagol_string_parse_bool_result" { + return self.emit_bool_result_out_param_call(expr, callee, &arg_values); + } + + if expr.ty == Type::Unit { + self.lines + .push(format!("call void @{}({})", callee, arg_values)); + Ok("0".to_string()) + } else { + let reg = self.reg(); + let result_ty = self.llvm_type(expr, &expr.ty)?; + self.lines.push(format!( + "{} = call {} @{}({})", + reg, result_ty, callee, arg_values + )); + Ok(reg) + } + } + TExprKind::If { + condition, + then_expr, + else_expr, + } => { + let then_block = self.block("if.then"); + let else_block = self.block("if.else"); + let end_block = self.block("if.end"); + let cond = self.emit_expr(condition)?; + + self.lines.push(format!( + "br i1 {}, label %{}, label %{}", + cond, then_block, else_block + )); + + self.label(&then_block); + let then_value = self.emit_expr(then_expr)?; + let then_pred = self.current_block.clone(); + self.lines.push(format!("br label %{}", end_block)); + + self.label(&else_block); + let else_value = self.emit_expr(else_expr)?; + let else_pred = self.current_block.clone(); + self.lines.push(format!("br label %{}", end_block)); + + self.label(&end_block); + + if expr.ty == Type::Unit { + Ok("0".to_string()) + } else { + let reg = self.reg(); + let phi_ty = self.llvm_type(expr, &expr.ty)?; + self.lines.push(format!( + "{} = phi {} [ {}, %{} ], [ {}, %{} ]", + reg, phi_ty, then_value, then_pred, else_value, else_pred, + )); + Ok(reg) + } + } + TExprKind::Match { subject, arms } => self.emit_match(expr, subject, arms), + TExprKind::While { condition, body } => { + let cond_block = self.block("while.cond"); + let body_block = self.block("while.body"); + let end_block = self.block("while.end"); + + self.lines.push(format!("br label %{}", cond_block)); + + self.label(&cond_block); + let cond = self.emit_expr(condition)?; + self.lines.push(format!( + "br i1 {}, label %{}, label %{}", + cond, body_block, end_block + )); + + self.label(&body_block); + for expr in body { + self.emit_expr(expr)?; + } + self.lines.push(format!("br label %{}", cond_block)); + + self.label(&end_block); + Ok("0".to_string()) + } + TExprKind::Unsafe { body } => { + let saved_locals = self.locals.clone(); + let mut value = "0".to_string(); + for expr in body { + value = self.emit_expr(expr)?; + } + self.locals = saved_locals; + Ok(value) + } + } + } + + fn emit_option_aggregate( + &mut self, + expr: &TExpr, + tag: bool, + payload: Option<&TExpr>, + ) -> Result { + let aggregate_ty = self.llvm_type(expr, &expr.ty)?; + let tag_value = if tag { "1" } else { "0" }; + let tagged = self.reg(); + self.lines.push(format!( + "{} = insertvalue {} undef, i1 {}, 0", + tagged, aggregate_ty, tag_value + )); + + let payload_value = match payload { + Some(payload) => self.emit_expr(payload)?, + None => match &expr.ty { + Type::Option(inner) => zero_value_for_type(inner).ok_or_else(|| { + self.unsupported(expr, "option constructor for unsupported payload type") + })?, + _ => return Err(self.unsupported(expr, "option constructor for non-option type")), + }, + }; + let payload_ty = match &expr.ty { + Type::Option(inner) if **inner == Type::I32 => "i32", + Type::Option(inner) if **inner == Type::I64 => "i64", + Type::Option(inner) if **inner == Type::U32 => "i32", + Type::Option(inner) if **inner == Type::U64 => "i64", + Type::Option(inner) if **inner == Type::F64 => "double", + Type::Option(inner) if **inner == Type::Bool => "i1", + Type::Option(inner) if **inner == Type::String => "ptr", + _ => return Err(self.unsupported(expr, "option constructor for non-option type")), + }; + + let with_payload = self.reg(); + self.lines.push(format!( + "{} = insertvalue {} {}, {} {}, 1", + with_payload, aggregate_ty, tagged, payload_ty, payload_value + )); + Ok(with_payload) + } + + fn emit_tagged_i32_aggregate( + &mut self, + expr: &TExpr, + tag: bool, + payload: Option<&TExpr>, + ) -> Result { + let aggregate_ty = self.llvm_type(expr, &expr.ty)?; + let tag_value = if tag { "1" } else { "0" }; + let tagged = self.reg(); + self.lines.push(format!( + "{} = insertvalue {} undef, i1 {}, 0", + tagged, aggregate_ty, tag_value + )); + + let payload_value = match payload { + Some(payload) => self.emit_expr(payload)?, + None => "0".to_string(), + }; + + let with_payload = self.reg(); + self.lines.push(format!( + "{} = insertvalue {} {}, i32 {}, 1", + with_payload, aggregate_ty, tagged, payload_value + )); + Ok(with_payload) + } + + fn emit_enum_variant( + &mut self, + expr: &TExpr, + enum_name: &str, + discriminant: i32, + payload: Option<&TExpr>, + ) -> Result { + let Some(layout) = self.enum_layouts.get(enum_name) else { + return Ok(discriminant.to_string()); + }; + let Some(payload_ty) = layout.payload_ty.as_ref() else { + return Ok(discriminant.to_string()); + }; + + let aggregate_ty = self.llvm_type(expr, &expr.ty)?; + let tagged = self.reg(); + self.lines.push(format!( + "{} = insertvalue {} undef, i32 {}, 0", + tagged, aggregate_ty, discriminant + )); + + let payload_value = match payload { + Some(payload) => self.emit_expr(payload)?, + None => self.enum_payload_zero_value(payload_ty)?, + }; + let payload_llvm_ty = self.enum_payload_llvm_type(payload_ty)?; + let with_payload = self.reg(); + self.lines.push(format!( + "{} = insertvalue {} {}, {} {}, 1", + with_payload, aggregate_ty, tagged, payload_llvm_ty, payload_value + )); + Ok(with_payload) + } + + fn emit_result_aggregate( + &mut self, + expr: &TExpr, + is_ok: bool, + payload: &TExpr, + ) -> Result { + match &expr.ty { + Type::Result(ok, err) if **ok == Type::I32 && **err == Type::I32 => { + self.emit_tagged_i32_aggregate(expr, is_ok, Some(payload)) + } + Type::Result(ok, err) if **ok == Type::I64 && **err == Type::I32 => { + let payload_value = self.emit_expr(payload)?; + if is_ok { + self.emit_i64_result_aggregate(expr, "1", &payload_value, "0") + } else { + self.emit_i64_result_aggregate(expr, "0", "0", &payload_value) + } + } + Type::Result(ok, err) if **ok == Type::U32 && **err == Type::I32 => { + self.emit_tagged_i32_aggregate(expr, is_ok, Some(payload)) + } + Type::Result(ok, err) if **ok == Type::U64 && **err == Type::I32 => { + let payload_value = self.emit_expr(payload)?; + if is_ok { + self.emit_i64_result_aggregate(expr, "1", &payload_value, "0") + } else { + self.emit_i64_result_aggregate(expr, "0", "0", &payload_value) + } + } + Type::Result(ok, err) if **ok == Type::F64 && **err == Type::I32 => { + let payload_value = self.emit_expr(payload)?; + if is_ok { + self.emit_f64_result_aggregate(expr, "1", &payload_value, "0") + } else { + self.emit_f64_result_aggregate(expr, "0", "0.0", &payload_value) + } + } + Type::Result(ok, err) if **ok == Type::Bool && **err == Type::I32 => { + let payload_value = self.emit_expr(payload)?; + if is_ok { + self.emit_bool_result_aggregate(expr, "1", &payload_value, "0") + } else { + self.emit_bool_result_aggregate(expr, "0", "0", &payload_value) + } + } + Type::Result(ok, err) if **ok == Type::String && **err == Type::I32 => { + let payload_value = self.emit_expr(payload)?; + if is_ok { + self.emit_string_result_aggregate(expr, "1", &payload_value, "0") + } else { + self.emit_string_result_aggregate(expr, "0", "null", &payload_value) + } + } + Type::Result(_, _) => Err(self.unsupported(expr, "unsupported result payload types")), + _ => Err(self.unsupported(expr, "result constructor for non-result type")), + } + } + + fn emit_string_result_aggregate( + &mut self, + expr: &TExpr, + tag_value: &str, + ok_value: &str, + err_value: &str, + ) -> Result { + let aggregate_ty = self.llvm_type(expr, &expr.ty)?; + let tagged = self.reg(); + self.lines.push(format!( + "{} = insertvalue {} undef, i1 {}, 0", + tagged, aggregate_ty, tag_value + )); + + let with_ok = self.reg(); + self.lines.push(format!( + "{} = insertvalue {} {}, ptr {}, 1", + with_ok, aggregate_ty, tagged, ok_value + )); + + let with_err = self.reg(); + self.lines.push(format!( + "{} = insertvalue {} {}, i32 {}, 2", + with_err, aggregate_ty, with_ok, err_value + )); + Ok(with_err) + } + + fn emit_i64_result_aggregate( + &mut self, + expr: &TExpr, + tag_value: &str, + ok_value: &str, + err_value: &str, + ) -> Result { + let aggregate_ty = self.llvm_type(expr, &expr.ty)?; + let tagged = self.reg(); + self.lines.push(format!( + "{} = insertvalue {} undef, i1 {}, 0", + tagged, aggregate_ty, tag_value + )); + + let with_ok = self.reg(); + self.lines.push(format!( + "{} = insertvalue {} {}, i64 {}, 1", + with_ok, aggregate_ty, tagged, ok_value + )); + + let with_err = self.reg(); + self.lines.push(format!( + "{} = insertvalue {} {}, i32 {}, 2", + with_err, aggregate_ty, with_ok, err_value + )); + Ok(with_err) + } + + fn emit_f64_result_aggregate( + &mut self, + expr: &TExpr, + tag_value: &str, + ok_value: &str, + err_value: &str, + ) -> Result { + let aggregate_ty = self.llvm_type(expr, &expr.ty)?; + let tagged = self.reg(); + self.lines.push(format!( + "{} = insertvalue {} undef, i1 {}, 0", + tagged, aggregate_ty, tag_value + )); + + let with_ok = self.reg(); + self.lines.push(format!( + "{} = insertvalue {} {}, double {}, 1", + with_ok, aggregate_ty, tagged, ok_value + )); + + let with_err = self.reg(); + self.lines.push(format!( + "{} = insertvalue {} {}, i32 {}, 2", + with_err, aggregate_ty, with_ok, err_value + )); + Ok(with_err) + } + + fn emit_bool_result_aggregate( + &mut self, + expr: &TExpr, + tag_value: &str, + ok_value: &str, + err_value: &str, + ) -> Result { + let aggregate_ty = self.llvm_type(expr, &expr.ty)?; + let tagged = self.reg(); + self.lines.push(format!( + "{} = insertvalue {} undef, i1 {}, 0", + tagged, aggregate_ty, tag_value + )); + + let with_ok = self.reg(); + self.lines.push(format!( + "{} = insertvalue {} {}, i1 {}, 1", + with_ok, aggregate_ty, tagged, ok_value + )); + + let with_err = self.reg(); + self.lines.push(format!( + "{} = insertvalue {} {}, i32 {}, 2", + with_err, aggregate_ty, with_ok, err_value + )); + Ok(with_err) + } + + fn emit_string_result_host_call( + &mut self, + expr: &TExpr, + callee: &str, + arg_values: &str, + ) -> Result { + let payload = self.reg(); + self.lines.push(format!( + "{} = call ptr @{}({})", + payload, callee, arg_values + )); + let is_ok = self.reg(); + self.lines + .push(format!("{} = icmp ne ptr {}, null", is_ok, payload)); + let err_code = self.reg(); + self.lines + .push(format!("{} = select i1 {}, i32 0, i32 1", err_code, is_ok)); + self.emit_string_result_aggregate(expr, &is_ok, &payload, &err_code) + } + + fn emit_i32_result_status_call( + &mut self, + expr: &TExpr, + callee: &str, + arg_values: &str, + ) -> Result { + let status = self.reg(); + self.lines + .push(format!("{} = call i32 @{}({})", status, callee, arg_values)); + let is_ok = self.reg(); + self.lines + .push(format!("{} = icmp eq i32 {}, 0", is_ok, status)); + self.emit_tagged_i32_aggregate_from_values(expr, &is_ok, &status) + } + + fn emit_i32_result_encoded_i64_call( + &mut self, + expr: &TExpr, + callee: &str, + arg_values: &str, + ) -> Result { + let encoded = self.reg(); + self.lines.push(format!( + "{} = call i64 @{}({})", + encoded, callee, arg_values + )); + let status64 = self.reg(); + self.lines + .push(format!("{} = lshr i64 {}, 32", status64, encoded)); + let status = self.reg(); + self.lines + .push(format!("{} = trunc i64 {} to i32", status, status64)); + let is_ok = self.reg(); + self.lines + .push(format!("{} = icmp eq i32 {}, 0", is_ok, status)); + let payload = self.reg(); + self.lines + .push(format!("{} = trunc i64 {} to i32", payload, encoded)); + self.emit_tagged_i32_aggregate_from_values(expr, &is_ok, &payload) + } + + fn emit_i64_result_out_param_call( + &mut self, + expr: &TExpr, + callee: &str, + arg_values: &str, + ) -> Result { + let out_ptr = self.reg(); + self.lines.push(format!("{} = alloca i64", out_ptr)); + self.lines.push(format!("store i64 0, ptr {}", out_ptr)); + let status = self.reg(); + self.lines.push(format!( + "{} = call i32 @{}({}, ptr {})", + status, callee, arg_values, out_ptr + )); + let is_ok = self.reg(); + self.lines + .push(format!("{} = icmp eq i32 {}, 0", is_ok, status)); + let ok_payload = self.reg(); + self.lines + .push(format!("{} = load i64, ptr {}", ok_payload, out_ptr)); + let err_payload = self.reg(); + self.lines.push(format!( + "{} = select i1 {}, i32 0, i32 1", + err_payload, is_ok + )); + self.emit_i64_result_aggregate(expr, &is_ok, &ok_payload, &err_payload) + } + + fn emit_f64_result_out_param_call( + &mut self, + expr: &TExpr, + callee: &str, + arg_values: &str, + ) -> Result { + let out_ptr = self.reg(); + self.lines.push(format!("{} = alloca double", out_ptr)); + self.lines + .push(format!("store double 0.0, ptr {}", out_ptr)); + let status = self.reg(); + self.lines.push(format!( + "{} = call i32 @{}({}, ptr {})", + status, callee, arg_values, out_ptr + )); + let is_ok = self.reg(); + self.lines + .push(format!("{} = icmp eq i32 {}, 0", is_ok, status)); + let ok_payload = self.reg(); + self.lines + .push(format!("{} = load double, ptr {}", ok_payload, out_ptr)); + let err_payload = self.reg(); + self.lines.push(format!( + "{} = select i1 {}, i32 0, i32 1", + err_payload, is_ok + )); + self.emit_f64_result_aggregate(expr, &is_ok, &ok_payload, &err_payload) + } + + fn emit_bool_result_out_param_call( + &mut self, + expr: &TExpr, + callee: &str, + arg_values: &str, + ) -> Result { + let out_ptr = self.reg(); + self.lines.push(format!("{} = alloca i1", out_ptr)); + self.lines.push(format!("store i1 0, ptr {}", out_ptr)); + let status = self.reg(); + self.lines.push(format!( + "{} = call i32 @{}({}, ptr {})", + status, callee, arg_values, out_ptr + )); + let is_ok = self.reg(); + self.lines + .push(format!("{} = icmp eq i32 {}, 0", is_ok, status)); + let ok_payload = self.reg(); + self.lines + .push(format!("{} = load i1, ptr {}", ok_payload, out_ptr)); + let err_payload = self.reg(); + self.lines.push(format!( + "{} = select i1 {}, i32 0, i32 1", + err_payload, is_ok + )); + self.emit_bool_result_aggregate(expr, &is_ok, &ok_payload, &err_payload) + } + + fn emit_tagged_i32_aggregate_from_values( + &mut self, + expr: &TExpr, + tag_value: &str, + payload_value: &str, + ) -> Result { + let aggregate_ty = self.llvm_type(expr, &expr.ty)?; + let tagged = self.reg(); + self.lines.push(format!( + "{} = insertvalue {} undef, i1 {}, 0", + tagged, aggregate_ty, tag_value + )); + + let with_payload = self.reg(); + self.lines.push(format!( + "{} = insertvalue {} {}, i32 {}, 1", + with_payload, aggregate_ty, tagged, payload_value + )); + Ok(with_payload) + } + + fn emit_tag_observer( + &mut self, + _expr: &TExpr, + value: &TExpr, + invert: bool, + ) -> Result { + let aggregate = self.emit_expr(value)?; + let aggregate_ty = self.llvm_type(value, &value.ty)?; + let tag = self.reg(); + self.lines.push(format!( + "{} = extractvalue {} {}, 0", + tag, aggregate_ty, aggregate + )); + + if invert { + let inverted = self.reg(); + self.lines + .push(format!("{} = xor i1 {}, true", inverted, tag)); + Ok(inverted) + } else { + Ok(tag) + } + } + + fn emit_tag_checked_payload_access( + &mut self, + _expr: &TExpr, + value: &TExpr, + expected_tag: bool, + trap_fn: &str, + ) -> Result { + let aggregate = self.emit_expr(value)?; + let aggregate_ty = self.llvm_type(value, &value.ty)?; + let tag = self.reg(); + self.lines.push(format!( + "{} = extractvalue {} {}, 0", + tag, aggregate_ty, aggregate + )); + + let valid = if expected_tag { + tag + } else { + let valid = self.reg(); + self.lines + .push(format!("{} = icmp eq i1 {}, 0", valid, tag)); + valid + }; + + let ok_block = self.block("unwrap.ok"); + let trap_block = self.block("unwrap.trap"); + self.lines.push(format!( + "br i1 {}, label %{}, label %{}", + valid, ok_block, trap_block + )); + + self.label(&trap_block); + self.lines.push(format!("call void @{}()", trap_fn)); + self.lines.push("unreachable".to_string()); + + self.label(&ok_block); + let payload_index = result_payload_index(&value.ty, expected_tag) + .ok_or_else(|| self.unsupported(value, "unsupported result payload access"))?; + let payload = self.reg(); + self.lines.push(format!( + "{} = extractvalue {} {}, {}", + payload, aggregate_ty, aggregate, payload_index + )); + Ok(payload) + } + + fn emit_match( + &mut self, + expr: &TExpr, + subject: &TExpr, + arms: &[TMatchArm], + ) -> Result { + if matches!(subject.ty, Type::Named(_)) { + return self.emit_enum_match(expr, subject, arms); + } + + let subject_value = self.emit_expr(subject)?; + let aggregate_ty = self.llvm_type(subject, &subject.ty)?; + let tag = self.reg(); + self.lines.push(format!( + "{} = extractvalue {} {}, 0", + tag, aggregate_ty, subject_value + )); + + let (truthy_kind, falsy_kind, truthy_prefix, falsy_prefix) = match &subject.ty { + Type::Option(inner) + if **inner == Type::I32 + || **inner == Type::I64 + || **inner == Type::U32 + || **inner == Type::U64 + || **inner == Type::F64 + || **inner == Type::Bool + || **inner == Type::String => + { + ( + MatchPatternKind::Some, + MatchPatternKind::None, + "match.some", + "match.none", + ) + } + Type::Result(ok, err) if result_type_supported(ok, err) => ( + MatchPatternKind::Ok, + MatchPatternKind::Err, + "match.ok", + "match.err", + ), + _ => { + return Err(self.unsupported( + expr, + "match subject types outside concrete option/result families", + )) + } + }; + + let truthy_arm = arms + .iter() + .find(|arm| arm.pattern == truthy_kind) + .ok_or_else(|| self.unsupported(expr, "non-exhaustive match lowering"))?; + let falsy_arm = arms + .iter() + .find(|arm| arm.pattern == falsy_kind) + .ok_or_else(|| self.unsupported(expr, "non-exhaustive match lowering"))?; + + let truthy_block = self.block(truthy_prefix); + let falsy_block = self.block(falsy_prefix); + let end_block = self.block("match.end"); + + self.lines.push(format!( + "br i1 {}, label %{}, label %{}", + tag, truthy_block, falsy_block + )); + + self.label(&truthy_block); + let truthy_value = + self.emit_match_arm(truthy_arm, &subject.ty, &aggregate_ty, &subject_value)?; + let truthy_pred = self.current_block.clone(); + self.lines.push(format!("br label %{}", end_block)); + + self.label(&falsy_block); + let falsy_value = + self.emit_match_arm(falsy_arm, &subject.ty, &aggregate_ty, &subject_value)?; + let falsy_pred = self.current_block.clone(); + self.lines.push(format!("br label %{}", end_block)); + + self.label(&end_block); + + if expr.ty == Type::Unit { + Ok("0".to_string()) + } else { + let reg = self.reg(); + let phi_ty = self.llvm_type(expr, &expr.ty)?; + self.lines.push(format!( + "{} = phi {} [ {}, %{} ], [ {}, %{} ]", + reg, phi_ty, truthy_value, truthy_pred, falsy_value, falsy_pred, + )); + Ok(reg) + } + } + + fn emit_enum_match( + &mut self, + expr: &TExpr, + subject: &TExpr, + arms: &[TMatchArm], + ) -> Result { + let subject_value = self.emit_expr(subject)?; + let subject_llvm_ty = self.llvm_type(subject, &subject.ty)?; + let switch_value = if self.is_payload_enum_type(&subject.ty) { + let tag = self.reg(); + self.lines.push(format!( + "{} = extractvalue {} {}, 0", + tag, subject_llvm_ty, subject_value + )); + tag + } else { + subject_value.clone() + }; + let end_block = self.block("match.end"); + let default_block = self.block("match.invalid"); + let mut arm_blocks = Vec::new(); + + for arm in arms { + let Some(discriminant) = arm.discriminant else { + return Err(self.unsupported(expr, "enum match arm without discriminant")); + }; + arm_blocks.push((discriminant, self.block("match.enum"), arm)); + } + + let cases = arm_blocks + .iter() + .map(|(discriminant, block, _)| format!("i32 {}, label %{}", discriminant, block)) + .collect::>() + .join("\n "); + self.lines.push(format!( + "switch i32 {}, label %{} [\n {}\n ]", + switch_value, default_block, cases + )); + + self.label(&default_block); + self.lines.push("unreachable".to_string()); + + let mut values = Vec::new(); + for (_, block, arm) in arm_blocks { + self.label(&block); + let value = self.emit_match_arm(arm, &subject.ty, &subject_llvm_ty, &subject_value)?; + let pred = self.current_block.clone(); + self.lines.push(format!("br label %{}", end_block)); + values.push((value, pred)); + } + + self.label(&end_block); + if expr.ty == Type::Unit { + Ok("0".to_string()) + } else { + let reg = self.reg(); + let phi_ty = self.llvm_type(expr, &expr.ty)?; + let incoming = values + .into_iter() + .map(|(value, pred)| format!("[ {}, %{} ]", value, pred)) + .collect::>() + .join(", "); + self.lines + .push(format!("{} = phi {} {}", reg, phi_ty, incoming)); + Ok(reg) + } + } + + fn emit_match_arm( + &mut self, + arm: &TMatchArm, + subject_ty: &Type, + aggregate_ty: &str, + subject_value: &str, + ) -> Result { + let saved_locals = self.locals.clone(); + + if let Some(binding) = &arm.binding { + let payload_index = match subject_ty { + Type::Named(name) + if self + .enum_layouts + .get(name) + .and_then(|layout| layout.payload_ty.as_ref()) + .is_some() => + { + Some(1) + } + Type::Option(_) => Some(1), + Type::Result(_, _) => { + result_payload_index(subject_ty, matches!(arm.pattern, MatchPatternKind::Ok)) + } + _ => None, + } + .ok_or_else(|| { + Diagnostic::new( + &self.file, + "UnsupportedBackendFeature", + "backend does not support this match payload binding", + ) + })?; + let payload = self.reg(); + self.lines.push(format!( + "{} = extractvalue {} {}, {}", + payload, aggregate_ty, subject_value, payload_index + )); + self.locals + .insert(binding.clone(), LocalValue::Value(payload)); + } + + let mut value = "0".to_string(); + for expr in &arm.body { + value = self.emit_expr(expr)?; + } + + self.locals = saved_locals; + Ok(value) + } + + fn emit_struct_init( + &mut self, + expr: &TExpr, + _name: &str, + fields: &[(String, TExpr)], + ) -> Result { + let aggregate_ty = self.llvm_type(expr, &expr.ty)?; + let mut aggregate = "undef".to_string(); + + for (index, (_, field_expr)) in fields.iter().enumerate() { + let value = self.emit_expr(field_expr)?; + let field_ty = self.llvm_type(field_expr, &field_expr.ty)?; + let with_field = self.reg(); + self.lines.push(format!( + "{} = insertvalue {} {}, {} {}, {}", + with_field, aggregate_ty, aggregate, field_ty, value, index + )); + aggregate = with_field; + } + + Ok(aggregate) + } + + fn emit_array_init(&mut self, expr: &TExpr, elements: &[TExpr]) -> Result { + let aggregate_ty = self.llvm_type(expr, &expr.ty)?; + let mut aggregate = "undef".to_string(); + + for (index, element) in elements.iter().enumerate() { + let value = self.emit_expr(element)?; + let element_ty = self.llvm_type(element, &element.ty)?; + let with_element = self.reg(); + self.lines.push(format!( + "{} = insertvalue {} {}, {} {}, {}", + with_element, aggregate_ty, aggregate, element_ty, value, index + )); + aggregate = with_element; + } + + Ok(aggregate) + } + + fn emit_ssa_local(&mut self, name: &str, initializer: &TExpr) -> Result { + let value = self.emit_expr(initializer)?; + self.locals + .insert(name.to_string(), LocalValue::Value(value)); + Ok("0".to_string()) + } + + fn emit_value_local(&mut self, name: &str, initializer: &TExpr) -> Result { + let value = self.emit_expr(initializer)?; + let llvm_ty = self.llvm_type(initializer, &initializer.ty)?; + let ptr = self.local_addr(name); + self.lines.push(format!("{} = alloca {}", ptr, llvm_ty)); + self.lines + .push(format!("store {} {}, ptr {}", llvm_ty, value, ptr)); + self.locals.insert( + name.to_string(), + LocalValue::Ptr { + ptr, + ty: initializer.ty.clone(), + }, + ); + Ok("0".to_string()) + } + + fn emit_array_local( + &mut self, + _expr: &TExpr, + name: &str, + initializer: &TExpr, + ) -> Result { + let Type::Array(inner, len) = &initializer.ty else { + return Err(self.unsupported(initializer, "unsupported array locals")); + }; + let llvm_ty = llvm_fixed_array_type(self.layouts, self.enum_layouts, inner, *len) + .ok_or_else(|| self.unsupported(initializer, "unsupported array locals"))?; + let ptr = self.local_addr(name); + self.lines.push(format!("{} = alloca {}", ptr, llvm_ty)); + + if let TExprKind::ArrayInit { elements } = &initializer.kind { + for (index, element) in elements.iter().enumerate() { + let value = self.emit_expr(element)?; + let element_ty = self.llvm_type(element, &element.ty)?; + let element_ptr = self.reg(); + self.lines.push(format!( + "{} = getelementptr inbounds {}, ptr {}, i32 0, i32 {}", + element_ptr, llvm_ty, ptr, index + )); + self.lines.push(format!( + "store {} {}, ptr {}", + element_ty, value, element_ptr + )); + } + } else { + let value = self.emit_expr(initializer)?; + self.lines + .push(format!("store {} {}, ptr {}", llvm_ty, value, ptr)); + } + + self.locals.insert( + name.to_string(), + LocalValue::Array { + ptr, + ty: initializer.ty.clone(), + }, + ); + Ok("0".to_string()) + } + + fn emit_index( + &mut self, + expr: &TExpr, + array: &TExpr, + index: &TExpr, + ) -> Result { + if let (TExprKind::ArrayInit { elements }, TExprKind::Int(index)) = + (&array.kind, &index.kind) + { + let index = usize::try_from(*index) + .map_err(|_| self.unsupported(expr, "negative array indices"))?; + if index >= elements.len() { + return Err(self.unsupported(expr, "out-of-bounds array indices")); + } + + let aggregate = self.emit_expr(array)?; + let aggregate_ty = self.llvm_type(array, &array.ty)?; + let value = self.reg(); + self.lines.push(format!( + "{} = extractvalue {} {}, {}", + value, aggregate_ty, aggregate, index + )); + return Ok(value); + } + + let Type::Array(inner, len) = &array.ty else { + return Err(self.unsupported(expr, "indexing non-array values")); + }; + let llvm_array_ty = llvm_fixed_array_type(self.layouts, self.enum_layouts, inner, *len) + .ok_or_else(|| self.unsupported(expr, "unsupported fixed array indexing"))?; + let element_ty = self.llvm_type(expr, inner)?; + + let (array_ptr, len) = self.emit_array_address(expr, array)?; + let index_value = self.emit_expr(index)?; + self.emit_array_bounds_check(&index_value, len); + + let element_ptr = self.reg(); + self.lines.push(format!( + "{} = getelementptr inbounds {}, ptr {}, i32 0, i32 {}", + element_ptr, llvm_array_ty, array_ptr, index_value + )); + let value = self.reg(); + self.lines.push(format!( + "{} = load {}, ptr {}", + value, element_ty, element_ptr + )); + Ok(value) + } + + fn emit_array_address( + &mut self, + _expr: &TExpr, + array: &TExpr, + ) -> Result<(String, usize), Diagnostic> { + if let TExprKind::Var(name) = &array.kind { + if let Some(LocalValue::Array { ptr, ty }) = self.locals.get(name).cloned() { + let Type::Array(_, len) = ty else { + return Err(self.unsupported(array, "unsupported fixed array locals")); + }; + return Ok((ptr, len)); + } + } + + let Type::Array(inner, len) = &array.ty else { + return Err(self.unsupported(array, "unsupported fixed array values")); + }; + let value = self.emit_expr(array)?; + let llvm_ty = llvm_fixed_array_type(self.layouts, self.enum_layouts, inner, *len) + .ok_or_else(|| self.unsupported(array, "unsupported fixed array values"))?; + let ptr = self.local_addr("array.tmp"); + self.lines.push(format!("{} = alloca {}", ptr, llvm_ty)); + self.lines + .push(format!("store {} {}, ptr {}", llvm_ty, value, ptr)); + Ok((ptr, *len)) + } + + fn emit_array_bounds_check(&mut self, index_value: &str, len: usize) { + let lower_ok = self.reg(); + self.lines + .push(format!("{} = icmp sge i32 {}, 0", lower_ok, index_value)); + let upper_ok = self.reg(); + self.lines.push(format!( + "{} = icmp slt i32 {}, {}", + upper_ok, index_value, len + )); + let in_bounds = self.reg(); + self.lines + .push(format!("{} = and i1 {}, {}", in_bounds, lower_ok, upper_ok)); + + let ok_block = self.block("array.index.ok"); + let trap_block = self.block("array.index.trap"); + self.lines.push(format!( + "br i1 {}, label %{}, label %{}", + in_bounds, ok_block, trap_block + )); + + self.label(&trap_block); + self.lines + .push("call void @__glagol_array_bounds_trap()".to_string()); + self.lines.push("unreachable".to_string()); + + self.label(&ok_block); + } + + fn emit_field_access( + &mut self, + expr: &TExpr, + value: &TExpr, + field: &str, + ) -> Result { + let Type::Named(struct_name) = &value.ty else { + return Err(self.unsupported(expr, "field access on non-struct values")); + }; + + let Some(layout) = self.layouts.get(struct_name) else { + return Err(self.unsupported(expr, "unknown struct layouts")); + }; + + let Some((index, (_, field_ty))) = layout + .fields + .iter() + .enumerate() + .find(|(_, (name, _))| name == field) + else { + return Err(self.unsupported(expr, "unknown struct fields")); + }; + + let aggregate = self.emit_expr(value)?; + let aggregate_ty = self.llvm_type(value, &value.ty)?; + let _field_ty = self.llvm_type(expr, field_ty)?; + let result = self.reg(); + self.lines.push(format!( + "{} = extractvalue {} {}, {}", + result, aggregate_ty, aggregate, index + )); + Ok(result) + } +} diff --git a/compiler/src/lower.rs b/compiler/src/lower.rs new file mode 100644 index 0000000..517c580 --- /dev/null +++ b/compiler/src/lower.rs @@ -0,0 +1,2349 @@ +use std::collections::hash_map::Entry; +use std::collections::{HashMap, HashSet}; + +use crate::{ + ast::{ + BinaryOp, CImportDecl, EnumDecl, EnumVariantDecl, Expr, ExprKind, Function, MatchArm, + MatchPattern, MatchPatternKind, Param, Program, StructDecl, StructField, StructInitField, + Test, + }, + diag::Diagnostic, + sexpr::{Atom, SExpr, SExprKind}, + token::Span, + types::Type, +}; + +pub fn lower_program(file: &str, forms: &[SExpr]) -> Result> { + lower_program_with_imported_names(file, forms, &[]) +} + +pub fn lower_program_with_imported_names( + file: &str, + forms: &[SExpr], + imported_names: &[String], +) -> Result> { + let mut module = None; + let mut enums = Vec::new(); + let mut enum_names = imported_names.iter().cloned().collect::>(); + let mut structs = Vec::new(); + let mut struct_names = HashSet::new(); + let mut c_imports = Vec::new(); + let mut functions = Vec::new(); + let mut tests = Vec::new(); + let mut test_names = HashMap::new(); + let mut errors = Vec::new(); + + for form in forms { + match list_head(form) { + Some("enum") => match lower_enum(file, form) { + Ok(enum_decl) => { + enum_names.insert(enum_decl.name.clone()); + enums.push(enum_decl); + } + Err(mut errs) => errors.append(&mut errs), + }, + Some("struct") => match lower_struct(file, form) { + Ok(struct_decl) => { + struct_names.insert(struct_decl.name.clone()); + structs.push(struct_decl); + } + Err(mut errs) => errors.append(&mut errs), + }, + _ => {} + } + } + + for form in forms { + match list_head(form) { + Some("module") => match lower_module(file, form) { + Ok(name) => module = Some(name), + Err(err) => errors.push(err), + }, + Some("enum") => {} + Some("struct") => {} + Some("import_c") => match lower_c_import(file, form) { + Ok(import) => c_imports.push(import), + Err(mut errs) => errors.append(&mut errs), + }, + Some("fn") => match lower_function(file, form, &struct_names, &enum_names) { + Ok(function) => functions.push(function), + Err(mut errs) => errors.append(&mut errs), + }, + Some("test") => match lower_test(file, form, &struct_names, &enum_names) { + Ok(test) => match test_names.entry(test.name.clone()) { + Entry::Vacant(entry) => { + entry.insert(test.name_span); + tests.push(test); + } + Entry::Occupied(entry) => errors.push( + Diagnostic::new( + file, + "DuplicateTestName", + format!("duplicate test name `{}`", test.name), + ) + .with_span(test.name_span) + .hint("test names must be unique within a module") + .related("original test name", *entry.get()), + ), + }, + Err(mut errs) => errors.append(&mut errs), + }, + Some(other) => errors.push( + Diagnostic::new( + file, + "UnknownTopLevelForm", + format!("unknown top-level form `{}`", other), + ) + .with_span(form.span), + ), + None => errors.push( + Diagnostic::new(file, "ExpectedTopLevelForm", "expected top-level form") + .with_span(form.span), + ), + } + } + + if errors.is_empty() { + Ok(Program { + module: module.unwrap_or_else(|| "main".to_string()), + enums, + structs, + c_imports, + functions, + tests, + }) + } else { + Err(errors) + } +} + +pub fn print_program(program: &Program) -> String { + let mut output = String::new(); + + output.push_str("program "); + output.push_str(&program.module); + output.push('\n'); + + for enum_decl in &program.enums { + output.push_str(" enum "); + output.push_str(&enum_decl.name); + output.push('\n'); + for variant in &enum_decl.variants { + output.push_str(" variant "); + output.push_str(&variant.name); + if let Some(payload_ty) = &variant.payload_ty { + output.push(' '); + output.push_str(&payload_ty.to_string()); + } + output.push('\n'); + } + } + + for struct_decl in &program.structs { + output.push_str(" struct "); + output.push_str(&struct_decl.name); + output.push('\n'); + for field in &struct_decl.fields { + output.push_str(" field "); + output.push_str(&field.name); + output.push_str(": "); + output.push_str(&field.ty.to_string()); + output.push('\n'); + } + } + + for import in &program.c_imports { + output.push_str(" import_c "); + output.push_str(&import.name); + output.push('('); + for (index, param) in import.params.iter().enumerate() { + if index > 0 { + output.push_str(", "); + } + output.push_str(¶m.name); + output.push_str(": "); + output.push_str(¶m.ty.to_string()); + } + output.push_str(") -> "); + output.push_str(&import.return_type.to_string()); + output.push('\n'); + } + + for function in &program.functions { + output.push_str(" fn "); + output.push_str(&function.name); + output.push('('); + for (index, param) in function.params.iter().enumerate() { + if index > 0 { + output.push_str(", "); + } + output.push_str(¶m.name); + output.push_str(": "); + output.push_str(¶m.ty.to_string()); + } + output.push_str(") -> "); + output.push_str(&function.return_type.to_string()); + output.push('\n'); + + for expr in &function.body { + write_expr(expr, 2, &mut output); + } + } + + for test in &program.tests { + output.push_str(" test \""); + for ch in test.name.chars() { + output.extend(ch.escape_default()); + } + output.push_str("\"\n"); + for expr in &test.body { + write_expr(expr, 2, &mut output); + } + } + + output +} + +fn write_expr(expr: &Expr, indent: usize, output: &mut String) { + output.push_str(&" ".repeat(indent)); + + match &expr.kind { + ExprKind::Int(value) => { + output.push_str("int "); + output.push_str(&value.to_string()); + output.push('\n'); + } + ExprKind::Int64(value) => { + output.push_str("i64 "); + output.push_str(&value.to_string()); + output.push('\n'); + } + ExprKind::UInt32(value) => { + output.push_str("u32 "); + output.push_str(&value.to_string()); + output.push('\n'); + } + ExprKind::UInt64(value) => { + output.push_str("u64 "); + output.push_str(&value.to_string()); + output.push('\n'); + } + ExprKind::Float(value) => { + output.push_str("float "); + output.push_str(&value.to_string()); + output.push('\n'); + } + ExprKind::Bool(value) => { + output.push_str("bool "); + output.push_str(&value.to_string()); + output.push('\n'); + } + ExprKind::String(value) => { + output.push_str("string \""); + for ch in value.chars() { + output.extend(ch.escape_default()); + } + output.push_str("\"\n"); + } + ExprKind::Var(name) => { + output.push_str("var "); + output.push_str(name); + output.push('\n'); + } + ExprKind::StructInit { name, fields, .. } => { + output.push_str("construct "); + output.push_str(name); + output.push('\n'); + for field in fields { + output.push_str(&" ".repeat(indent + 1)); + output.push_str("field "); + output.push_str(&field.name); + output.push('\n'); + write_expr(&field.expr, indent + 2, output); + } + } + ExprKind::ArrayInit { + elem_ty, elements, .. + } => { + output.push_str("array "); + output.push_str(&elem_ty.to_string()); + output.push('\n'); + for element in elements { + write_expr(element, indent + 1, output); + } + } + ExprKind::OptionSome { + payload_ty, value, .. + } => { + output.push_str("some "); + output.push_str(&payload_ty.to_string()); + output.push('\n'); + write_expr(value, indent + 1, output); + } + ExprKind::OptionNone { payload_ty, .. } => { + output.push_str("none "); + output.push_str(&payload_ty.to_string()); + output.push('\n'); + } + ExprKind::ResultOk { + ok_ty, + err_ty, + value, + .. + } => { + output.push_str("ok "); + output.push_str(&ok_ty.to_string()); + output.push(' '); + output.push_str(&err_ty.to_string()); + output.push('\n'); + write_expr(value, indent + 1, output); + } + ExprKind::ResultErr { + ok_ty, + err_ty, + value, + .. + } => { + output.push_str("err "); + output.push_str(&ok_ty.to_string()); + output.push(' '); + output.push_str(&err_ty.to_string()); + output.push('\n'); + write_expr(value, indent + 1, output); + } + ExprKind::OptionIsSome { value } => { + output.push_str("is_some\n"); + write_expr(value, indent + 1, output); + } + ExprKind::OptionIsNone { value } => { + output.push_str("is_none\n"); + write_expr(value, indent + 1, output); + } + ExprKind::OptionUnwrapSome { value } => { + output.push_str("unwrap_some\n"); + write_expr(value, indent + 1, output); + } + ExprKind::ResultIsOk { source_name, value } => { + output.push_str(source_name); + output.push('\n'); + write_expr(value, indent + 1, output); + } + ExprKind::ResultIsErr { source_name, value } => { + output.push_str(source_name); + output.push('\n'); + write_expr(value, indent + 1, output); + } + ExprKind::ResultUnwrapOk { source_name, value } => { + output.push_str(source_name); + output.push('\n'); + write_expr(value, indent + 1, output); + } + ExprKind::ResultUnwrapErr { source_name, value } => { + output.push_str(source_name); + output.push('\n'); + write_expr(value, indent + 1, output); + } + ExprKind::EnumVariant { + enum_name, + variant, + args, + .. + } => { + output.push_str("enum-variant "); + output.push_str(enum_name); + output.push('.'); + output.push_str(variant); + output.push('\n'); + for arg in args { + write_expr(arg, indent + 1, output); + } + } + ExprKind::FieldAccess { value, field, .. } => { + output.push_str("field-access "); + output.push_str(field); + output.push('\n'); + write_expr(value, indent + 1, output); + } + ExprKind::Index { array, index } => { + output.push_str("index\n"); + write_expr(array, indent + 1, output); + write_expr(index, indent + 1, output); + } + ExprKind::Local { + mutable, + name, + ty, + expr, + .. + } => { + output.push_str(if *mutable { "local var " } else { "local let " }); + output.push_str(name); + output.push_str(": "); + output.push_str(&ty.to_string()); + output.push('\n'); + write_expr(expr, indent + 1, output); + } + ExprKind::Set { name, expr, .. } => { + output.push_str("set "); + output.push_str(name); + output.push('\n'); + write_expr(expr, indent + 1, output); + } + ExprKind::Binary { op, left, right } => { + output.push_str("binary "); + output.push_str(binary_op_name(*op)); + output.push('\n'); + write_expr(left, indent + 1, output); + write_expr(right, indent + 1, output); + } + ExprKind::If { + condition, + then_expr, + else_expr, + } => { + output.push_str("if\n"); + write_expr(condition, indent + 1, output); + write_expr(then_expr, indent + 1, output); + write_expr(else_expr, indent + 1, output); + } + ExprKind::Match { subject, arms } => { + output.push_str("match\n"); + output.push_str(&" ".repeat(indent + 1)); + output.push_str("subject\n"); + write_expr(subject, indent + 2, output); + for arm in arms { + output.push_str(&" ".repeat(indent + 1)); + output.push_str("arm "); + output.push_str(&match_pattern_name(&arm.pattern.kind)); + if let Some(binding) = &arm.pattern.binding { + output.push(' '); + output.push_str(binding); + } + output.push('\n'); + for expr in &arm.body { + write_expr(expr, indent + 2, output); + } + } + } + ExprKind::While { condition, body } => { + output.push_str("while\n"); + write_expr(condition, indent + 1, output); + for expr in body { + write_expr(expr, indent + 1, output); + } + } + ExprKind::Unsafe { body } => { + output.push_str("unsafe\n"); + for expr in body { + write_expr(expr, indent + 1, output); + } + } + ExprKind::Call { name, args, .. } => { + output.push_str("call "); + output.push_str(name); + output.push('\n'); + for arg in args { + write_expr(arg, indent + 1, output); + } + } + } +} + +pub fn match_pattern_name(kind: &MatchPatternKind) -> String { + match kind { + MatchPatternKind::Some => "some".to_string(), + MatchPatternKind::None => "none".to_string(), + MatchPatternKind::Ok => "ok".to_string(), + MatchPatternKind::Err => "err".to_string(), + MatchPatternKind::EnumVariant { enum_name, variant } => { + format!("{}.{}", enum_name, variant) + } + } +} + +pub fn binary_op_name(op: BinaryOp) -> &'static str { + match op { + BinaryOp::Add => "+", + BinaryOp::Sub => "-", + BinaryOp::Mul => "*", + BinaryOp::Div => "/", + BinaryOp::Rem => "%", + BinaryOp::BitAnd => "bit_and", + BinaryOp::BitOr => "bit_or", + BinaryOp::BitXor => "bit_xor", + BinaryOp::Eq => "=", + BinaryOp::Lt => "<", + BinaryOp::Gt => ">", + BinaryOp::Le => "<=", + BinaryOp::Ge => ">=", + } +} + +fn lower_module(file: &str, form: &SExpr) -> Result { + let items = expect_list(form).ok_or_else(|| { + Diagnostic::new(file, "ExpectedList", "expected module list").with_span(form.span) + })?; + + if items.len() != 2 && items.len() != 3 { + return Err(Diagnostic::new( + file, + "InvalidModule", + "module form must be `(module name)` or `(module name (export ...))`", + ) + .with_span(form.span)); + } + + if let Some(export_form) = items.get(2) { + let export_items = expect_list(export_form).ok_or_else(|| { + Diagnostic::new( + file, + "InvalidExport", + "export list must be `(export name...)`", + ) + .with_span(export_form.span) + })?; + if !matches!(export_items.first().and_then(expect_ident), Some("export")) { + return Err(Diagnostic::new( + file, + "InvalidExport", + "module option must be an export list", + ) + .with_span(export_form.span)); + } + let mut seen = HashMap::new(); + for item in &export_items[1..] { + let Some(name) = expect_ident(item) else { + return Err(Diagnostic::new( + file, + "InvalidExport", + "exported name must be an identifier", + ) + .with_span(item.span)); + }; + if seen.insert(name.to_string(), item.span).is_some() { + return Err(Diagnostic::new( + file, + "DuplicateName", + format!("duplicate exported name `{}`", name), + ) + .with_span(item.span)); + } + } + } + + expect_ident(&items[1]) + .map(|s| s.to_string()) + .ok_or_else(|| { + Diagnostic::new( + file, + "InvalidModuleName", + "module name must be an identifier", + ) + .with_span(items[1].span) + }) +} + +fn lower_struct(file: &str, form: &SExpr) -> Result> { + let mut errors = Vec::new(); + let Some(items) = expect_list(form) else { + return Err(vec![Diagnostic::new( + file, + "ExpectedList", + "expected struct list", + ) + .with_span(form.span)]); + }; + + if items.len() < 2 { + return Err(vec![Diagnostic::new( + file, + "MalformedStructForm", + "struct form must be `(struct Name (field type)...)`", + ) + .with_span(form.span)]); + } + + let name = match expect_ident(&items[1]) { + Some(name) => name.to_string(), + None => { + errors.push( + Diagnostic::new( + file, + "InvalidStructName", + "struct name must be an identifier", + ) + .with_span(items[1].span), + ); + "".to_string() + } + }; + + let mut fields = Vec::new(); + for item in &items[2..] { + let Some(pair) = expect_list(item) else { + errors.push( + Diagnostic::new( + file, + "InvalidStructField", + "struct field must be `(name type)`", + ) + .with_span(item.span), + ); + continue; + }; + + if pair.len() != 2 { + errors.push( + Diagnostic::new( + file, + "InvalidStructField", + "struct field must be `(name type)`", + ) + .with_span(item.span), + ); + continue; + } + + let Some(field_name) = expect_ident(&pair[0]) else { + errors.push( + Diagnostic::new( + file, + "InvalidStructFieldName", + "struct field name must be an identifier", + ) + .with_span(pair[0].span), + ); + continue; + }; + + let Some(ty) = lower_type(&pair[1]) else { + errors.push( + Diagnostic::new(file, "InvalidStructFieldType", "invalid struct field type") + .with_span(pair[1].span), + ); + continue; + }; + + fields.push(StructField { + name: field_name.to_string(), + name_span: pair[0].span, + ty, + ty_span: pair[1].span, + }); + } + + if errors.is_empty() { + Ok(StructDecl { + name, + name_span: items[1].span, + fields, + span: form.span, + }) + } else { + Err(errors) + } +} + +fn lower_enum(file: &str, form: &SExpr) -> Result> { + let mut errors = Vec::new(); + let Some(items) = expect_list(form) else { + return Err(vec![Diagnostic::new( + file, + "ExpectedList", + "expected enum list", + ) + .with_span(form.span)]); + }; + + if items.len() < 2 { + return Err(vec![Diagnostic::new( + file, + "MalformedEnumForm", + "enum form must be `(enum Name Variant...)`", + ) + .with_span(form.span)]); + } + + let name = match expect_ident(&items[1]) { + Some(name) => name.to_string(), + None => { + errors.push( + Diagnostic::new(file, "InvalidEnumName", "enum name must be an identifier") + .with_span(items[1].span), + ); + "".to_string() + } + }; + + let mut variants = Vec::new(); + for item in &items[2..] { + match expect_ident(item) { + Some(name) => variants.push(EnumVariantDecl { + name: name.to_string(), + name_span: item.span, + payload_ty: None, + payload_ty_span: None, + }), + None => { + let Some(variant_items) = expect_list(item) else { + errors.push( + Diagnostic::new( + file, + "InvalidEnumVariant", + "enum variants must be identifiers or unary payload forms", + ) + .with_span(item.span) + .expected("Variant or (Variant i32)"), + ); + continue; + }; + + if variant_items.len() != 2 { + errors.push( + Diagnostic::new( + file, + "InvalidEnumVariant", + "enum payload variants must be unary forms", + ) + .with_span(item.span) + .expected("(Variant i32)"), + ); + continue; + } + + let Some(name) = expect_ident(&variant_items[0]) else { + errors.push( + Diagnostic::new( + file, + "InvalidEnumVariant", + "enum variant name must be an identifier", + ) + .with_span(variant_items[0].span), + ); + continue; + }; + + let Some(payload_ty) = lower_type(&variant_items[1]) else { + errors.push( + Diagnostic::new( + file, + "InvalidEnumVariant", + "enum variant payload type is invalid", + ) + .with_span(variant_items[1].span) + .expected("i32"), + ); + continue; + }; + + variants.push(EnumVariantDecl { + name: name.to_string(), + name_span: variant_items[0].span, + payload_ty: Some(payload_ty), + payload_ty_span: Some(variant_items[1].span), + }); + } + } + } + + if errors.is_empty() { + Ok(EnumDecl { + name, + name_span: items[1].span, + variants, + span: form.span, + }) + } else { + Err(errors) + } +} + +fn lower_function( + file: &str, + form: &SExpr, + struct_names: &HashSet, + enum_names: &HashSet, +) -> Result> { + let mut errors = Vec::new(); + let Some(items) = expect_list(form) else { + return Err(vec![Diagnostic::new( + file, + "ExpectedList", + "expected function list", + ) + .with_span(form.span)]); + }; + + if items.len() < 6 { + return Err(vec![Diagnostic::new( + file, + "InvalidFunction", + "function form is incomplete", + ) + .with_span(form.span) + .hint("expected `(fn name ((arg Type) ...) -> ReturnType body...)`")]); + } + + let name = match expect_ident(&items[1]) { + Some(name) => name.to_string(), + None => { + errors.push( + Diagnostic::new( + file, + "InvalidFunctionName", + "function name must be an identifier", + ) + .with_span(items[1].span), + ); + "".to_string() + } + }; + + let params = match lower_params(file, &items[2]) { + Ok(params) => params, + Err(mut errs) => { + errors.append(&mut errs); + Vec::new() + } + }; + + if !matches!(items[3].kind, SExprKind::Atom(Atom::Arrow)) { + errors.push( + Diagnostic::new(file, "ExpectedArrow", "expected `->` in function signature") + .with_span(items[3].span), + ); + } + + let return_type = match lower_type(&items[4]) { + Some(Type::Unit) => { + errors.push(unsupported_unit_return_signature(file, items[4].span)); + Type::Unit + } + Some(ty) => ty, + None => { + errors.push( + Diagnostic::new(file, "InvalidReturnType", "invalid return type") + .with_span(items[4].span), + ); + Type::Unit + } + }; + + let mut body = Vec::new(); + for expr in &items[5..] { + match lower_expr(file, expr, struct_names, enum_names) { + Ok(expr) => body.push(expr), + Err(err) => errors.push(err), + } + } + + if errors.is_empty() { + Ok(Function { + name, + params, + return_type, + return_type_span: items[4].span, + body, + span: form.span, + }) + } else { + Err(errors) + } +} + +fn lower_c_import(file: &str, form: &SExpr) -> Result> { + let mut errors = Vec::new(); + let Some(items) = expect_list(form) else { + return Err(vec![Diagnostic::new( + file, + "MalformedCImport", + "`import_c` declaration must be a list", + ) + .with_span(form.span)]); + }; + + if items.len() != 5 { + return Err(vec![Diagnostic::new( + file, + "MalformedCImport", + "`import_c` form must be `(import_c name ((arg i32)...) -> ReturnType)`", + ) + .with_span(form.span) + .expected("(import_c name ((arg i32)...) -> i32)")]); + } + + let name = match expect_ident(&items[1]) { + Some(name) => name.to_string(), + None => { + errors.push( + Diagnostic::new( + file, + "MalformedCImport", + "C import name must be an identifier", + ) + .with_span(items[1].span), + ); + "".to_string() + } + }; + if name != "" && !is_c_symbol_name(&name) { + errors.push( + Diagnostic::new( + file, + "MalformedCImport", + "C import name must be a C symbol identifier", + ) + .with_span(items[1].span) + .expected("ASCII letter or `_`, followed by ASCII letters, digits, or `_`") + .found(name.clone()), + ); + } + + let params = match lower_c_import_params(file, &items[2]) { + Ok(params) => params, + Err(mut errs) => { + errors.append(&mut errs); + Vec::new() + } + }; + + if !matches!(items[3].kind, SExprKind::Atom(Atom::Arrow)) { + errors.push( + Diagnostic::new( + file, + "MalformedCImport", + "expected `->` in C import signature", + ) + .with_span(items[3].span), + ); + } + + let return_type = match lower_type(&items[4]) { + Some(ty) => ty, + None => { + errors.push( + Diagnostic::new(file, "MalformedCImport", "invalid C import return type") + .with_span(items[4].span), + ); + Type::Unit + } + }; + + for param in ¶ms { + if param.ty != Type::I32 { + errors.push( + Diagnostic::new( + file, + "UnsupportedCImportType", + "C import parameters support only `i32` in exp-6", + ) + .with_span(param.ty_span) + .expected(Type::I32.to_string()) + .found(param.ty.to_string()), + ); + } + } + + if return_type != Type::I32 && return_type != Type::Unit { + errors.push( + Diagnostic::new( + file, + "UnsupportedCImportType", + "C import return type must be `i32` or `unit` in exp-6", + ) + .with_span(items[4].span) + .expected("i32 or unit") + .found(return_type.to_string()), + ); + } + + if errors.is_empty() { + Ok(CImportDecl { + name, + name_span: items[1].span, + params, + return_type, + return_type_span: items[4].span, + span: form.span, + }) + } else { + Err(errors) + } +} + +fn lower_c_import_params(file: &str, form: &SExpr) -> Result, Vec> { + let mut errors = Vec::new(); + let Some(items) = expect_list(form) else { + return Err(vec![Diagnostic::new( + file, + "MalformedCImport", + "C import parameters must be a list", + ) + .with_span(form.span)]); + }; + + let mut params = Vec::new(); + let mut seen = HashMap::::new(); + for item in items { + let Some(pair) = expect_list(item) else { + errors.push( + Diagnostic::new( + file, + "MalformedCImport", + "C import parameter must be `(name Type)`", + ) + .with_span(item.span), + ); + continue; + }; + if pair.len() != 2 { + errors.push( + Diagnostic::new( + file, + "MalformedCImport", + "C import parameter must be `(name Type)`", + ) + .with_span(item.span), + ); + continue; + } + let Some(name) = expect_ident(&pair[0]) else { + errors.push( + Diagnostic::new( + file, + "MalformedCImport", + "C import parameter name must be an identifier", + ) + .with_span(pair[0].span), + ); + continue; + }; + if let Some(original) = seen.insert(name.to_string(), pair[0].span) { + errors.push( + Diagnostic::new( + file, + "DuplicateName", + format!("duplicate C import parameter `{}`", name), + ) + .with_span(pair[0].span) + .related("original parameter", original), + ); + } + let Some(ty) = lower_type(&pair[1]) else { + errors.push( + Diagnostic::new(file, "MalformedCImport", "invalid C import parameter type") + .with_span(pair[1].span), + ); + continue; + }; + params.push(Param { + name: name.to_string(), + name_span: pair[0].span, + ty, + ty_span: pair[1].span, + }); + } + + if errors.is_empty() { + Ok(params) + } else { + Err(errors) + } +} + +fn is_c_symbol_name(value: &str) -> bool { + let mut chars = value.chars(); + let Some(first) = chars.next() else { + return false; + }; + (first == '_' || first.is_ascii_alphabetic()) + && chars.all(|ch| ch == '_' || ch.is_ascii_alphanumeric()) +} + +fn lower_test( + file: &str, + form: &SExpr, + struct_names: &HashSet, + enum_names: &HashSet, +) -> Result> { + let mut errors = Vec::new(); + let Some(items) = expect_list(form) else { + return Err(vec![Diagnostic::new( + file, + "ExpectedList", + "expected test list", + ) + .with_span(form.span)]); + }; + + if items.len() < 3 { + return Err(vec![Diagnostic::new( + file, + "MalformedTestForm", + "test form must be `(test \"name\" body...)`", + ) + .with_span(form.span) + .hint("use one string name followed by a bool result expression")]); + } + + let name = match expect_string(&items[1]) { + Some(name) if valid_test_name(name) => name.to_string(), + Some(_) => { + errors.push( + Diagnostic::new( + file, + "InvalidTestName", + "test name must be non-empty printable ASCII without quotes, backslashes, or newlines", + ) + .with_span(items[1].span) + .expected("non-empty printable ASCII without quotes, backslashes, or newlines"), + ); + "".to_string() + } + None => { + errors.push( + Diagnostic::new( + file, + "InvalidTestName", + "test name must be a string literal", + ) + .with_span(items[1].span) + .expected("string"), + ); + "".to_string() + } + }; + + let mut body = Vec::new(); + for item in &items[2..] { + match lower_expr(file, item, struct_names, enum_names) { + Ok(expr) => body.push(expr), + Err(err) => { + errors.push(err); + } + } + } + + if body.len() > 1 { + for expr in &body[..body.len() - 1] { + if !matches!( + expr.kind, + ExprKind::Local { .. } | ExprKind::Set { .. } | ExprKind::While { .. } + ) { + errors.push( + Diagnostic::new( + file, + "MalformedTestForm", + "test body forms before the final expression must be local declarations, assignments, or while loops", + ) + .with_span(expr.span) + .hint("use `(let name i32 expr)`, `(var name i32 expr)`, `(set name expr)`, or `(while condition body...)` before the final bool expression"), + ); + } + } + } + + if errors.is_empty() { + Ok(Test { + name, + name_span: items[1].span, + body, + span: form.span, + }) + } else { + Err(errors) + } +} + +fn lower_params(file: &str, form: &SExpr) -> Result, Vec> { + let mut errors = Vec::new(); + let Some(items) = expect_list(form) else { + return Err(vec![Diagnostic::new( + file, + "InvalidParams", + "parameters must be a list", + ) + .with_span(form.span)]); + }; + + let mut params = Vec::new(); + + for item in items { + let Some(pair) = expect_list(item) else { + errors.push( + Diagnostic::new(file, "InvalidParam", "parameter must be `(name Type)`") + .with_span(item.span), + ); + continue; + }; + + if pair.len() != 2 { + errors.push( + Diagnostic::new(file, "InvalidParam", "parameter must be `(name Type)`") + .with_span(item.span), + ); + continue; + } + + let Some(name) = expect_ident(&pair[0]) else { + errors.push( + Diagnostic::new( + file, + "InvalidParamName", + "parameter name must be an identifier", + ) + .with_span(pair[0].span), + ); + continue; + }; + + let Some(ty) = lower_type(&pair[1]) else { + errors.push( + Diagnostic::new(file, "InvalidParamType", "invalid parameter type") + .with_span(pair[1].span), + ); + continue; + }; + + if ty == Type::Unit { + errors.push(unsupported_unit_parameter_signature(file, pair[1].span)); + continue; + } + + params.push(Param { + name: name.to_string(), + name_span: pair[0].span, + ty, + ty_span: pair[1].span, + }); + } + + if errors.is_empty() { + Ok(params) + } else { + Err(errors) + } +} + +fn unsupported_unit_parameter_signature(file: &str, span: crate::token::Span) -> Diagnostic { + Diagnostic::new( + file, + "UnsupportedUnitSignatureType", + "function parameter type `unit` is unsupported", + ) + .with_span(span) + .expected("non-unit function parameter type") + .found(Type::Unit.to_string()) + .hint("`unit` is reserved for compiler/runtime unit-producing forms") +} + +fn unsupported_unit_return_signature(file: &str, span: crate::token::Span) -> Diagnostic { + Diagnostic::new( + file, + "UnsupportedUnitSignatureType", + "function return type `unit` is unsupported", + ) + .with_span(span) + .expected("non-unit function return type") + .found(Type::Unit.to_string()) + .hint("`unit` is reserved for compiler/runtime unit-producing forms") +} + +fn lower_type(form: &SExpr) -> Option { + match &form.kind { + SExprKind::Atom(Atom::Ident(name)) => match name.as_str() { + "i32" => Some(Type::I32), + "i64" => Some(Type::I64), + "u32" => Some(Type::U32), + "u64" => Some(Type::U64), + "f64" => Some(Type::F64), + "bool" => Some(Type::Bool), + "unit" => Some(Type::Unit), + "string" => Some(Type::String), + other => Some(Type::Named(other.to_string())), + }, + SExprKind::List(items) if !items.is_empty() => { + let head = expect_ident(&items[0])?; + match head { + "ptr" if items.len() == 2 => Some(Type::Ptr(Box::new(lower_type(&items[1])?))), + "slice" if items.len() == 2 => Some(Type::Slice(Box::new(lower_type(&items[1])?))), + "option" if items.len() == 2 => { + Some(Type::Option(Box::new(lower_type(&items[1])?))) + } + "result" if items.len() == 3 => Some(Type::Result( + Box::new(lower_type(&items[1])?), + Box::new(lower_type(&items[2])?), + )), + "array" if items.len() == 3 => { + let inner = lower_type(&items[1])?; + let n = match items[2].kind { + SExprKind::Atom(Atom::Int(n)) if n >= 0 => n as usize, + _ => return None, + }; + Some(Type::Array(Box::new(inner), n)) + } + "vec" if items.len() == 2 => Some(Type::Vec(Box::new(lower_type(&items[1])?))), + _ => None, + } + } + _ => None, + } +} + +fn lower_expr( + file: &str, + form: &SExpr, + struct_names: &HashSet, + enum_names: &HashSet, +) -> Result { + match &form.kind { + SExprKind::Atom(Atom::Int(value)) => { + let value = i32::try_from(*value).map_err(|_| { + Diagnostic::new( + file, + "IntegerOutOfRange", + "integer literal is outside the supported i32 range", + ) + .with_span(form.span) + .expected("i32") + .found(value.to_string()) + })?; + + Ok(Expr { + kind: ExprKind::Int(value), + span: form.span, + }) + } + SExprKind::Atom(Atom::I64(value)) => Ok(Expr { + kind: ExprKind::Int64(*value), + span: form.span, + }), + SExprKind::Atom(Atom::U32(value)) => Ok(Expr { + kind: ExprKind::UInt32(*value), + span: form.span, + }), + SExprKind::Atom(Atom::U64(value)) => Ok(Expr { + kind: ExprKind::UInt64(*value), + span: form.span, + }), + SExprKind::Atom(Atom::String(value)) => Ok(Expr { + kind: ExprKind::String(value.clone()), + span: form.span, + }), + SExprKind::Atom(Atom::Float(value)) => { + if !value.is_finite() { + return Err(Diagnostic::new( + file, + "UnsupportedFloatLiteral", + "f64 literals must be finite in exp-20", + ) + .with_span(form.span) + .expected("finite f64 literal") + .found(value.to_string()) + .hint("NaN and infinity semantics remain deferred")); + } + + Ok(Expr { + kind: ExprKind::Float(*value), + span: form.span, + }) + } + SExprKind::Atom(Atom::Ident(name)) if name == "true" => Ok(Expr { + kind: ExprKind::Bool(true), + span: form.span, + }), + SExprKind::Atom(Atom::Ident(name)) if name == "false" => Ok(Expr { + kind: ExprKind::Bool(false), + span: form.span, + }), + SExprKind::Atom(Atom::Ident(name)) => Ok(Expr { + kind: ExprKind::Var(name.clone()), + span: form.span, + }), + SExprKind::List(items) if items.is_empty() => { + Err(Diagnostic::new(file, "EmptyForm", "empty form").with_span(form.span)) + } + SExprKind::List(items) => { + let Some(head) = expect_ident(&items[0]) else { + return Err(Diagnostic::new( + file, + "InvalidCall", + "form head must be an identifier", + ) + .with_span(items[0].span)); + }; + + match head { + "+" | "-" | "*" | "/" | "%" | "bit_and" | "bit_or" | "bit_xor" | "=" | "<" + | ">" | "<=" | ">=" => { + if items.len() != 3 { + return Err(Diagnostic::new( + file, + "InvalidBinary", + "binary operator expects exactly two operands", + ) + .with_span(form.span)); + } + + let op = match head { + "+" => BinaryOp::Add, + "-" => BinaryOp::Sub, + "*" => BinaryOp::Mul, + "/" => BinaryOp::Div, + "%" => BinaryOp::Rem, + "bit_and" => BinaryOp::BitAnd, + "bit_or" => BinaryOp::BitOr, + "bit_xor" => BinaryOp::BitXor, + "=" => BinaryOp::Eq, + "<" => BinaryOp::Lt, + ">" => BinaryOp::Gt, + "<=" => BinaryOp::Le, + ">=" => BinaryOp::Ge, + _ => unreachable!(), + }; + + Ok(Expr { + kind: ExprKind::Binary { + op, + left: Box::new(lower_expr(file, &items[1], struct_names, enum_names)?), + right: Box::new(lower_expr(file, &items[2], struct_names, enum_names)?), + }, + span: form.span, + }) + } + "and" | "or" => { + if items.len() != 3 { + return Err(Diagnostic::new( + file, + "InvalidLogical", + "logical operator expects exactly two operands", + ) + .with_span(form.span)); + } + + let left = lower_expr(file, &items[1], struct_names, enum_names)?; + let right = lower_expr(file, &items[2], struct_names, enum_names)?; + let literal = Expr { + kind: ExprKind::Bool(head == "or"), + span: form.span, + }; + let (then_expr, else_expr) = if head == "and" { + (right, literal) + } else { + (literal, right) + }; + + Ok(Expr { + kind: ExprKind::If { + condition: Box::new(left), + then_expr: Box::new(then_expr), + else_expr: Box::new(else_expr), + }, + span: form.span, + }) + } + "not" => { + if items.len() != 2 { + return Err(Diagnostic::new( + file, + "InvalidLogical", + "logical not expects exactly one operand", + ) + .with_span(form.span)); + } + + Ok(Expr { + kind: ExprKind::If { + condition: Box::new(lower_expr( + file, + &items[1], + struct_names, + enum_names, + )?), + then_expr: Box::new(Expr { + kind: ExprKind::Bool(false), + span: form.span, + }), + else_expr: Box::new(Expr { + kind: ExprKind::Bool(true), + span: form.span, + }), + }, + span: form.span, + }) + } + "." => lower_field_access(file, form, items, struct_names, enum_names), + "array" => lower_array_init(file, form, items, struct_names, enum_names), + "some" => lower_option_some(file, form, items, struct_names, enum_names), + "none" => lower_option_none(file, form, items), + "ok" => lower_result_ok(file, form, items, struct_names, enum_names), + "err" => lower_result_err(file, form, items, struct_names, enum_names), + "is_some" | "is_none" | "is_ok" | "is_err" | "std.result.is_ok" + | "std.result.is_err" => { + lower_unary_observer(file, form, items, struct_names, enum_names, head) + } + "unwrap_some" + | "unwrap_ok" + | "unwrap_err" + | "std.result.unwrap_ok" + | "std.result.unwrap_err" => { + lower_unary_payload_access(file, form, items, struct_names, enum_names, head) + } + "index" => lower_index(file, form, items, struct_names, enum_names), + "if" => { + if items.len() != 4 { + return Err(Diagnostic::new( + file, + "MalformedIfForm", + "`if` expects condition, then expression, else expression", + ) + .with_span(form.span) + .hint("use `(if condition then-expression else-expression)`")); + } + + Ok(Expr { + kind: ExprKind::If { + condition: Box::new(lower_expr( + file, + &items[1], + struct_names, + enum_names, + )?), + then_expr: Box::new(lower_expr( + file, + &items[2], + struct_names, + enum_names, + )?), + else_expr: Box::new(lower_expr( + file, + &items[3], + struct_names, + enum_names, + )?), + }, + span: form.span, + }) + } + "match" => lower_match(file, form, items, struct_names, enum_names), + "let" | "var" => { + lower_local(file, form, items, head == "var", struct_names, enum_names) + } + "set" => lower_set(file, form, items, struct_names, enum_names), + "while" => lower_while(file, form, items, struct_names, enum_names), + "unsafe" => lower_unsafe(file, form, items, struct_names, enum_names), + name if struct_names.contains(name) => { + lower_struct_init(file, form, items, name, struct_names, enum_names) + } + name if qualified_enum_name(name) + .map(|(enum_name, _)| { + enum_names.contains(enum_name) || starts_uppercase(enum_name) + }) + .unwrap_or(false) => + { + let (enum_name, variant) = qualified_enum_name(name).expect("qualified enum"); + let mut args = Vec::new(); + for item in &items[1..] { + args.push(lower_expr(file, item, struct_names, enum_names)?); + } + Ok(Expr { + kind: ExprKind::EnumVariant { + enum_name: enum_name.to_string(), + variant: variant.to_string(), + name_span: items[0].span, + args, + }, + span: form.span, + }) + } + name => { + let mut args = Vec::new(); + for item in &items[1..] { + args.push(lower_expr(file, item, struct_names, enum_names)?); + } + Ok(Expr { + kind: ExprKind::Call { + name: name.to_string(), + name_span: items[0].span, + args, + }, + span: form.span, + }) + } + } + } + _ => Err( + Diagnostic::new(file, "InvalidExpression", "invalid expression").with_span(form.span), + ), + } +} + +fn lower_match( + file: &str, + form: &SExpr, + items: &[SExpr], + struct_names: &HashSet, + enum_names: &HashSet, +) -> Result { + if items.len() < 2 { + return Err( + Diagnostic::new(file, "MalformedMatchPattern", "`match` expects a subject") + .with_span(form.span) + .expected("(match subject (pattern body...) (pattern body...))"), + ); + } + + let subject = Box::new(lower_expr(file, &items[1], struct_names, enum_names)?); + let mut arms = Vec::new(); + + for item in &items[2..] { + let Some(arm_items) = expect_list(item) else { + return Err(Diagnostic::new( + file, + "MalformedMatchPattern", + "match arm must be a list containing a pattern and body", + ) + .with_span(item.span) + .expected("((some payload) body...)")); + }; + + if arm_items.len() < 2 { + return Err(Diagnostic::new( + file, + "MalformedMatchPattern", + "match arm must contain a pattern and at least one body expression", + ) + .with_span(item.span) + .expected("((some payload) body...)")); + } + + let pattern = lower_match_pattern(file, &arm_items[0])?; + let mut body = Vec::new(); + for body_expr in &arm_items[1..] { + body.push(lower_expr(file, body_expr, struct_names, enum_names)?); + } + + arms.push(MatchArm { + pattern, + body, + span: item.span, + }); + } + + Ok(Expr { + kind: ExprKind::Match { subject, arms }, + span: form.span, + }) +} + +fn lower_match_pattern(file: &str, form: &SExpr) -> Result { + let Some(items) = expect_list(form) else { + return Err(Diagnostic::new( + file, + "MalformedMatchPattern", + "match arm pattern must be a list", + ) + .with_span(form.span) + .expected("(some binding), (none), (ok binding), or (err binding)")); + }; + + let Some(head) = items.first().and_then(expect_ident) else { + return Err(Diagnostic::new( + file, + "MalformedMatchPattern", + "match arm pattern head must be an identifier", + ) + .with_span(form.span) + .expected("(some binding), (none), (ok binding), or (err binding)")); + }; + + match head { + "some" | "ok" | "err" => { + if items.len() != 2 { + return Err(Diagnostic::new( + file, + "MalformedMatchPattern", + format!("`{}` match pattern requires one payload binding", head), + ) + .with_span(form.span) + .expected(format!("({} binding)", head))); + } + + let Some(binding) = expect_ident(&items[1]) else { + return Err(Diagnostic::new( + file, + "MalformedMatchPattern", + "match payload binding must be an identifier", + ) + .with_span(items[1].span) + .expected("identifier")); + }; + + let kind = match head { + "some" => MatchPatternKind::Some, + "ok" => MatchPatternKind::Ok, + "err" => MatchPatternKind::Err, + _ => unreachable!("known payload match pattern"), + }; + + Ok(MatchPattern { + kind, + binding: Some(binding.to_string()), + binding_span: Some(items[1].span), + span: form.span, + }) + } + "none" => { + if items.len() != 1 { + return Err(Diagnostic::new( + file, + "MalformedMatchPattern", + "`none` match pattern does not take a payload binding", + ) + .with_span(form.span) + .expected("(none)")); + } + + Ok(MatchPattern { + kind: MatchPatternKind::None, + binding: None, + binding_span: None, + span: form.span, + }) + } + name if qualified_enum_name(name).is_some() => { + if items.len() > 2 { + return Err(Diagnostic::new( + file, + "InvalidEnumMatchArm", + "enum match patterns support at most one payload binding", + ) + .with_span(form.span) + .expected("(Name.Variant) or (Name.Variant binding)")); + } + + let (enum_name, variant) = qualified_enum_name(name).expect("qualified enum pattern"); + let binding = if items.len() == 2 { + let Some(binding) = expect_ident(&items[1]) else { + return Err(Diagnostic::new( + file, + "MalformedMatchPattern", + "match payload binding must be an identifier", + ) + .with_span(items[1].span) + .expected("identifier")); + }; + Some(binding.to_string()) + } else { + None + }; + Ok(MatchPattern { + kind: MatchPatternKind::EnumVariant { + enum_name: enum_name.to_string(), + variant: variant.to_string(), + }, + binding, + binding_span: items.get(1).map(|item| item.span), + span: form.span, + }) + } + _ => Err(Diagnostic::new( + file, + "MalformedMatchPattern", + format!("unsupported match pattern `{}`", head), + ) + .with_span(form.span) + .expected("(some binding), (none), (ok binding), or (err binding)")), + } +} + +fn qualified_enum_name(name: &str) -> Option<(&str, &str)> { + let (enum_name, variant) = name.split_once('.')?; + if enum_name.is_empty() || variant.is_empty() || variant.contains('.') { + return None; + } + Some((enum_name, variant)) +} + +fn starts_uppercase(name: &str) -> bool { + name.chars().next().is_some_and(char::is_uppercase) +} + +fn lower_unary_observer( + file: &str, + form: &SExpr, + items: &[SExpr], + struct_names: &HashSet, + enum_names: &HashSet, + name: &str, +) -> Result { + if items.len() != 2 { + return Err(Diagnostic::new( + file, + "MalformedObservationForm", + format!("`{}` expects exactly one value", name), + ) + .with_span(form.span) + .hint(format!("use `({} value)`", name))); + } + + let value = Box::new(lower_expr(file, &items[1], struct_names, enum_names)?); + let kind = match name { + "is_some" => ExprKind::OptionIsSome { value }, + "is_none" => ExprKind::OptionIsNone { value }, + "is_ok" | "std.result.is_ok" => ExprKind::ResultIsOk { + source_name: name.to_string(), + value, + }, + "is_err" | "std.result.is_err" => ExprKind::ResultIsErr { + source_name: name.to_string(), + value, + }, + _ => unreachable!("unknown observer"), + }; + + Ok(Expr { + kind, + span: form.span, + }) +} + +fn lower_unary_payload_access( + file: &str, + form: &SExpr, + items: &[SExpr], + struct_names: &HashSet, + enum_names: &HashSet, + name: &str, +) -> Result { + if items.len() != 2 { + return Err(Diagnostic::new( + file, + "MalformedUnwrapForm", + format!("`{}` expects exactly one value", name), + ) + .with_span(form.span) + .hint(format!("use `({} value)`", name))); + } + + let value = Box::new(lower_expr(file, &items[1], struct_names, enum_names)?); + let kind = match name { + "unwrap_some" => ExprKind::OptionUnwrapSome { value }, + "unwrap_ok" | "std.result.unwrap_ok" => ExprKind::ResultUnwrapOk { + source_name: name.to_string(), + value, + }, + "unwrap_err" | "std.result.unwrap_err" => ExprKind::ResultUnwrapErr { + source_name: name.to_string(), + value, + }, + _ => unreachable!("unknown payload access"), + }; + + Ok(Expr { + kind, + span: form.span, + }) +} + +fn lower_unsafe( + file: &str, + form: &SExpr, + items: &[SExpr], + struct_names: &HashSet, + enum_names: &HashSet, +) -> Result { + if items.len() < 2 { + return Err(Diagnostic::new( + file, + "MalformedUnsafeForm", + "`unsafe` block must contain a final expression", + ) + .with_span(form.span) + .expected("(unsafe body-form... final-expression)") + .hint( + "use `(unsafe expression)` or add supported body forms before the final expression", + )); + } + + let mut body = Vec::new(); + for item in &items[1..] { + body.push(lower_expr(file, item, struct_names, enum_names)?); + } + + Ok(Expr { + kind: ExprKind::Unsafe { body }, + span: form.span, + }) +} + +fn lower_field_access( + file: &str, + form: &SExpr, + items: &[SExpr], + struct_names: &HashSet, + enum_names: &HashSet, +) -> Result { + if items.len() != 3 { + return Err(Diagnostic::new( + file, + "MalformedFieldAccess", + "field access must be `(. value field)`", + ) + .with_span(form.span)); + } + + let Some(field) = expect_ident(&items[2]) else { + return Err( + Diagnostic::new(file, "InvalidFieldName", "field name must be an identifier") + .with_span(items[2].span), + ); + }; + + Ok(Expr { + kind: ExprKind::FieldAccess { + value: Box::new(lower_expr(file, &items[1], struct_names, enum_names)?), + field: field.to_string(), + field_span: items[2].span, + }, + span: form.span, + }) +} + +fn lower_array_init( + file: &str, + form: &SExpr, + items: &[SExpr], + struct_names: &HashSet, + enum_names: &HashSet, +) -> Result { + if items.len() < 2 { + return Err(Diagnostic::new( + file, + "MalformedArrayConstructor", + "array constructor must be `(array TYPE value...)`", + ) + .with_span(form.span) + .hint( + "use `(array i32 1 2 3)`, `(array bool true false)`, or `(array string \"a\" \"b\")`", + )); + } + + let Some(elem_ty) = lower_type(&items[1]) else { + return Err(Diagnostic::new( + file, + "InvalidArrayElementType", + "array constructor element type is invalid", + ) + .with_span(items[1].span) + .hint("fixed arrays use direct scalar `i32`, `i64`, `f64`, `bool`, `string`, known enum, or known non-recursive struct elements")); + }; + + let mut elements = Vec::new(); + for item in &items[2..] { + elements.push(lower_expr(file, item, struct_names, enum_names)?); + } + + Ok(Expr { + kind: ExprKind::ArrayInit { + elem_ty, + elem_ty_span: items[1].span, + elements, + }, + span: form.span, + }) +} + +fn lower_option_some( + file: &str, + form: &SExpr, + items: &[SExpr], + struct_names: &HashSet, + enum_names: &HashSet, +) -> Result { + if items.len() != 3 { + return Err(Diagnostic::new( + file, + "MalformedOptionConstructor", + "`some` constructor must be `(some i32 value)`", + ) + .with_span(form.span) + .hint("use `(some i32 value)`")); + } + + let Some(payload_ty) = lower_type(&items[1]) else { + return Err(Diagnostic::new( + file, + "InvalidOptionPayloadType", + "option constructor payload type is invalid", + ) + .with_span(items[1].span) + .hint("first-pass options use `i32` payloads")); + }; + + Ok(Expr { + kind: ExprKind::OptionSome { + payload_ty, + payload_ty_span: items[1].span, + value: Box::new(lower_expr(file, &items[2], struct_names, enum_names)?), + }, + span: form.span, + }) +} + +fn lower_option_none(file: &str, form: &SExpr, items: &[SExpr]) -> Result { + if items.len() != 2 { + return Err(Diagnostic::new( + file, + "MalformedOptionConstructor", + "`none` constructor must be `(none i32)`", + ) + .with_span(form.span) + .hint("use `(none i32)`")); + } + + let Some(payload_ty) = lower_type(&items[1]) else { + return Err(Diagnostic::new( + file, + "InvalidOptionPayloadType", + "option constructor payload type is invalid", + ) + .with_span(items[1].span) + .hint("first-pass options use `i32` payloads")); + }; + + Ok(Expr { + kind: ExprKind::OptionNone { + payload_ty, + payload_ty_span: items[1].span, + }, + span: form.span, + }) +} + +fn lower_result_ok( + file: &str, + form: &SExpr, + items: &[SExpr], + struct_names: &HashSet, + enum_names: &HashSet, +) -> Result { + if items.len() != 4 { + return Err(Diagnostic::new( + file, + "MalformedResultConstructor", + "`ok` constructor must be `(ok i32 i32 value)`", + ) + .with_span(form.span) + .hint("use `(ok i32 i32 value)`")); + } + + let Some(ok_ty) = lower_type(&items[1]) else { + return Err(Diagnostic::new( + file, + "InvalidResultPayloadType", + "result constructor ok type is invalid", + ) + .with_span(items[1].span) + .hint("first-pass results use `i32` payloads")); + }; + let Some(err_ty) = lower_type(&items[2]) else { + return Err(Diagnostic::new( + file, + "InvalidResultPayloadType", + "result constructor err type is invalid", + ) + .with_span(items[2].span) + .hint("first-pass results use `i32` payloads")); + }; + + Ok(Expr { + kind: ExprKind::ResultOk { + ok_ty, + ok_ty_span: items[1].span, + err_ty, + err_ty_span: items[2].span, + value: Box::new(lower_expr(file, &items[3], struct_names, enum_names)?), + }, + span: form.span, + }) +} + +fn lower_result_err( + file: &str, + form: &SExpr, + items: &[SExpr], + struct_names: &HashSet, + enum_names: &HashSet, +) -> Result { + if items.len() != 4 { + return Err(Diagnostic::new( + file, + "MalformedResultConstructor", + "`err` constructor must be `(err i32 i32 value)`", + ) + .with_span(form.span) + .hint("use `(err i32 i32 value)`")); + } + + let Some(ok_ty) = lower_type(&items[1]) else { + return Err(Diagnostic::new( + file, + "InvalidResultPayloadType", + "result constructor ok type is invalid", + ) + .with_span(items[1].span) + .hint("first-pass results use `i32` payloads")); + }; + let Some(err_ty) = lower_type(&items[2]) else { + return Err(Diagnostic::new( + file, + "InvalidResultPayloadType", + "result constructor err type is invalid", + ) + .with_span(items[2].span) + .hint("first-pass results use `i32` payloads")); + }; + + Ok(Expr { + kind: ExprKind::ResultErr { + ok_ty, + ok_ty_span: items[1].span, + err_ty, + err_ty_span: items[2].span, + value: Box::new(lower_expr(file, &items[3], struct_names, enum_names)?), + }, + span: form.span, + }) +} + +fn lower_index( + file: &str, + form: &SExpr, + items: &[SExpr], + struct_names: &HashSet, + enum_names: &HashSet, +) -> Result { + if items.len() != 3 { + return Err(Diagnostic::new( + file, + "MalformedIndex", + "index expression must be `(index array-expr index-expr)`", + ) + .with_span(form.span) + .hint("use `(index values 0)`")); + } + + Ok(Expr { + kind: ExprKind::Index { + array: Box::new(lower_expr(file, &items[1], struct_names, enum_names)?), + index: Box::new(lower_expr(file, &items[2], struct_names, enum_names)?), + }, + span: form.span, + }) +} + +fn lower_struct_init( + file: &str, + form: &SExpr, + items: &[SExpr], + name: &str, + struct_names: &HashSet, + enum_names: &HashSet, +) -> Result { + let mut fields = Vec::new(); + + for item in &items[1..] { + let Some(pair) = expect_list(item) else { + return Err(Diagnostic::new( + file, + "MalformedStructConstructor", + "struct constructor fields must be `(field value)`", + ) + .with_span(item.span)); + }; + + if pair.len() != 2 { + return Err(Diagnostic::new( + file, + "MalformedStructConstructor", + "struct constructor fields must be `(field value)`", + ) + .with_span(item.span)); + } + + let Some(field_name) = expect_ident(&pair[0]) else { + return Err(Diagnostic::new( + file, + "InvalidStructConstructorField", + "struct constructor field name must be an identifier", + ) + .with_span(pair[0].span)); + }; + + fields.push(StructInitField { + name: field_name.to_string(), + name_span: pair[0].span, + expr: lower_expr(file, &pair[1], struct_names, enum_names)?, + }); + } + + Ok(Expr { + kind: ExprKind::StructInit { + name: name.to_string(), + name_span: items[0].span, + fields, + }, + span: form.span, + }) +} + +fn lower_local( + file: &str, + form: &SExpr, + items: &[SExpr], + mutable: bool, + struct_names: &HashSet, + enum_names: &HashSet, +) -> Result { + let keyword = if mutable { "var" } else { "let" }; + if items.len() != 4 { + return Err(Diagnostic::new( + file, + "InvalidLocalDeclaration", + format!("`{}` must be `({} name i32 expr)`", keyword, keyword), + ) + .with_span(form.span)); + } + + let Some(name) = expect_ident(&items[1]) else { + return Err( + Diagnostic::new(file, "InvalidLocalName", "local name must be an identifier") + .with_span(items[1].span), + ); + }; + + let Some(ty) = lower_type(&items[2]) else { + return Err( + Diagnostic::new(file, "InvalidLocalType", "invalid local type") + .with_span(items[2].span), + ); + }; + + Ok(Expr { + kind: ExprKind::Local { + mutable, + name: name.to_string(), + name_span: items[1].span, + ty, + expr: Box::new(lower_expr(file, &items[3], struct_names, enum_names)?), + }, + span: form.span, + }) +} + +fn lower_set( + file: &str, + form: &SExpr, + items: &[SExpr], + struct_names: &HashSet, + enum_names: &HashSet, +) -> Result { + if items.len() != 3 { + return Err( + Diagnostic::new(file, "InvalidSet", "`set` must be `(set name expr)`") + .with_span(form.span), + ); + } + + let Some(name) = expect_ident(&items[1]) else { + return Err(Diagnostic::new( + file, + "InvalidSetTarget", + "`set` target must be an identifier", + ) + .with_span(items[1].span)); + }; + + Ok(Expr { + kind: ExprKind::Set { + name: name.to_string(), + name_span: items[1].span, + expr: Box::new(lower_expr(file, &items[2], struct_names, enum_names)?), + }, + span: form.span, + }) +} + +fn lower_while( + file: &str, + form: &SExpr, + items: &[SExpr], + struct_names: &HashSet, + enum_names: &HashSet, +) -> Result { + if items.len() < 2 { + return Err( + Diagnostic::new(file, "MalformedWhileForm", "`while` must have a condition") + .with_span(form.span) + .hint("use `(while condition body...)`"), + ); + } + + if items.len() < 3 { + return Err(Diagnostic::new( + file, + "EmptyWhileBody", + "`while` must have at least one body form", + ) + .with_span(form.span) + .hint("provide at least one unit-producing loop body form")); + } + + let mut body = Vec::new(); + for item in &items[2..] { + body.push(lower_expr(file, item, struct_names, enum_names)?); + } + + Ok(Expr { + kind: ExprKind::While { + condition: Box::new(lower_expr(file, &items[1], struct_names, enum_names)?), + body, + }, + span: form.span, + }) +} + +fn list_head(form: &SExpr) -> Option<&str> { + let items = expect_list(form)?; + let first = items.first()?; + expect_ident(first) +} + +fn expect_list(form: &SExpr) -> Option<&[SExpr]> { + match &form.kind { + SExprKind::List(items) => Some(items), + _ => None, + } +} + +fn expect_ident(form: &SExpr) -> Option<&str> { + match &form.kind { + SExprKind::Atom(Atom::Ident(name)) => Some(name.as_str()), + _ => None, + } +} + +fn expect_string(form: &SExpr) -> Option<&str> { + match &form.kind { + SExprKind::Atom(Atom::String(value)) => Some(value.as_str()), + _ => None, + } +} + +fn valid_test_name(name: &str) -> bool { + !name.is_empty() + && name + .bytes() + .all(|byte| matches!(byte, 0x20..=0x7e) && byte != b'"' && byte != b'\\') +} diff --git a/compiler/src/main.rs b/compiler/src/main.rs new file mode 100644 index 0000000..effe939 --- /dev/null +++ b/compiler/src/main.rs @@ -0,0 +1,2107 @@ +mod ast; +mod check; +mod diag; +mod docgen; +mod driver; +mod formatter; +mod lexer; +mod llvm; +mod lower; +mod project; +mod scaffold; +mod sexpr; +mod std_runtime; +mod test_runner; +mod token; +mod types; +mod unsafe_ops; + +use std::{ + env, fs, io, + panic::{self, AssertUnwindSafe}, + path::{Path, PathBuf}, + process::{self, Command as ProcessCommand}, +}; + +fn main() { + let raw_args = env::args().collect::>(); + let command_line = raw_args.join(" "); + + let args = match parse_args(&raw_args) { + Ok(args) => args, + Err(err) => exit_parse_error(err, &command_line), + }; + + match args { + Args::Help => { + print_usage(); + } + Args::Version => { + println!("{} {}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")); + } + Args::Run(invocation) => run_invocation_guarded(invocation), + } +} + +fn run_invocation_guarded(invocation: Invocation) -> ! { + let previous_hook = panic::take_hook(); + panic::set_hook(Box::new(|_| {})); + + let run_invocation = invocation.clone(); + let result = panic::catch_unwind(AssertUnwindSafe(move || { + run_invocation_inner(run_invocation); + })); + + panic::set_hook(previous_hook); + + match result { + Ok(()) => unreachable!("compiler invocation returned without exiting"), + Err(payload) => { + let detail = panic_payload_message(payload.as_ref()); + let message = format!("internal compiler error: {}", detail); + emit_message_diagnostic( + &message, + "InternalCompilerError", + ExitCode::Internal, + &invocation, + PrimaryOutput::Diagnostics { + text: message.as_str(), + }, + Some("report this as a compiler bug with the source file and command line"), + ); + } + } +} + +fn panic_payload_message(payload: &(dyn std::any::Any + Send)) -> &str { + if let Some(message) = payload.downcast_ref::<&str>() { + message + } else if let Some(message) = payload.downcast_ref::() { + message.as_str() + } else { + "non-string panic payload" + } +} + +fn run_invocation_inner(invocation: Invocation) -> ! { + if invocation.mode == Mode::New { + run_new(invocation); + } + if invocation.mode == Mode::Doc { + run_doc(invocation); + } + if invocation.mode == Mode::Format && invocation.fmt_action != FmtAction::Stdout { + run_fmt_action(invocation); + } + + let project_capable_mode = matches!(invocation.mode, Mode::Check | Mode::Build) + || (invocation.mode == Mode::RunTests && invocation.manifest_mode_name == "test"); + if project_capable_mode && project::is_project_input(&invocation.path) { + match invocation.mode { + Mode::Check => run_project_check(invocation), + Mode::RunTests => run_project_test(invocation), + Mode::Build => run_project_build(invocation), + _ => unreachable!("project mode is selected only for check/test/build"), + } + } + + let source = match fs::read_to_string(&invocation.path) { + Ok(source) => source, + Err(err) => { + let message = format!("cannot read `{}`: {}", invocation.path, err); + emit_message_diagnostic( + &message, + "InputReadFailed", + ExitCode::SourceFailure, + &invocation, + PrimaryOutput::Diagnostics { + text: message.as_str(), + }, + None, + ); + } + }; + + match invocation.mode { + Mode::Build => run_build(invocation, &source), + mode => run_text_mode(invocation, mode, &source), + } +} + +fn run_project_check(invocation: Invocation) -> ! { + match project::check_project(&invocation.path) { + Ok(output) => { + write_manifest_if_requested_with_project( + &invocation, + true, + PrimaryOutput::NoOutput, + None, + None, + Some(&output.artifact), + ); + process::exit(0); + } + Err(failure) => exit_project_failure(invocation, failure), + } +} + +fn run_project_test(invocation: Invocation) -> ! { + match project::run_tests(&invocation.path, invocation.test_filter.as_deref()) { + Ok(success) => { + let output = success.output; + let primary_output = if let Some(output_path) = invocation.output_path.as_deref() { + if let Err(err) = fs::write(output_path, &output) { + let message = format!("cannot write `{}`: {}", output_path, err); + emit_message_diagnostic( + &message, + "OutputWriteFailed", + ExitCode::ArtifactFailure, + &invocation, + PrimaryOutput::Diagnostics { + text: message.as_str(), + }, + None, + ); + } + PrimaryOutput::Path { + kind: Mode::RunTests.output_kind(), + path: output_path, + } + } else { + print!("{}", output); + PrimaryOutput::Stdout { + kind: Mode::RunTests.output_kind(), + text: &output, + } + }; + + write_manifest_if_requested_with_project( + &invocation, + true, + primary_output, + Some(test_summary_from_report(success.report)), + None, + Some(&success.artifact), + ); + process::exit(0); + } + Err(failure) => exit_project_failure(invocation, failure), + } +} + +fn run_project_build(invocation: Invocation) -> ! { + let output = match project::compile_to_llvm(&invocation.path) { + Ok(output) => output, + Err(failure) => exit_project_failure(invocation, failure), + }; + run_build_from_llvm(invocation, output.text, Some(output.artifact), Vec::new()); +} + +fn exit_project_failure(invocation: Invocation, failure: project::ProjectTestFailure) -> ! { + let rendered = render_source_diagnostics_multi( + &failure.diagnostics, + &failure.sources, + invocation.diagnostics, + ); + eprint!("{}", rendered.stderr); + let test_summary = failure.report.map(test_summary_from_report); + emit_filtered_test_summary_if_present(&invocation, test_summary.as_ref()); + write_manifest_if_requested_with_project( + &invocation, + false, + PrimaryOutput::Diagnostics { + text: &rendered.machine_text, + }, + test_summary, + None, + failure.artifact.as_ref(), + ); + process::exit(ExitCode::SourceFailure.code()); +} + +fn run_text_mode(invocation: Invocation, mode: Mode, source: &str) -> ! { + if mode == Mode::RunTests { + run_test_mode(invocation, source); + } + + let foreign_imports = c_imports_for_manifest(&invocation.path, source); + let result = match mode { + Mode::EmitLlvm => driver::compile_to_llvm(&invocation.path, source), + Mode::Check => driver::check_source(&invocation.path, source), + Mode::Format => driver::format_source(&invocation.path, source), + Mode::PrintTree => driver::print_parse_tree(&invocation.path, source), + Mode::InspectLoweringSurface => driver::inspect_lowering_surface(&invocation.path, source), + Mode::InspectLoweringChecked => driver::inspect_lowering_checked(&invocation.path, source), + Mode::CheckTests => driver::check_tests(&invocation.path, source), + Mode::RunTests => unreachable!("test mode is handled separately"), + Mode::Build => unreachable!("build is handled separately"), + Mode::New => unreachable!("new is handled separately"), + Mode::Doc => unreachable!("doc is handled separately"), + }; + + match result { + Ok(output) => { + let primary_output = if mode == Mode::Check { + PrimaryOutput::NoOutput + } else if let Some(output_path) = invocation.output_path.as_deref() { + if let Err(err) = fs::write(output_path, &output) { + let message = format!("cannot write `{}`: {}", output_path, err); + emit_message_diagnostic( + &message, + "OutputWriteFailed", + ExitCode::ArtifactFailure, + &invocation, + PrimaryOutput::Diagnostics { + text: message.as_str(), + }, + None, + ); + } + PrimaryOutput::Path { + kind: mode.output_kind(), + path: output_path, + } + } else { + print!("{}", output); + PrimaryOutput::Stdout { + kind: mode.output_kind(), + text: &output, + } + }; + + write_manifest_if_requested_with_foreign_imports( + &invocation, + true, + primary_output, + mode.test_summary(&output), + None, + &foreign_imports, + None, + ); + process::exit(0); + } + Err(diagnostics) => { + let rendered = render_source_diagnostics(&diagnostics, source, invocation.diagnostics); + eprint!("{}", rendered.stderr); + write_manifest_if_requested_with_foreign_imports( + &invocation, + false, + PrimaryOutput::Diagnostics { + text: &rendered.machine_text, + }, + None, + None, + &foreign_imports, + None, + ); + process::exit(ExitCode::SourceFailure.code()); + } + } +} + +fn run_new(invocation: Invocation) -> ! { + match scaffold::create_project(&invocation.path, invocation.project_name.as_deref()) { + Ok(()) => { + write_manifest_if_requested(&invocation, true, PrimaryOutput::NoOutput, None, None); + process::exit(0); + } + Err(diagnostic) => { + let rendered = render_source_diagnostics(&[diagnostic], "", invocation.diagnostics); + eprint!("{}", rendered.stderr); + write_manifest_if_requested( + &invocation, + false, + PrimaryOutput::Diagnostics { + text: &rendered.machine_text, + }, + None, + None, + ); + process::exit(ExitCode::SourceFailure.code()); + } + } +} + +fn run_doc(invocation: Invocation) -> ! { + let Some(output_dir) = invocation.output_path.as_deref() else { + emit_message_diagnostic( + "`doc` requires `-o `", + "UsageError", + ExitCode::Usage, + &invocation, + PrimaryOutput::Diagnostics { + text: "`doc` requires `-o `", + }, + None, + ); + }; + + match docgen::generate(&invocation.path, output_dir) { + Ok(()) => { + let index = Path::new(output_dir).join("index.md"); + let index = index.display().to_string(); + write_manifest_if_requested( + &invocation, + true, + PrimaryOutput::Path { + kind: Mode::Doc.output_kind(), + path: &index, + }, + None, + None, + ); + process::exit(0); + } + Err(failure) => { + let rendered = render_source_diagnostics_multi( + &failure.diagnostics, + &failure.sources, + invocation.diagnostics, + ); + eprint!("{}", rendered.stderr); + write_manifest_if_requested_with_project( + &invocation, + false, + PrimaryOutput::Diagnostics { + text: &rendered.machine_text, + }, + None, + None, + failure.artifact.as_ref(), + ); + process::exit(ExitCode::SourceFailure.code()); + } + } +} + +fn run_fmt_action(invocation: Invocation) -> ! { + let result = if project::is_project_input(&invocation.path) { + format_project(&invocation) + } else { + format_single_file(&invocation) + }; + + match result { + Ok(()) => { + write_manifest_if_requested(&invocation, true, PrimaryOutput::NoOutput, None, None); + process::exit(0); + } + Err(failure) => { + let rendered = render_source_diagnostics_multi( + &failure.diagnostics, + &failure.sources, + invocation.diagnostics, + ); + eprint!("{}", rendered.stderr); + write_manifest_if_requested_with_project( + &invocation, + false, + PrimaryOutput::Diagnostics { + text: &rendered.machine_text, + }, + None, + None, + failure.artifact.as_ref(), + ); + process::exit(ExitCode::SourceFailure.code()); + } + } +} + +fn format_single_file(invocation: &Invocation) -> Result<(), project::ToolFailure> { + let source = fs::read_to_string(&invocation.path).map_err(|err| project::ToolFailure { + diagnostics: vec![diag::Diagnostic::new( + &invocation.path, + "InputReadFailed", + format!("cannot read `{}`: {}", invocation.path, err), + )], + sources: vec![], + artifact: None, + })?; + let formatted = driver::format_source(&invocation.path, &source).map_err(|diagnostics| { + project::ToolFailure { + diagnostics, + sources: vec![project::SourceFile { + path: invocation.path.clone(), + source: source.clone(), + }], + artifact: None, + } + })?; + finish_formatted_source(invocation, &invocation.path, &source, &formatted, None) +} + +fn format_project(invocation: &Invocation) -> Result<(), project::ToolFailure> { + let loaded = project::load_project_sources_for_tools(&invocation.path)?; + for source in &loaded.sources { + let formatted = + driver::format_source(&source.path, &source.source).map_err(|diagnostics| { + project::ToolFailure { + diagnostics, + sources: loaded.sources.clone(), + artifact: Some(loaded.artifact.clone()), + } + })?; + finish_formatted_source( + invocation, + &source.path, + &source.source, + &formatted, + Some((&loaded.sources, &loaded.artifact)), + )?; + } + Ok(()) +} + +fn finish_formatted_source( + invocation: &Invocation, + path: &str, + original: &str, + formatted: &str, + project_context: Option<(&[project::SourceFile], &project::ProjectArtifact)>, +) -> Result<(), project::ToolFailure> { + if original == formatted { + return Ok(()); + } + + match invocation.fmt_action { + FmtAction::Check => { + let sources = project_context + .map(|(sources, _)| sources.to_vec()) + .unwrap_or_else(|| { + vec![project::SourceFile { + path: path.to_string(), + source: original.to_string(), + }] + }); + Err(project::ToolFailure { + diagnostics: vec![diag::Diagnostic::new( + path, + "FormatCheckFailed", + format!("`{}` is not formatted", path), + ) + .hint("run `glagol fmt --write` to update canonical formatting")], + sources, + artifact: project_context.map(|(_, artifact)| artifact.clone()), + }) + } + FmtAction::Write => fs::write(path, formatted).map_err(|err| project::ToolFailure { + diagnostics: vec![diag::Diagnostic::new( + path, + "OutputWriteFailed", + format!("cannot write `{}`: {}", path, err), + )], + sources: project_context + .map(|(sources, _)| sources.to_vec()) + .unwrap_or_default(), + artifact: project_context.map(|(_, artifact)| artifact.clone()), + }), + FmtAction::Stdout => unreachable!("stdout formatting is handled by text mode"), + } +} + +fn run_test_mode(invocation: Invocation, source: &str) -> ! { + let foreign_imports = c_imports_for_manifest(&invocation.path, source); + match driver::run_tests(&invocation.path, source, invocation.test_filter.as_deref()) { + Ok(result) => { + let output = result.output; + let primary_output = if let Some(output_path) = invocation.output_path.as_deref() { + if let Err(err) = fs::write(output_path, &output) { + let message = format!("cannot write `{}`: {}", output_path, err); + emit_message_diagnostic( + &message, + "OutputWriteFailed", + ExitCode::ArtifactFailure, + &invocation, + PrimaryOutput::Diagnostics { + text: message.as_str(), + }, + None, + ); + } + PrimaryOutput::Path { + kind: Mode::RunTests.output_kind(), + path: output_path, + } + } else { + print!("{}", output); + PrimaryOutput::Stdout { + kind: Mode::RunTests.output_kind(), + text: &output, + } + }; + + write_manifest_if_requested_with_foreign_imports( + &invocation, + true, + primary_output, + Some(test_summary_from_report(result.report)), + None, + &foreign_imports, + None, + ); + process::exit(0); + } + Err(failure) => { + let rendered = + render_source_diagnostics(&failure.diagnostics, source, invocation.diagnostics); + eprint!("{}", rendered.stderr); + let test_summary = failure.report.map(test_summary_from_report); + emit_filtered_test_summary_if_present(&invocation, test_summary.as_ref()); + write_manifest_if_requested_with_foreign_imports( + &invocation, + false, + PrimaryOutput::Diagnostics { + text: &rendered.machine_text, + }, + test_summary, + None, + &foreign_imports, + None, + ); + process::exit(ExitCode::SourceFailure.code()); + } + } +} + +fn run_build(invocation: Invocation, source: &str) -> ! { + let foreign_imports = c_imports_for_manifest(&invocation.path, source); + let llvm_ir = match driver::compile_to_llvm(&invocation.path, source) { + Ok(output) => output, + Err(diagnostics) => { + let rendered = render_source_diagnostics(&diagnostics, source, invocation.diagnostics); + eprint!("{}", rendered.stderr); + write_manifest_if_requested_with_foreign_imports( + &invocation, + false, + PrimaryOutput::Diagnostics { + text: &rendered.machine_text, + }, + None, + None, + &foreign_imports, + None, + ); + process::exit(ExitCode::SourceFailure.code()); + } + }; + + run_build_from_llvm(invocation, llvm_ir, None, foreign_imports); +} + +fn run_build_from_llvm( + invocation: Invocation, + llvm_ir: String, + project_artifact: Option, + foreign_imports: Vec, +) -> ! { + let Some(output_path) = invocation.output_path.as_deref() else { + emit_message_diagnostic( + "`build` requires `-o `", + "UsageError", + ExitCode::Usage, + &invocation, + PrimaryOutput::Diagnostics { + text: "`build` requires `-o `", + }, + None, + ); + }; + + if let Some(parent) = Path::new(output_path).parent() { + if !parent.as_os_str().is_empty() && !parent.is_dir() { + let message = format!( + "cannot write `{}`: parent directory does not exist", + output_path + ); + emit_message_diagnostic( + &message, + "OutputWriteFailed", + ExitCode::ArtifactFailure, + &invocation, + PrimaryOutput::Diagnostics { + text: message.as_str(), + }, + None, + ); + } + } + + let runtime = runtime_path(); + if !runtime.is_file() { + let message = format!("cannot find runtime C input `{}`", runtime.display()); + emit_message_diagnostic( + &message, + "ToolchainUnavailable", + ExitCode::Toolchain, + &invocation, + PrimaryOutput::Diagnostics { + text: message.as_str(), + }, + Some("build from a Glagol checkout or install that includes runtime/runtime.c"), + ); + } + + for path in &invocation.link_c_paths { + let c_path = Path::new(path); + if !c_path.is_file() { + let message = format!("cannot find C link input `{}`", path); + emit_message_diagnostic( + &message, + "InputReadFailed", + ExitCode::SourceFailure, + &invocation, + PrimaryOutput::Diagnostics { + text: message.as_str(), + }, + Some("pass an explicit local C source path after `--link-c`"), + ); + } + } + + let output = Path::new(output_path); + let output_dir = output + .parent() + .filter(|parent| !parent.as_os_str().is_empty()) + .unwrap_or_else(|| Path::new(".")); + let stem = output + .file_name() + .and_then(|name| name.to_str()) + .unwrap_or("glagol-output"); + let temp_llvm = output_dir.join(format!(".{}.{}.glagol.ll", stem, process::id())); + let temp_binary = output_dir.join(format!( + ".{}.{}.glagol{}", + stem, + process::id(), + env::consts::EXE_SUFFIX + )); + + if let Err(err) = fs::write(&temp_llvm, llvm_ir) { + let message = format!("cannot write `{}`: {}", temp_llvm.display(), err); + emit_message_diagnostic( + &message, + "OutputWriteFailed", + ExitCode::ArtifactFailure, + &invocation, + PrimaryOutput::Diagnostics { + text: message.as_str(), + }, + None, + ); + } + + let clang = env::var("GLAGOL_CLANG").unwrap_or_else(|_| "clang".to_string()); + let mut clang_command = ProcessCommand::new(&clang); + clang_command.arg("-O2").arg(&runtime).arg(&temp_llvm); + for path in &invocation.link_c_paths { + clang_command.arg(path); + } + let clang_output = clang_command.arg("-o").arg(&temp_binary).output(); + + let clang_output = match clang_output { + Ok(output) => output, + Err(err) if err.kind() == io::ErrorKind::NotFound => { + let _ = fs::remove_file(&temp_llvm); + let message = format!("cannot find Clang executable `{}`", clang); + emit_message_diagnostic( + &message, + "ToolchainUnavailable", + ExitCode::Toolchain, + &invocation, + PrimaryOutput::Diagnostics { + text: message.as_str(), + }, + Some("set GLAGOL_CLANG or add clang to PATH"), + ); + } + Err(err) => { + let _ = fs::remove_file(&temp_llvm); + let message = format!("cannot run Clang executable `{}`: {}", clang, err); + emit_message_diagnostic( + &message, + "ToolchainUnavailable", + ExitCode::Toolchain, + &invocation, + PrimaryOutput::Diagnostics { + text: message.as_str(), + }, + Some("set GLAGOL_CLANG to a usable clang-compatible compiler"), + ); + } + }; + + if !clang_output.status.success() { + let _ = fs::remove_file(&temp_llvm); + let _ = fs::remove_file(&temp_binary); + let stderr = String::from_utf8_lossy(&clang_output.stderr); + let message = if stderr.trim().is_empty() { + format!("Clang failed with status {}", clang_output.status) + } else { + format!( + "Clang failed with status {}: {}", + clang_output.status, + stderr.trim() + ) + }; + emit_message_diagnostic( + &message, + "ToolchainUnavailable", + ExitCode::Toolchain, + &invocation, + PrimaryOutput::Diagnostics { + text: message.as_str(), + }, + None, + ); + } + + if let Err(err) = fs::rename(&temp_binary, output_path) { + let _ = fs::remove_file(&temp_llvm); + let _ = fs::remove_file(&temp_binary); + let message = format!("cannot write `{}`: {}", output_path, err); + emit_message_diagnostic( + &message, + "OutputWriteFailed", + ExitCode::ArtifactFailure, + &invocation, + PrimaryOutput::Diagnostics { + text: message.as_str(), + }, + None, + ); + } + + let _ = fs::remove_file(&temp_llvm); + write_manifest_if_requested_with_foreign_imports( + &invocation, + true, + PrimaryOutput::Path { + kind: "native-executable", + path: output_path, + }, + None, + Some(BuildInfo { + clang: &clang, + runtime: &runtime, + c_inputs: &invocation.link_c_paths, + }), + &foreign_imports, + project_artifact.as_ref(), + ); + process::exit(0); +} + +#[derive(Clone)] +enum Args { + Help, + Version, + Run(Invocation), +} + +#[derive(Clone)] +struct Invocation { + mode: Mode, + manifest_mode_name: String, + path: String, + output_path: Option, + manifest_path: Option, + diagnostics: DiagnosticFormat, + command_line: String, + fmt_action: FmtAction, + project_name: Option, + link_c_paths: Vec, + test_filter: Option, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +enum FmtAction { + Stdout, + Check, + Write, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +enum DiagnosticFormat { + TextAndSexpr, + Json, +} + +#[derive(Debug)] +struct ParseError { + message: String, + manifest_path: Option, + diagnostics: DiagnosticFormat, + command_line: String, +} + +fn parse_args(raw_args: &[String]) -> Result { + let mut mode = None; + let mut mode_spelling = None::; + let mut path = None; + let mut output_path = None; + let mut manifest_path = None; + let mut diagnostics = DiagnosticFormat::TextAndSexpr; + let mut fmt_action = FmtAction::Stdout; + let mut project_name = None; + let mut link_c_paths = Vec::new(); + let mut test_filter = None; + let mut no_color = false; + let command_line = raw_args.join(" "); + let mut iter = raw_args + .iter() + .skip(1) + .cloned() + .collect::>() + .into_iter(); + + while let Some(arg) = iter.next() { + match arg.as_str() { + "-h" | "--help" => return Ok(Args::Help), + "--version" => return Ok(Args::Version), + "--json-diagnostics" => { + if diagnostics == DiagnosticFormat::Json { + return parse_error( + "--json-diagnostics was provided more than once", + manifest_path, + diagnostics, + command_line, + ); + } + diagnostics = DiagnosticFormat::Json; + } + "--no-color" => no_color = true, + "--emit=llvm" => set_mode( + &mut mode, + &mut mode_spelling, + Mode::EmitLlvm, + "--emit=llvm", + &manifest_path, + diagnostics, + &command_line, + )?, + "--format" => set_mode( + &mut mode, + &mut mode_spelling, + Mode::Format, + "--format", + &manifest_path, + diagnostics, + &command_line, + )?, + "--print-tree" => set_mode( + &mut mode, + &mut mode_spelling, + Mode::PrintTree, + "--print-tree", + &manifest_path, + diagnostics, + &command_line, + )?, + "--inspect-lowering=surface" => set_mode( + &mut mode, + &mut mode_spelling, + Mode::InspectLoweringSurface, + "--inspect-lowering=surface", + &manifest_path, + diagnostics, + &command_line, + )?, + "--inspect-lowering=checked" => set_mode( + &mut mode, + &mut mode_spelling, + Mode::InspectLoweringChecked, + "--inspect-lowering=checked", + &manifest_path, + diagnostics, + &command_line, + )?, + "--check-tests" => set_mode( + &mut mode, + &mut mode_spelling, + Mode::CheckTests, + "--check-tests", + &manifest_path, + diagnostics, + &command_line, + )?, + "--run-tests" => set_mode( + &mut mode, + &mut mode_spelling, + Mode::RunTests, + "--run-tests", + &manifest_path, + diagnostics, + &command_line, + )?, + "-o" => { + if output_path.is_some() { + return parse_error( + "output path was provided more than once", + manifest_path, + diagnostics, + command_line, + ); + } + output_path = Some(iter.next().ok_or_else(|| ParseError { + message: "`-o` requires a following path".to_string(), + manifest_path: manifest_path.clone(), + diagnostics, + command_line: command_line.clone(), + })?); + } + "--check" => { + if fmt_action != FmtAction::Stdout { + return parse_error( + "formatter action was provided more than once", + manifest_path, + diagnostics, + command_line, + ); + } + fmt_action = FmtAction::Check; + } + "--write" => { + if fmt_action != FmtAction::Stdout { + return parse_error( + "formatter action was provided more than once", + manifest_path, + diagnostics, + command_line, + ); + } + fmt_action = FmtAction::Write; + } + "--name" => { + if project_name.is_some() { + return parse_error( + "project name was provided more than once", + manifest_path, + diagnostics, + command_line, + ); + } + project_name = Some(iter.next().ok_or_else(|| ParseError { + message: "`--name` requires a following project name".to_string(), + manifest_path: manifest_path.clone(), + diagnostics, + command_line: command_line.clone(), + })?); + } + "--manifest" => { + if manifest_path.is_some() { + return parse_error( + "manifest path was provided more than once", + manifest_path, + diagnostics, + command_line, + ); + } + manifest_path = Some(iter.next().ok_or_else(|| ParseError { + message: "`--manifest` requires a following path".to_string(), + manifest_path: manifest_path.clone(), + diagnostics, + command_line: command_line.clone(), + })?); + } + "--link-c" => { + link_c_paths.push(iter.next().ok_or_else(|| ParseError { + message: "`--link-c` requires a following local C source path".to_string(), + manifest_path: manifest_path.clone(), + diagnostics, + command_line: command_line.clone(), + })?); + } + "--filter" => { + if test_filter.is_some() { + return parse_error( + "`--filter` was provided more than once", + manifest_path, + diagnostics, + command_line, + ); + } + test_filter = Some(iter.next().ok_or_else(|| ParseError { + message: "`--filter` requires a following substring".to_string(), + manifest_path: manifest_path.clone(), + diagnostics, + command_line: command_line.clone(), + })?); + } + "check" | "fmt" | "test" | "build" | "new" | "doc" if path.is_none() => { + let next = match arg.as_str() { + "check" => Mode::Check, + "fmt" => Mode::Format, + "test" => Mode::RunTests, + "build" => Mode::Build, + "new" => Mode::New, + "doc" => Mode::Doc, + _ => unreachable!(), + }; + set_mode( + &mut mode, + &mut mode_spelling, + next, + arg.as_str(), + &manifest_path, + diagnostics, + &command_line, + )?; + } + _ if arg.starts_with("--emit=") => { + return parse_error( + format!("unsupported emit mode `{}`", arg), + manifest_path, + diagnostics, + command_line, + ); + } + _ if arg.starts_with('-') => { + return parse_error( + format!("unexpected argument `{}`", arg), + manifest_path, + diagnostics, + command_line, + ); + } + _ if path.is_none() => path = Some(arg), + _ => { + return parse_error( + format!("unexpected argument `{}`", arg), + manifest_path, + diagnostics, + command_line, + ); + } + } + } + + let _ = no_color; + let mode = mode.unwrap_or(Mode::EmitLlvm); + + if path.is_none() { + return parse_error( + "missing source file", + manifest_path, + diagnostics, + command_line, + ); + } + + if mode == Mode::Build && output_path.is_none() { + return parse_error( + "`build` requires `-o `", + manifest_path, + diagnostics, + command_line, + ); + } + + if fmt_action != FmtAction::Stdout && mode != Mode::Format { + return parse_error( + "`--check` and `--write` are only supported with `fmt`", + manifest_path, + diagnostics, + command_line, + ); + } + + if project_name.is_some() && mode != Mode::New { + return parse_error( + "`--name` is only supported with `new`", + manifest_path, + diagnostics, + command_line, + ); + } + + if !link_c_paths.is_empty() && mode != Mode::Build { + return parse_error( + "`--link-c` is only supported with `build`", + manifest_path, + diagnostics, + command_line, + ); + } + + if test_filter.is_some() && mode != Mode::RunTests { + return parse_error( + "`--filter` is only supported with `test` and `--run-tests`", + manifest_path, + diagnostics, + command_line, + ); + } + + if mode == Mode::Doc && output_path.is_none() { + return parse_error( + "`doc` requires `-o `", + manifest_path, + diagnostics, + command_line, + ); + } + + if let (Some(output_path), Some(manifest_path)) = (&output_path, &manifest_path) { + if same_output_path(output_path, manifest_path) { + return parse_error( + "output path and manifest path must be different", + Some(manifest_path.clone()), + diagnostics, + command_line, + ); + } + } + + Ok(Args::Run(Invocation { + mode, + manifest_mode_name: manifest_mode_name(mode, mode_spelling.as_deref()).to_string(), + path: path.expect("checked above"), + output_path, + manifest_path, + diagnostics, + command_line, + fmt_action, + project_name, + link_c_paths, + test_filter, + })) +} + +fn set_mode( + mode: &mut Option, + mode_spelling: &mut Option, + next: Mode, + spelling: &str, + manifest_path: &Option, + diagnostics: DiagnosticFormat, + command_line: &str, +) -> Result<(), ParseError> { + if let Some(existing) = mode_spelling { + return parse_error( + format!( + "mode flags are mutually exclusive: cannot combine `{}` and `{}`", + existing, spelling + ), + manifest_path.clone(), + diagnostics, + command_line.to_string(), + ); + } + + *mode = Some(next); + *mode_spelling = Some(spelling.to_string()); + Ok(()) +} + +fn parse_error( + message: impl Into, + manifest_path: Option, + diagnostics: DiagnosticFormat, + command_line: String, +) -> Result { + Err(ParseError { + message: message.into(), + manifest_path, + diagnostics, + command_line, + }) +} + +fn exit_parse_error(err: ParseError, command_line: &str) -> ! { + let stderr = if err.diagnostics == DiagnosticFormat::Json { + let json = diag::render_json_message("error", "UsageError", &err.message, None); + eprintln!("{}", json); + json + } else { + eprintln!("error[UsageError]: {}", err.message); + print_usage(); + format!("error[UsageError]: {}", err.message) + }; + + if let Some(manifest_path) = err.manifest_path.as_deref() { + let invocation = Invocation { + mode: Mode::EmitLlvm, + manifest_mode_name: "usage-error".to_string(), + path: String::new(), + output_path: None, + manifest_path: Some(manifest_path.to_string()), + diagnostics: err.diagnostics, + command_line: if err.command_line.is_empty() { + command_line.to_string() + } else { + err.command_line + }, + fmt_action: FmtAction::Stdout, + project_name: None, + link_c_paths: Vec::new(), + test_filter: None, + }; + write_manifest_or_exit( + manifest_path, + &render_manifest( + None, + &invocation.command_line, + None, + false, + PrimaryOutput::Diagnostics { text: &stderr }, + None, + None, + &[], + None, + err.diagnostics, + ), + ); + } + + process::exit(ExitCode::Usage.code()); +} + +#[derive(Copy, Clone, PartialEq, Eq)] +enum Mode { + EmitLlvm, + Check, + Format, + PrintTree, + InspectLoweringSurface, + InspectLoweringChecked, + CheckTests, + RunTests, + Build, + New, + Doc, +} + +impl Mode { + fn manifest_name(self) -> &'static str { + match self { + Self::EmitLlvm => "emit-llvm", + Self::Check => "check", + Self::Format => "format", + Self::PrintTree => "print-tree", + Self::InspectLoweringSurface => "inspect-lowering-surface", + Self::InspectLoweringChecked => "inspect-lowering-checked", + Self::CheckTests => "check-tests", + Self::RunTests => "test", + Self::Build => "build", + Self::New => "new", + Self::Doc => "doc", + } + } + + fn output_kind(self) -> &'static str { + match self { + Self::EmitLlvm => "llvm-ir", + Self::Check => "no-output", + Self::Format => "formatted-source", + Self::PrintTree => "parse-tree", + Self::InspectLoweringSurface | Self::InspectLoweringChecked => "lowering-inspector", + Self::CheckTests | Self::RunTests => "stdout", + Self::Build => "native-executable", + Self::New => "no-output", + Self::Doc => "documentation", + } + } + + fn test_summary(self, output: &str) -> Option { + match self { + Self::CheckTests => { + parse_test_count(output, " test(s) checked").map(|total| TestSummary { + total_discovered: total, + selected: total, + passed: 0, + failed: 0, + skipped: total, + filter: None, + }) + } + Self::RunTests => { + parse_test_count(output, " test(s) passed").map(|total| TestSummary { + total_discovered: total, + selected: total, + passed: total, + failed: 0, + skipped: 0, + filter: None, + }) + } + _ => None, + } + } +} + +fn manifest_mode_name(mode: Mode, spelling: Option<&str>) -> &'static str { + match (mode, spelling) { + (Mode::RunTests, Some("--run-tests")) => "run-tests", + _ => mode.manifest_name(), + } +} + +#[derive(Copy, Clone)] +enum ExitCode { + SourceFailure, + Usage, + Toolchain, + Internal, + ArtifactFailure, +} + +impl ExitCode { + fn code(self) -> i32 { + match self { + Self::SourceFailure => 1, + Self::Usage => 2, + Self::Toolchain => 3, + Self::Internal => 4, + Self::ArtifactFailure => 5, + } + } +} + +struct RenderedDiagnostics { + stderr: String, + machine_text: String, +} + +fn render_source_diagnostics( + diagnostics: &[diag::Diagnostic], + source: &str, + format: DiagnosticFormat, +) -> RenderedDiagnostics { + let mut stderr = String::new(); + let mut machine = Vec::new(); + + for diagnostic in diagnostics { + match format { + DiagnosticFormat::TextAndSexpr => { + stderr.push_str(&diagnostic.render_human(source)); + stderr.push('\n'); + let rendered = diagnostic.render_machine(source); + stderr.push_str(&rendered); + stderr.push('\n'); + machine.push(rendered); + } + DiagnosticFormat::Json => { + let rendered = diagnostic.render_json(source); + stderr.push_str(&rendered); + stderr.push('\n'); + machine.push(rendered); + } + } + } + + RenderedDiagnostics { + stderr, + machine_text: machine.join("\n"), + } +} + +fn render_source_diagnostics_multi( + diagnostics: &[diag::Diagnostic], + sources: &[project::SourceFile], + format: DiagnosticFormat, +) -> RenderedDiagnostics { + let mut stderr = String::new(); + let mut machine = Vec::new(); + + for diagnostic in diagnostics { + match format { + DiagnosticFormat::TextAndSexpr => { + stderr.push_str(&diagnostic.render_human_with_sources(|file| { + sources + .iter() + .find(|source| source.path == file) + .map(|source| source.source.as_str()) + })); + stderr.push('\n'); + let rendered = diagnostic.render_machine_with_sources(|file| { + sources + .iter() + .find(|source| source.path == file) + .map(|source| source.source.as_str()) + }); + stderr.push_str(&rendered); + stderr.push('\n'); + machine.push(rendered); + } + DiagnosticFormat::Json => { + let rendered = diagnostic.render_json_with_sources(|file| { + sources + .iter() + .find(|source| source.path == file) + .map(|source| source.source.as_str()) + }); + stderr.push_str(&rendered); + stderr.push('\n'); + machine.push(rendered); + } + } + } + + RenderedDiagnostics { + stderr, + machine_text: machine.join("\n"), + } +} + +fn emit_message_diagnostic( + message: &str, + code: &str, + exit_code: ExitCode, + invocation: &Invocation, + _primary_output: PrimaryOutput<'_>, + hint: Option<&str>, +) -> ! { + let rendered = if invocation.diagnostics == DiagnosticFormat::Json { + diag::render_json_message("error", code, message, hint) + } else { + let mut text = format!("error[{}]: {}", code, message); + if let Some(hint) = hint { + text.push_str("\nhint: "); + text.push_str(hint); + } + text + }; + + eprintln!("{}", rendered); + write_manifest_if_requested( + invocation, + false, + PrimaryOutput::Diagnostics { text: &rendered }, + None, + None, + ); + process::exit(exit_code.code()); +} + +#[derive(Copy, Clone)] +enum PrimaryOutput<'a> { + NoOutput, + Path { kind: &'static str, path: &'a str }, + Stdout { kind: &'static str, text: &'a str }, + Diagnostics { text: &'a str }, +} + +struct TestSummary { + total_discovered: usize, + selected: usize, + passed: usize, + failed: usize, + skipped: usize, + filter: Option, +} + +fn test_summary_from_report(report: test_runner::TestReport) -> TestSummary { + TestSummary { + total_discovered: report.total_discovered, + selected: report.selected, + passed: report.passed, + failed: report.failed, + skipped: report.skipped, + filter: report.filter, + } +} + +fn emit_filtered_test_summary_if_present(invocation: &Invocation, summary: Option<&TestSummary>) { + let Some(summary) = summary else { + return; + }; + if summary.filter.is_none() { + return; + } + + let message = test_summary_line(summary); + if invocation.diagnostics == DiagnosticFormat::Json { + eprintln!( + "{}", + diag::render_json_message("note", "TestRunSummary", &message, None) + ); + } else { + eprintln!("{}", message); + } +} + +fn test_summary_line(summary: &TestSummary) -> String { + let mut line = format!( + "test summary: total_discovered {}, selected {}, passed {}, failed {}, skipped {}", + summary.total_discovered, summary.selected, summary.passed, summary.failed, summary.skipped + ); + if let Some(filter) = summary.filter.as_deref() { + line.push_str(", filter "); + line.push_str(&diag::render_string(filter)); + } + line +} + +struct BuildInfo<'a> { + clang: &'a str, + runtime: &'a Path, + c_inputs: &'a [String], +} + +fn write_manifest_if_requested( + invocation: &Invocation, + success: bool, + primary_output: PrimaryOutput<'_>, + test_summary: Option, + build_info: Option>, +) { + write_manifest_if_requested_with_foreign_imports( + invocation, + success, + primary_output, + test_summary, + build_info, + &[], + None, + ); +} + +fn write_manifest_if_requested_with_project( + invocation: &Invocation, + success: bool, + primary_output: PrimaryOutput<'_>, + test_summary: Option, + build_info: Option>, + project_info: Option<&project::ProjectArtifact>, +) { + write_manifest_if_requested_with_foreign_imports( + invocation, + success, + primary_output, + test_summary, + build_info, + &[], + project_info, + ); +} + +fn write_manifest_if_requested_with_foreign_imports( + invocation: &Invocation, + success: bool, + primary_output: PrimaryOutput<'_>, + test_summary: Option, + build_info: Option>, + foreign_imports: &[project::ProjectArtifactCImport], + project_info: Option<&project::ProjectArtifact>, +) { + if let Some(manifest_path) = invocation.manifest_path.as_deref() { + let manifest = render_manifest( + Some(&invocation.path), + &invocation.command_line, + Some(invocation.manifest_mode_name.as_str()), + success, + primary_output, + test_summary, + build_info, + foreign_imports, + project_info, + invocation.diagnostics, + ); + write_manifest_or_exit(manifest_path, &manifest); + } +} + +fn write_manifest_or_exit(path: &str, manifest: &str) { + if let Err(err) = fs::write(path, manifest) { + eprintln!("cannot write manifest `{}`: {}", path, err); + process::exit(ExitCode::ArtifactFailure.code()); + } +} + +fn render_manifest( + source: Option<&str>, + command: &str, + mode: Option<&str>, + success: bool, + primary_output: PrimaryOutput<'_>, + test_summary: Option, + build_info: Option>, + foreign_imports: &[project::ProjectArtifactCImport], + project_info: Option<&project::ProjectArtifact>, + diagnostics: DiagnosticFormat, +) -> String { + let mut out = String::new(); + out.push_str("(artifact-manifest\n"); + out.push_str(" (schema slovo.artifact-manifest)\n"); + out.push_str(" (version 1)\n"); + match source { + Some(source) => out.push_str(&format!(" (source {})\n", diag::render_string(source))), + None => out.push_str(" (source null)\n"), + } + out.push_str(&format!(" (command {})\n", diag::render_string(command))); + if let Some(mode) = mode { + out.push_str(&format!(" (mode {})\n", mode)); + } else { + out.push_str(" (mode usage-error)\n"); + } + out.push_str(&format!( + " (success {})\n", + if success { "true" } else { "false" } + )); + out.push_str(" (diagnostics-schema-version 1)\n"); + out.push_str(&format!( + " (diagnostics-encoding {})\n", + match diagnostics { + DiagnosticFormat::TextAndSexpr => "sexpr", + DiagnosticFormat::Json => "json", + } + )); + let project_diagnostics_count = diagnostics_count(&primary_output); + let project_build_output = match &primary_output { + PrimaryOutput::Path { + kind: "native-executable", + path, + } => Some(*path), + _ => None, + }; + + match primary_output { + PrimaryOutput::NoOutput => { + out.push_str(" (primary-output\n"); + out.push_str(" (kind no-output)\n"); + out.push_str(" )\n"); + out.push_str(" (artifacts)"); + } + PrimaryOutput::Path { kind, path } => { + out.push_str(" (primary-output\n"); + out.push_str(&format!(" (kind {})\n", kind)); + out.push_str(&format!(" (path {})\n", diag::render_string(path))); + out.push_str(" )\n"); + out.push_str(" (artifacts\n"); + out.push_str(" (artifact\n"); + out.push_str(&format!(" (kind {})\n", kind)); + out.push_str(&format!(" (path {})\n", diag::render_string(path))); + out.push_str(" )\n"); + out.push_str(" )"); + } + PrimaryOutput::Stdout { kind, text } => { + out.push_str(" (primary-output\n"); + out.push_str(&format!(" (kind {})\n", kind)); + out.push_str(&format!(" (stdout {})\n", diag::render_string(text))); + out.push_str(" )\n"); + out.push_str(" (artifacts)"); + } + PrimaryOutput::Diagnostics { text } => { + out.push_str(" (primary-output\n"); + out.push_str(" (kind diagnostics)\n"); + out.push_str(&format!(" (stderr {})\n", diag::render_string(text))); + out.push_str(" )\n"); + out.push_str(" (artifacts\n"); + out.push_str(" (artifact\n"); + out.push_str(" (kind diagnostics)\n"); + out.push_str(" (stream stderr)\n"); + out.push_str(" )\n"); + out.push_str(" )"); + } + } + + if let Some(summary) = test_summary { + out.push('\n'); + out.push_str(" (test-report\n"); + out.push_str(&format!(" (total {})\n", summary.total_discovered)); + out.push_str(&format!( + " (total_discovered {})\n", + summary.total_discovered + )); + out.push_str(&format!(" (selected {})\n", summary.selected)); + out.push_str(&format!(" (passed {})\n", summary.passed)); + out.push_str(&format!(" (failed {})\n", summary.failed)); + out.push_str(&format!(" (skipped {})\n", summary.skipped)); + if let Some(filter) = summary.filter.as_deref() { + out.push_str(&format!(" (filter {})\n", diag::render_string(filter))); + } + out.push_str(" )"); + } + + if let Some(build) = build_info { + out.push('\n'); + out.push_str(" (hosted-build\n"); + out.push_str(&format!( + " (clang {})\n", + diag::render_string(build.clang) + )); + out.push_str(&format!( + " (runtime {})\n", + diag::render_string(&build.runtime.display().to_string()) + )); + out.push_str(" (c_link_inputs"); + if build.c_inputs.is_empty() { + out.push_str(")\n"); + } else { + out.push('\n'); + for input in build.c_inputs { + out.push_str(&format!(" (input {})\n", diag::render_string(input))); + } + out.push_str(" )\n"); + } + out.push_str(" )"); + } + + if !foreign_imports.is_empty() { + out.push('\n'); + render_c_imports(" ", foreign_imports, &mut out); + } + + if let Some(project) = project_info { + out.push('\n'); + out.push_str(" (project\n"); + out.push_str(&format!( + " (project_manifest {})\n", + diag::render_string(&project.manifest_path) + )); + out.push_str(&format!( + " (project_root {})\n", + diag::render_string(&project.project_root) + )); + out.push_str(&format!( + " (source_root {})\n", + diag::render_string(&project.source_root) + )); + out.push_str(&format!( + " (project_name {})\n", + diag::render_string(&project.project_name) + )); + out.push_str(&format!( + " (entry_module {})\n", + diag::render_string(&project.entry) + )); + if let Some(workspace) = &project.workspace { + out.push_str(" (workspace\n"); + out.push_str(&format!( + " (workspace_root {})\n", + diag::render_string(&workspace.workspace_root) + )); + out.push_str(&format!( + " (workspace_manifest {})\n", + diag::render_string(&workspace.workspace_manifest) + )); + out.push_str(" (members"); + if workspace.members.is_empty() { + out.push_str(")\n"); + } else { + out.push('\n'); + for member in &workspace.members { + out.push_str(&format!( + " (member {})\n", + diag::render_string(member) + )); + } + out.push_str(" )\n"); + } + out.push_str(" (packages"); + if workspace.packages.is_empty() { + out.push_str(")\n"); + } else { + out.push('\n'); + for package in &workspace.packages { + out.push_str(" (package\n"); + out.push_str(&format!( + " (name {})\n", + diag::render_string(&package.name) + )); + out.push_str(&format!( + " (version {})\n", + diag::render_string(&package.version) + )); + out.push_str(&format!( + " (root {})\n", + diag::render_string(&package.root) + )); + out.push_str(&format!( + " (manifest {})\n", + diag::render_string(&package.manifest) + )); + out.push_str(&format!( + " (source_root {})\n", + diag::render_string(&package.source_root) + )); + out.push_str(&format!( + " (entry {})\n", + diag::render_string(&package.entry) + )); + out.push_str(&format!(" (test_count {})\n", package.test_count)); + out.push_str(" (modules"); + if package.modules.is_empty() { + out.push_str(")\n"); + } else { + out.push('\n'); + for module in &package.modules { + out.push_str(" (module\n"); + out.push_str(&format!( + " (name {})\n", + diag::render_string(&module.name) + )); + out.push_str(&format!( + " (path {})\n", + diag::render_string(&module.path) + )); + out.push_str(" (imports"); + if module.imports.is_empty() { + out.push_str(")\n"); + } else { + out.push('\n'); + for import in &module.imports { + out.push_str(&format!( + " (import {})\n", + diag::render_string(import) + )); + } + out.push_str(" )\n"); + } + if !module.c_imports.is_empty() { + render_c_imports(" ", &module.c_imports, &mut out); + } + out.push_str(" )\n"); + } + out.push_str(" )\n"); + } + out.push_str(" )\n"); + } + out.push_str(" )\n"); + } + out.push_str(" (package_dependency_edges"); + if workspace.dependencies.is_empty() { + out.push_str(")\n"); + } else { + out.push('\n'); + for dependency in &workspace.dependencies { + out.push_str(" (package_dependency\n"); + out.push_str(&format!( + " (from {})\n", + diag::render_string(&dependency.from) + )); + out.push_str(&format!( + " (to {})\n", + diag::render_string(&dependency.to) + )); + out.push_str(&format!( + " (kind {})\n", + diag::render_string(&dependency.kind) + )); + out.push_str(&format!( + " (path {})\n", + diag::render_string(&dependency.path) + )); + out.push_str(" )\n"); + } + out.push_str(" )\n"); + } + match &workspace.selected_build_entry_package { + Some(package) => out.push_str(&format!( + " (selected_build_entry_package {})\n", + diag::render_string(package) + )), + None => out.push_str(" (selected_build_entry_package)\n"), + } + out.push_str(" )\n"); + } + out.push_str(" (modules\n"); + for module in &project.modules { + out.push_str(" (module\n"); + out.push_str(&format!( + " (name {})\n", + diag::render_string(&module.name) + )); + out.push_str(&format!( + " (path {})\n", + diag::render_string(&module.path) + )); + out.push_str(" (imports"); + if module.imports.is_empty() { + out.push(')'); + } else { + out.push('\n'); + for import in &module.imports { + out.push_str(&format!( + " (import {})\n", + diag::render_string(import) + )); + } + out.push_str(" )"); + } + out.push('\n'); + if !module.c_imports.is_empty() { + render_c_imports(" ", &module.c_imports, &mut out); + } + out.push_str(" )\n"); + } + out.push_str(" )\n"); + out.push_str(" (import_edges"); + let mut has_import_edges = false; + for module in &project.modules { + for import in &module.imports { + if !has_import_edges { + out.push('\n'); + has_import_edges = true; + } + out.push_str(" (import_edge\n"); + out.push_str(&format!( + " (from {})\n", + diag::render_string(&module.name) + )); + out.push_str(&format!(" (to {})\n", diag::render_string(import))); + out.push_str(" )\n"); + } + } + if has_import_edges { + out.push_str(" )\n"); + } else { + out.push_str(")\n"); + } + out.push_str(&format!( + " (diagnostics_count {})\n", + project_diagnostics_count + )); + if project_diagnostics_count > 0 { + out.push_str(" (diagnostic_artifacts\n"); + out.push_str(" (artifact\n"); + out.push_str(" (kind diagnostics)\n"); + out.push_str(" (stream stderr)\n"); + out.push_str(" )\n"); + out.push_str(" )\n"); + } else { + out.push_str(" (diagnostic_artifacts)\n"); + } + if let Some(path) = project_build_output { + out.push_str(" (build_outputs\n"); + out.push_str(&format!(" (output {})\n", diag::render_string(path))); + out.push_str(" )\n"); + } else { + out.push_str(" (build_outputs)\n"); + } + out.push_str(" )"); + } + + out.push_str("\n)\n"); + out +} + +fn diagnostics_count(primary_output: &PrimaryOutput<'_>) -> usize { + match primary_output { + PrimaryOutput::Diagnostics { text } => { + let sexpr_count = text.matches("(diagnostic\n").count(); + if sexpr_count > 0 { + sexpr_count + } else { + text.lines().filter(|line| !line.trim().is_empty()).count() + } + } + _ => 0, + } +} + +fn parse_test_count(output: &str, suffix: &str) -> Option { + output + .lines() + .rev() + .find_map(|line| line.strip_suffix(suffix)?.parse().ok()) +} + +fn runtime_path() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")).join("../runtime/runtime.c") +} + +fn c_imports_for_manifest(file: &str, source: &str) -> Vec { + let Ok(tokens) = lexer::lex(file, source) else { + return Vec::new(); + }; + let Ok(forms) = sexpr::parse(file, &tokens) else { + return Vec::new(); + }; + let Ok(program) = lower::lower_program(file, &forms) else { + return Vec::new(); + }; + program + .c_imports + .iter() + .map(|import| project::project_artifact_c_import(&program.module, import)) + .collect() +} + +fn render_c_imports(indent: &str, imports: &[project::ProjectArtifactCImport], out: &mut String) { + out.push_str(indent); + out.push_str("(foreign_imports"); + if imports.is_empty() { + out.push_str(")\n"); + return; + } + out.push('\n'); + for import in imports { + out.push_str(indent); + out.push_str(" (foreign_import\n"); + out.push_str(&format!( + "{} (name {})\n", + indent, + diag::render_string(&import.name) + )); + out.push_str(&format!( + "{} (source_module {})\n", + indent, + diag::render_string(&import.source_module) + )); + out.push_str(&format!( + "{} (symbol {})\n", + indent, + diag::render_string(&import.symbol) + )); + out.push_str(indent); + out.push_str(" (params"); + if import.params.is_empty() { + out.push_str(")\n"); + } else { + out.push('\n'); + for param in &import.params { + out.push_str(&format!( + "{} (param {})\n", + indent, + diag::render_string(param) + )); + } + out.push_str(indent); + out.push_str(" )\n"); + } + out.push_str(&format!( + "{} (return {})\n", + indent, + diag::render_string(&import.return_type) + )); + out.push_str(&format!( + "{} (abi {})\n", + indent, + diag::render_string(&import.abi) + )); + out.push_str(indent); + out.push_str(" )\n"); + } + out.push_str(indent); + out.push_str(")\n"); +} + +fn same_output_path(left: &str, right: &str) -> bool { + match (normalized_output_path(left), normalized_output_path(right)) { + (Some(left), Some(right)) => left == right, + _ => left == right, + } +} + +fn normalized_output_path(path: &str) -> Option { + let path = Path::new(path); + + if let Ok(canonical) = fs::canonicalize(path) { + return Some(canonical); + } + + let parent = path + .parent() + .filter(|parent| !parent.as_os_str().is_empty()) + .unwrap_or_else(|| Path::new(".")); + let mut normalized = fs::canonicalize(parent).ok()?; + normalized.push(path.file_name()?); + Some(normalized) +} + +fn print_usage() { + eprintln!( + "usage: glagol [check|fmt|test|build] [--json-diagnostics] [--no-color] [--manifest ] [--link-c ] [-o ] [--filter ] \n glagol fmt [--check|--write] \n glagol new [--name ]\n glagol doc -o \n glagol [--emit=llvm|--format|--print-tree|--inspect-lowering=surface|--inspect-lowering=checked|--check-tests|--run-tests] [--json-diagnostics] [--no-color] [-o ] [--manifest ] [--filter ] \n glagol --version" + ); +} diff --git a/compiler/src/project.rs b/compiler/src/project.rs new file mode 100644 index 0000000..c530030 --- /dev/null +++ b/compiler/src/project.rs @@ -0,0 +1,4330 @@ +use std::{ + collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque}, + env, fs, io, + path::{Path, PathBuf}, +}; + +use crate::{ + ast::{MatchPatternKind, Program}, + check::{ + self, CheckedFunction, CheckedProgram, ExternalEnum, ExternalEnumVariant, ExternalFunction, + ExternalStruct, TExpr, TExprKind, + }, + diag::Diagnostic, + lexer, llvm, lower, + sexpr::{Atom, SExpr, SExprKind}, + std_runtime, test_runner, + token::Span, + types::Type, +}; + +pub struct ProjectOutput { + pub text: String, + pub sources: Vec, + pub artifact: ProjectArtifact, +} + +pub struct ProjectTestSuccess { + pub output: String, + pub report: test_runner::TestReport, + pub sources: Vec, + pub artifact: ProjectArtifact, +} + +pub struct ProjectTestFailure { + pub diagnostics: Vec, + pub report: Option, + pub sources: Vec, + pub artifact: Option, +} + +pub struct ToolProject { + pub sources: Vec, + pub artifact: ProjectArtifact, +} + +pub struct ToolFailure { + pub diagnostics: Vec, + pub sources: Vec, + pub artifact: Option, +} + +#[derive(Debug, Clone)] +pub struct SourceFile { + pub path: String, + pub source: String, +} + +#[derive(Debug, Clone)] +pub struct ProjectArtifact { + pub manifest_path: String, + pub project_root: String, + pub source_root: String, + pub project_name: String, + pub entry: String, + pub modules: Vec, + pub workspace: Option, +} + +#[derive(Debug, Clone)] +pub struct ProjectArtifactModule { + pub name: String, + pub path: String, + pub imports: Vec, + pub c_imports: Vec, +} + +#[derive(Debug, Clone)] +pub struct ProjectArtifactCImport { + pub source_module: String, + pub name: String, + pub symbol: String, + pub params: Vec, + pub return_type: String, + pub abi: String, +} + +#[derive(Debug, Clone)] +pub struct WorkspaceArtifact { + pub workspace_root: String, + pub workspace_manifest: String, + pub members: Vec, + pub packages: Vec, + pub dependencies: Vec, + pub selected_build_entry_package: Option, +} + +#[derive(Debug, Clone)] +pub struct WorkspaceArtifactPackage { + pub name: String, + pub version: String, + pub root: String, + pub manifest: String, + pub source_root: String, + pub entry: String, + pub modules: Vec, + pub test_count: usize, +} + +#[derive(Debug, Clone)] +pub struct WorkspaceArtifactDependency { + pub from: String, + pub to: String, + pub kind: String, + pub path: String, +} + +#[derive(Clone)] +struct Manifest { + path: PathBuf, + source: String, + project_root: PathBuf, + name: String, + source_root: String, + entry: String, +} + +#[derive(Clone)] +struct WorkspaceManifest { + path: PathBuf, + source: String, + root: PathBuf, + members: Vec, +} + +#[derive(Clone)] +struct PackageManifest { + path: PathBuf, + root: PathBuf, + member: String, + name: String, + version: String, + source_root: String, + entry: String, + dependencies: Vec, +} + +#[derive(Clone)] +struct PackageDependency { + key: String, + path: String, + span: Span, +} + +#[derive(Clone)] +struct PackageUnit { + manifest: PackageManifest, + modules: Vec, + dependency_indices: Vec, +} + +#[derive(Debug, Clone)] +struct WorkspaceImportBinding { + provider_package: usize, + provider_module: usize, + kind: DeclKind, + import_span: Span, +} + +#[derive(Clone)] +struct ModuleUnit { + name: String, + file: String, + exports: Vec, + imports: Vec, + program: Program, + local_functions: HashMap, + local_structs: HashMap, + local_enums: HashMap, +} + +#[derive(Debug, Clone)] +struct ImportDecl { + module: String, + module_span: Span, + names: Vec, +} + +#[derive(Debug, Clone)] +struct NameSpan { + name: String, + span: Span, +} + +#[derive(Debug, Clone)] +struct DeclInfo { + span: Span, + kind: DeclKind, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum DeclKind { + Function, + CImport, + Struct, + Enum, +} + +#[derive(Debug, Clone)] +struct ImportBinding { + provider: String, + kind: DeclKind, + import_span: Span, +} + +pub fn is_project_input(path: &str) -> bool { + let path = Path::new(path); + if path.is_dir() { + return path.join("slovo.toml").is_file(); + } + path.file_name().and_then(|name| name.to_str()) == Some("slovo.toml") && path.is_file() +} + +pub fn load_project_sources_for_tools(input: &str) -> Result { + let manifest_path = manifest_path(input); + let manifest_file = manifest_path.display().to_string(); + let manifest_source = fs::read_to_string(&manifest_path).map_err(|err| ToolFailure { + diagnostics: vec![Diagnostic::new( + &manifest_file, + "ProjectManifestReadFailed", + format!("cannot read project manifest `{}`: {}", manifest_file, err), + )], + sources: Vec::new(), + artifact: None, + })?; + + if is_workspace_manifest_source(&manifest_source) { + return load_workspace_sources_for_tools(manifest_path, manifest_source); + } + + let manifest = + parse_manifest(manifest_path, manifest_source.clone()).map_err(|diagnostics| { + ToolFailure { + diagnostics, + sources: vec![SourceFile { + path: manifest_file.clone(), + source: manifest_source.clone(), + }], + artifact: None, + } + })?; + + let mut sources = vec![SourceFile { + path: manifest.path.display().to_string(), + source: manifest.source.clone(), + }]; + let artifact = empty_artifact(&manifest); + let source_root = manifest.project_root.join(&manifest.source_root); + if !source_root.is_dir() { + return Err(ToolFailure { + diagnostics: vec![Diagnostic::new( + &manifest.path.display().to_string(), + "ProjectSourceRootMissing", + format!( + "project source root `{}` does not exist", + source_root.display() + ), + )], + sources, + artifact: Some(artifact), + }); + } + if let Err(diagnostic) = validate_source_root_boundary(&manifest, &source_root) { + return Err(ToolFailure { + diagnostics: vec![diagnostic], + sources, + artifact: Some(artifact), + }); + } + let canonical_source_root = fs::canonicalize(&source_root).ok(); + + let module_paths = discover_module_paths(&source_root).map_err(|err| ToolFailure { + diagnostics: vec![Diagnostic::new( + &manifest.path.display().to_string(), + "ProjectSourceReadFailed", + format!( + "cannot read source root `{}`: {}", + source_root.display(), + err + ), + )], + sources: sources.clone(), + artifact: Some(artifact.clone()), + })?; + if module_paths.is_empty() { + return Err(ToolFailure { + diagnostics: vec![Diagnostic::new( + &manifest.path.display().to_string(), + "ProjectSourceReadFailed", + format!( + "project source root `{}` contains no `.slo` modules", + source_root.display() + ), + )], + sources, + artifact: Some(artifact), + }); + } + + let mut modules = Vec::new(); + let mut diagnostics = Vec::new(); + for path in module_paths { + let file = path.display().to_string(); + if let Some(root) = &canonical_source_root { + match fs::canonicalize(&path) { + Ok(canonical) if canonical.starts_with(root) => {} + Ok(_) => { + diagnostics.push(Diagnostic::new( + &manifest.path.display().to_string(), + "ProjectManifestInvalid", + format!("project module `{}` escapes the source root", file), + )); + continue; + } + Err(err) => { + diagnostics.push(Diagnostic::new( + &manifest.path.display().to_string(), + "ProjectSourceReadFailed", + format!("cannot canonicalize module `{}`: {}", file, err), + )); + continue; + } + } + } + match fs::read_to_string(&path) { + Ok(source) => { + sources.push(SourceFile { + path: file.clone(), + source: source.clone(), + }); + modules.push(ProjectArtifactModule { + name: path + .file_stem() + .and_then(|stem| stem.to_str()) + .unwrap_or("") + .to_string(), + path: file, + imports: tool_imports(&source), + c_imports: tool_c_imports(&source), + }); + } + Err(err) => diagnostics.push(Diagnostic::new( + &manifest.path.display().to_string(), + "ProjectSourceReadFailed", + format!("cannot read module `{}`: {}", file, err), + )), + } + } + load_tool_standard_import_modules(&manifest, &mut modules, &mut sources, &mut diagnostics); + + let artifact = ProjectArtifact { + modules, + ..artifact + }; + if diagnostics.is_empty() { + Ok(ToolProject { + sources: sources.into_iter().skip(1).collect(), + artifact, + }) + } else { + Err(ToolFailure { + diagnostics, + sources, + artifact: Some(artifact), + }) + } +} + +pub fn compile_to_llvm(input: &str) -> Result { + let checked = load_checked_project(input, true).map_err(|failure| ProjectTestFailure { + diagnostics: failure.diagnostics, + report: None, + sources: failure.sources, + artifact: failure.artifact, + })?; + let llvm = + llvm::emit(&checked.artifact.manifest_path, &checked.program).map_err(|diagnostics| { + ProjectTestFailure { + diagnostics, + report: None, + sources: checked.sources.clone(), + artifact: Some(checked.artifact.clone()), + } + })?; + + Ok(ProjectOutput { + text: llvm, + sources: checked.sources, + artifact: checked.artifact, + }) +} + +pub fn check_project(input: &str) -> Result { + let checked = load_checked_project(input, false).map_err(|failure| ProjectTestFailure { + diagnostics: failure.diagnostics, + report: None, + sources: failure.sources, + artifact: failure.artifact, + })?; + llvm::emit(&checked.artifact.manifest_path, &checked.program).map_err(|diagnostics| { + ProjectTestFailure { + diagnostics, + report: None, + sources: checked.sources.clone(), + artifact: Some(checked.artifact.clone()), + } + })?; + + Ok(ProjectOutput { + text: String::new(), + sources: checked.sources, + artifact: checked.artifact, + }) +} + +pub fn run_tests( + input: &str, + filter: Option<&str>, +) -> Result { + let checked = load_checked_project(input, false).map_err(|failure| ProjectTestFailure { + diagnostics: failure.diagnostics, + report: None, + sources: failure.sources, + artifact: failure.artifact, + })?; + + match test_runner::run(&checked.artifact.manifest_path, &checked.program, filter) { + Ok(success) => Ok(ProjectTestSuccess { + output: success.output, + report: success.report, + sources: checked.sources, + artifact: checked.artifact, + }), + Err(failure) => Err(ProjectTestFailure { + diagnostics: failure.diagnostics, + report: failure.report, + sources: checked.sources, + artifact: Some(checked.artifact), + }), + } +} + +struct CheckedProject { + program: CheckedProgram, + sources: Vec, + artifact: ProjectArtifact, +} + +struct ProjectLoadFailure { + diagnostics: Vec, + sources: Vec, + artifact: Option, +} + +fn load_checked_project( + input: &str, + require_entry_wrapper: bool, +) -> Result { + let manifest_path = manifest_path(input); + let manifest_file = manifest_path.display().to_string(); + let manifest_source = match fs::read_to_string(&manifest_path) { + Ok(source) => source, + Err(err) => { + return Err(ProjectLoadFailure { + diagnostics: vec![Diagnostic::new( + &manifest_file, + "ProjectManifestReadFailed", + format!("cannot read project manifest `{}`: {}", manifest_file, err), + )], + sources: Vec::new(), + artifact: None, + }); + } + }; + + if is_workspace_manifest_source(&manifest_source) { + return load_checked_workspace(manifest_path, manifest_source, require_entry_wrapper); + } + + let manifest = match parse_manifest(manifest_path, manifest_source.clone()) { + Ok(manifest) => manifest, + Err(diagnostics) => { + return Err(ProjectLoadFailure { + sources: vec![SourceFile { + path: manifest_file, + source: manifest_source, + }], + diagnostics, + artifact: None, + }); + } + }; + + let mut sources = vec![SourceFile { + path: manifest.path.display().to_string(), + source: manifest.source.clone(), + }]; + + let mut artifact = empty_artifact(&manifest); + let source_root = manifest.project_root.join(&manifest.source_root); + let source_root_file = source_root.display().to_string(); + if !source_root.is_dir() { + return Err(ProjectLoadFailure { + diagnostics: vec![Diagnostic::new( + &manifest.path.display().to_string(), + "ProjectSourceRootMissing", + format!("project source root `{}` does not exist", source_root_file), + )], + sources, + artifact: Some(artifact), + }); + } + + if let Err(diagnostic) = validate_source_root_boundary(&manifest, &source_root) { + return Err(ProjectLoadFailure { + diagnostics: vec![diagnostic], + sources, + artifact: Some(artifact), + }); + } + let canonical_source_root = fs::canonicalize(&source_root).ok(); + + let module_paths = match discover_module_paths(&source_root) { + Ok(paths) => paths, + Err(err) => { + return Err(ProjectLoadFailure { + diagnostics: vec![Diagnostic::new( + &manifest.path.display().to_string(), + "ProjectSourceReadFailed", + format!("cannot read source root `{}`: {}", source_root_file, err), + )], + sources, + artifact: Some(artifact), + }); + } + }; + + if module_paths.is_empty() { + return Err(ProjectLoadFailure { + diagnostics: vec![Diagnostic::new( + &manifest.path.display().to_string(), + "ProjectSourceReadFailed", + format!( + "project source root `{}` contains no `.slo` modules", + source_root_file + ), + )], + sources, + artifact: Some(artifact), + }); + } + + let mut modules = Vec::new(); + let mut diagnostics = Vec::new(); + for path in module_paths { + let file = path.display().to_string(); + if let Some(root) = &canonical_source_root { + match fs::canonicalize(&path) { + Ok(canonical) if canonical.starts_with(root) => {} + Ok(_) => { + diagnostics.push(Diagnostic::new( + &manifest.path.display().to_string(), + "ProjectManifestInvalid", + format!("project module `{}` escapes the source root", file), + )); + continue; + } + Err(err) => { + diagnostics.push(Diagnostic::new( + &manifest.path.display().to_string(), + "ProjectSourceReadFailed", + format!("cannot canonicalize module `{}`: {}", file, err), + )); + continue; + } + } + } + match fs::read_to_string(&path) { + Ok(source) => { + sources.push(SourceFile { + path: file.clone(), + source: source.clone(), + }); + match parse_module(&path, file, source) { + Ok(module) => modules.push(module), + Err(mut errs) => diagnostics.append(&mut errs), + } + } + Err(err) => diagnostics.push(Diagnostic::new( + &manifest.path.display().to_string(), + "ProjectSourceReadFailed", + format!("cannot read module `{}`: {}", file, err), + )), + } + } + load_standard_import_modules(&manifest, &mut modules, &mut sources, &mut diagnostics); + + artifact = artifact_from_modules(&manifest, &modules, None); + + if !diagnostics.is_empty() { + return Err(ProjectLoadFailure { + diagnostics, + sources, + artifact: Some(artifact), + }); + } + + match resolve_and_check(manifest, modules, sources, require_entry_wrapper) { + Ok(checked) => Ok(checked), + Err(failure) => Err(failure), + } +} + +struct WorkspaceLoaded { + workspace: WorkspaceManifest, + packages: Vec, + sources: Vec, + artifact: ProjectArtifact, +} + +fn is_workspace_manifest_source(source: &str) -> bool { + manifest_lines(source) + .iter() + .any(|line| line.text.trim() == "[workspace]") +} + +fn load_workspace_sources_for_tools( + manifest_path: PathBuf, + manifest_source: String, +) -> Result { + match load_workspace_base(manifest_path, manifest_source, None) { + Ok(loaded) => Ok(ToolProject { + sources: loaded + .sources + .into_iter() + .filter(|source| source.path.ends_with(".slo")) + .collect(), + artifact: loaded.artifact, + }), + Err(failure) => Err(ToolFailure { + diagnostics: failure.diagnostics, + sources: failure.sources, + artifact: failure.artifact, + }), + } +} + +fn load_checked_workspace( + manifest_path: PathBuf, + manifest_source: String, + require_entry_wrapper: bool, +) -> Result { + let loaded = load_workspace_base(manifest_path, manifest_source, None)?; + resolve_and_check_workspace(loaded, require_entry_wrapper) +} + +fn load_workspace_base( + manifest_path: PathBuf, + manifest_source: String, + selected_build_entry_package: Option, +) -> Result { + let manifest_file = manifest_path.display().to_string(); + let workspace = match parse_workspace_manifest(manifest_path, manifest_source.clone()) { + Ok(workspace) => workspace, + Err(diagnostics) => { + return Err(ProjectLoadFailure { + sources: vec![SourceFile { + path: manifest_file, + source: manifest_source, + }], + diagnostics, + artifact: None, + }); + } + }; + + let mut sources = vec![SourceFile { + path: workspace.path.display().to_string(), + source: workspace.source.clone(), + }]; + let mut diagnostics = Vec::new(); + let mut packages = Vec::new(); + + for member in &workspace.members { + let package_root = workspace.root.join(member); + let package_manifest = package_root.join("slovo.toml"); + let package_file = package_manifest.display().to_string(); + let package_source = match fs::read_to_string(&package_manifest) { + Ok(source) => source, + Err(err) => { + diagnostics.push(Diagnostic::new( + &workspace.path.display().to_string(), + "WorkspaceMemberManifestMissing", + format!( + "workspace member `{}` must contain `slovo.toml`: {}", + member, err + ), + )); + continue; + } + }; + sources.push(SourceFile { + path: package_file.clone(), + source: package_source.clone(), + }); + + let manifest = match parse_package_manifest( + package_manifest, + package_source, + package_root, + member.clone(), + ) { + Ok(manifest) => manifest, + Err(mut errs) => { + diagnostics.append(&mut errs); + continue; + } + }; + + if let Err(diagnostic) = validate_package_root_boundary(&workspace, &manifest) { + diagnostics.push(diagnostic); + continue; + } + + let source_root = manifest.root.join(&manifest.source_root); + let source_root_file = source_root.display().to_string(); + if !source_root.is_dir() { + diagnostics.push(Diagnostic::new( + &manifest.path.display().to_string(), + "PackageSourceRootMissing", + format!("package source root `{}` does not exist", source_root_file), + )); + continue; + } + if let Err(diagnostic) = validate_package_source_root_boundary(&manifest, &source_root) { + diagnostics.push(diagnostic); + continue; + } + let canonical_source_root = fs::canonicalize(&source_root).ok(); + let module_paths = match discover_module_paths(&source_root) { + Ok(paths) => paths, + Err(err) => { + diagnostics.push(Diagnostic::new( + &manifest.path.display().to_string(), + "PackageSourceReadFailed", + format!("cannot read source root `{}`: {}", source_root_file, err), + )); + continue; + } + }; + if module_paths.is_empty() { + diagnostics.push(Diagnostic::new( + &manifest.path.display().to_string(), + "PackageSourceReadFailed", + format!( + "package source root `{}` contains no `.slo` modules", + source_root_file + ), + )); + continue; + } + + let mut modules = Vec::new(); + for path in module_paths { + let file = path.display().to_string(); + if let Some(root) = &canonical_source_root { + match fs::canonicalize(&path) { + Ok(canonical) if canonical.starts_with(root) => {} + Ok(_) => { + diagnostics.push(Diagnostic::new( + &manifest.path.display().to_string(), + "PackageManifestInvalid", + format!("package module `{}` escapes the source root", file), + )); + continue; + } + Err(err) => { + diagnostics.push(Diagnostic::new( + &manifest.path.display().to_string(), + "PackageSourceReadFailed", + format!("cannot canonicalize module `{}`: {}", file, err), + )); + continue; + } + } + } + match fs::read_to_string(&path) { + Ok(source) => { + sources.push(SourceFile { + path: file.clone(), + source: source.clone(), + }); + match parse_module(&path, file, source) { + Ok(module) => modules.push(module), + Err(mut errs) => diagnostics.append(&mut errs), + } + } + Err(err) => diagnostics.push(Diagnostic::new( + &manifest.path.display().to_string(), + "PackageSourceReadFailed", + format!("cannot read module `{}`: {}", file, err), + )), + } + } + load_package_standard_import_modules( + &manifest, + &mut modules, + &mut sources, + &mut diagnostics, + ); + packages.push(PackageUnit { + manifest, + modules, + dependency_indices: Vec::new(), + }); + } + + validate_workspace_packages(&workspace, &mut packages, &mut diagnostics); + let artifact = workspace_artifact( + &workspace, + &packages, + None, + None, + selected_build_entry_package, + ); + + if !diagnostics.is_empty() { + return Err(ProjectLoadFailure { + diagnostics, + sources, + artifact: Some(artifact), + }); + } + + Ok(WorkspaceLoaded { + workspace, + packages, + sources, + artifact, + }) +} + +fn manifest_path(input: &str) -> PathBuf { + let path = Path::new(input); + if path.is_dir() { + path.join("slovo.toml") + } else { + path.to_path_buf() + } +} + +fn empty_artifact(manifest: &Manifest) -> ProjectArtifact { + ProjectArtifact { + manifest_path: manifest.path.display().to_string(), + project_root: manifest.project_root.display().to_string(), + source_root: manifest.source_root.clone(), + project_name: manifest.name.clone(), + entry: manifest.entry.clone(), + modules: Vec::new(), + workspace: None, + } +} + +fn artifact_from_modules( + manifest: &Manifest, + modules: &[ModuleUnit], + order: Option<&[usize]>, +) -> ProjectArtifact { + let module_indices = order + .map(|order| order.to_vec()) + .unwrap_or_else(|| (0..modules.len()).collect()); + ProjectArtifact { + manifest_path: manifest.path.display().to_string(), + project_root: manifest.project_root.display().to_string(), + source_root: manifest.source_root.clone(), + project_name: manifest.name.clone(), + entry: manifest.entry.clone(), + modules: module_indices + .iter() + .map(|index| ProjectArtifactModule { + name: modules[*index].name.clone(), + path: modules[*index].file.clone(), + imports: modules[*index] + .imports + .iter() + .map(|import| import.module.clone()) + .collect(), + c_imports: modules[*index] + .program + .c_imports + .iter() + .map(|import| project_artifact_c_import(&modules[*index].name, import)) + .collect(), + }) + .collect(), + workspace: None, + } +} + +fn workspace_artifact( + workspace: &WorkspaceManifest, + packages: &[PackageUnit], + package_order: Option<&[usize]>, + module_orders: Option<&HashMap>>, + selected_build_entry_package: Option, +) -> ProjectArtifact { + let mut package_indices = package_order + .map(|order| order.to_vec()) + .unwrap_or_else(|| (0..packages.len()).collect()); + if package_order.is_none() { + package_indices.sort_by(|left, right| { + packages[*left] + .manifest + .name + .cmp(&packages[*right].manifest.name) + .then_with(|| { + packages[*left] + .manifest + .member + .cmp(&packages[*right].manifest.member) + }) + }); + } + + let mut flat_modules = Vec::new(); + let mut artifact_packages = Vec::new(); + for package_index in &package_indices { + let package = &packages[*package_index]; + let module_indices = module_orders + .and_then(|orders| orders.get(package_index)) + .cloned() + .unwrap_or_else(|| (0..package.modules.len()).collect()); + let modules = module_indices + .iter() + .map(|module_index| { + let module = &package.modules[*module_index]; + ProjectArtifactModule { + name: module.name.clone(), + path: module.file.clone(), + imports: module + .imports + .iter() + .map(|import| import.module.clone()) + .collect(), + c_imports: module + .program + .c_imports + .iter() + .map(|import| project_artifact_c_import(&module.name, import)) + .collect(), + } + }) + .collect::>(); + for module in &modules { + let qualified_module = format!("{}.{}", package.manifest.name, module.name); + flat_modules.push(ProjectArtifactModule { + name: qualified_module.clone(), + path: module.path.clone(), + imports: module.imports.clone(), + c_imports: module + .c_imports + .iter() + .map(|import| { + let mut import = import.clone(); + import.source_module = qualified_module.clone(); + import + }) + .collect(), + }); + } + artifact_packages.push(WorkspaceArtifactPackage { + name: package.manifest.name.clone(), + version: package.manifest.version.clone(), + root: package.manifest.root.display().to_string(), + manifest: package.manifest.path.display().to_string(), + source_root: package.manifest.source_root.clone(), + entry: package.manifest.entry.clone(), + test_count: package + .modules + .iter() + .map(|module| module.program.tests.len()) + .sum(), + modules, + }); + } + + let mut dependencies = Vec::new(); + for (from_index, package) in packages.iter().enumerate() { + for to_index in &package.dependency_indices { + dependencies.push(WorkspaceArtifactDependency { + from: packages[from_index].manifest.name.clone(), + to: packages[*to_index].manifest.name.clone(), + kind: "local-path".to_string(), + path: packages[*to_index].manifest.member.clone(), + }); + } + } + dependencies.sort_by(|left, right| { + left.from + .cmp(&right.from) + .then_with(|| left.to.cmp(&right.to)) + .then_with(|| left.path.cmp(&right.path)) + }); + + ProjectArtifact { + manifest_path: workspace.path.display().to_string(), + project_root: workspace.root.display().to_string(), + source_root: String::new(), + project_name: workspace + .root + .file_name() + .and_then(|name| name.to_str()) + .unwrap_or("workspace") + .to_string(), + entry: selected_build_entry_package + .clone() + .unwrap_or_else(|| "main".to_string()), + modules: flat_modules, + workspace: Some(WorkspaceArtifact { + workspace_root: workspace.root.display().to_string(), + workspace_manifest: workspace.path.display().to_string(), + members: workspace.members.clone(), + packages: artifact_packages, + dependencies, + selected_build_entry_package, + }), + } +} + +fn parse_manifest(path: PathBuf, source: String) -> Result> { + let file = path.display().to_string(); + let mut errors = Vec::new(); + let mut in_project = false; + let mut saw_project = false; + let mut name = None::; + let mut source_root = None::; + let mut entry = None::; + + for line in manifest_lines(&source) { + let trimmed = line.text.trim(); + if trimmed.is_empty() || trimmed.starts_with('#') { + continue; + } + if trimmed.starts_with('[') { + if trimmed == "[project]" { + in_project = true; + saw_project = true; + } else { + errors.push( + Diagnostic::new(&file, "ProjectManifestInvalid", "unknown manifest section") + .with_span(line.span), + ); + in_project = false; + } + continue; + } + + if !in_project { + errors.push( + Diagnostic::new( + &file, + "ProjectManifestInvalid", + "manifest keys must be inside `[project]`", + ) + .with_span(line.span), + ); + continue; + } + + let Some((key, value)) = trimmed.split_once('=') else { + errors.push( + Diagnostic::new(&file, "ProjectManifestInvalid", "manifest key must use `=`") + .with_span(line.span), + ); + continue; + }; + let key = key.trim(); + let value = value.trim(); + let parsed = match parse_manifest_string(value) { + Some(value) => value, + None => { + errors.push( + Diagnostic::new( + &file, + "ProjectManifestInvalid", + "manifest values must be strings", + ) + .with_span(line.span), + ); + continue; + } + }; + + match key { + "name" => set_manifest_key(&file, &mut errors, &mut name, parsed, line.span, "name"), + "source_root" => set_manifest_key( + &file, + &mut errors, + &mut source_root, + parsed, + line.span, + "source_root", + ), + "entry" => set_manifest_key(&file, &mut errors, &mut entry, parsed, line.span, "entry"), + _ => errors.push( + Diagnostic::new( + &file, + "ProjectManifestInvalid", + format!("unknown project manifest key `{}`", key), + ) + .with_span(line.span), + ), + } + } + + if !saw_project { + errors.push(Diagnostic::new( + &file, + "ProjectManifestInvalid", + "project manifest is missing `[project]` section", + )); + } + + let Some(name) = name else { + errors.push(Diagnostic::new( + &file, + "ProjectManifestInvalid", + "project manifest is missing required key `name`", + )); + return Err(errors); + }; + + if !is_project_name(&name) { + errors.push(Diagnostic::new( + &file, + "ProjectManifestInvalid", + "project name must be lowercase ASCII and may contain only `a-z`, `0-9`, and `-` after the first letter", + )); + } + + let source_root = source_root.unwrap_or_else(|| "src".to_string()); + if !is_safe_relative_source_root(&source_root) { + errors.push(Diagnostic::new( + &file, + "ProjectManifestInvalid", + "project source_root must be a relative path under the project root", + )); + } + + let entry = entry.unwrap_or_else(|| "main".to_string()); + if !is_flat_module_identifier(&entry) { + errors.push(Diagnostic::new( + &file, + "ProjectManifestInvalid", + "project entry must be a flat module identifier", + )); + } + + if !errors.is_empty() { + return Err(errors); + } + + let project_root = path + .parent() + .filter(|parent| !parent.as_os_str().is_empty()) + .unwrap_or_else(|| Path::new(".")) + .to_path_buf(); + + Ok(Manifest { + path, + source, + project_root, + name, + source_root, + entry, + }) +} + +fn parse_workspace_manifest( + path: PathBuf, + source: String, +) -> Result> { + let file = path.display().to_string(); + let mut errors = Vec::new(); + let mut in_workspace = false; + let mut saw_workspace = false; + let mut members = None::>; + + for line in manifest_lines(&source) { + let trimmed = line.text.trim(); + if trimmed.is_empty() || trimmed.starts_with('#') { + continue; + } + if trimmed.starts_with('[') { + if trimmed == "[workspace]" { + in_workspace = true; + saw_workspace = true; + } else { + errors.push( + Diagnostic::new( + &file, + "WorkspaceManifestInvalid", + "unknown workspace manifest section", + ) + .with_span(line.span), + ); + in_workspace = false; + } + continue; + } + + if !in_workspace { + errors.push( + Diagnostic::new( + &file, + "WorkspaceManifestInvalid", + "workspace manifest keys must be inside `[workspace]`", + ) + .with_span(line.span), + ); + continue; + } + + let Some((key, value)) = trimmed.split_once('=') else { + errors.push( + Diagnostic::new( + &file, + "WorkspaceManifestInvalid", + "workspace manifest key must use `=`", + ) + .with_span(line.span), + ); + continue; + }; + match key.trim() { + "members" => match parse_manifest_string_array(value.trim()) { + Some(parsed) if !parsed.is_empty() => { + if members.replace(parsed).is_some() { + errors.push( + Diagnostic::new( + &file, + "WorkspaceManifestInvalid", + "duplicate workspace manifest key `members`", + ) + .with_span(line.span), + ); + } + } + _ => errors.push( + Diagnostic::new( + &file, + "WorkspaceManifestInvalid", + "workspace members must be a non-empty array of strings", + ) + .with_span(line.span), + ), + }, + other => errors.push( + Diagnostic::new( + &file, + "WorkspaceManifestInvalid", + format!("unknown workspace manifest key `{}`", other), + ) + .with_span(line.span), + ), + } + } + + if !saw_workspace { + errors.push(Diagnostic::new( + &file, + "WorkspaceManifestInvalid", + "workspace manifest is missing `[workspace]` section", + )); + } + + let mut members = members.unwrap_or_else(|| { + errors.push(Diagnostic::new( + &file, + "WorkspaceManifestInvalid", + "workspace manifest is missing required key `members`", + )); + Vec::new() + }); + + for member in &members { + if normalize_workspace_member(member).is_none() { + errors.push(Diagnostic::new( + &file, + "WorkspaceMemberPathEscape", + format!( + "workspace member path `{}` must be relative and stay under the workspace root", + member + ), + )); + } + } + members = members + .into_iter() + .filter_map(|member| normalize_workspace_member(&member)) + .collect(); + members.sort(); + + if !errors.is_empty() { + return Err(errors); + } + + let root = path + .parent() + .filter(|parent| !parent.as_os_str().is_empty()) + .unwrap_or_else(|| Path::new(".")) + .to_path_buf(); + + Ok(WorkspaceManifest { + path, + source, + root, + members, + }) +} + +fn parse_package_manifest( + path: PathBuf, + source: String, + root: PathBuf, + member: String, +) -> Result> { + let file = path.display().to_string(); + let mut errors = Vec::new(); + let mut section = ""; + let mut saw_package = false; + let mut saw_project = false; + let mut name = None::; + let mut version = None::; + let mut source_root = None::; + let mut entry = None::; + let mut dependencies = Vec::new(); + + for line in manifest_lines(&source) { + let trimmed = line.text.trim(); + if trimmed.is_empty() || trimmed.starts_with('#') { + continue; + } + if trimmed.starts_with('[') { + match trimmed { + "[package]" => { + section = "package"; + saw_package = true; + } + "[dependencies]" => section = "dependencies", + "[project]" => { + section = "project"; + saw_project = true; + errors.push( + Diagnostic::new( + &file, + "PackageManifestInvalid", + "workspace package manifest may not contain `[project]`", + ) + .with_span(line.span), + ); + } + _ => { + section = ""; + errors.push( + Diagnostic::new( + &file, + "PackageManifestInvalid", + "unknown package manifest section", + ) + .with_span(line.span), + ); + } + } + continue; + } + + let Some((key, value)) = trimmed.split_once('=') else { + errors.push( + Diagnostic::new( + &file, + "PackageManifestInvalid", + "package manifest key must use `=`", + ) + .with_span(line.span), + ); + continue; + }; + let key = key.trim(); + let value = value.trim(); + match section { + "package" => { + let parsed = match parse_manifest_string(value) { + Some(value) => value, + None => { + errors.push( + Diagnostic::new( + &file, + "PackageManifestInvalid", + "package manifest values must be strings", + ) + .with_span(line.span), + ); + continue; + } + }; + match key { + "name" => set_manifest_key( + &file, + &mut errors, + &mut name, + parsed, + line.span, + "name", + ), + "version" => set_manifest_key( + &file, + &mut errors, + &mut version, + parsed, + line.span, + "version", + ), + "source_root" => set_manifest_key( + &file, + &mut errors, + &mut source_root, + parsed, + line.span, + "source_root", + ), + "entry" => set_manifest_key( + &file, + &mut errors, + &mut entry, + parsed, + line.span, + "entry", + ), + other => errors.push( + Diagnostic::new( + &file, + "PackageManifestInvalid", + format!("unknown package manifest key `{}`", other), + ) + .with_span(line.span), + ), + } + } + "dependencies" => match parse_dependency_path(value) { + Some(path) => dependencies.push(PackageDependency { + key: key.to_string(), + path, + span: line.span, + }), + None => errors.push( + Diagnostic::new( + &file, + "UnsupportedDependency", + "workspace dependencies must use `{ path = \"...\" }` local path records only", + ) + .with_span(line.span), + ), + }, + "" | "project" => errors.push( + Diagnostic::new( + &file, + "PackageManifestInvalid", + "package manifest keys must be inside `[package]` or `[dependencies]`", + ) + .with_span(line.span), + ), + _ => unreachable!("package manifest parser section is known"), + } + } + + if saw_project && saw_package { + errors.push(Diagnostic::new( + &file, + "PackageManifestInvalid", + "package manifest may not combine `[project]` and `[package]`", + )); + } + if !saw_package { + errors.push(Diagnostic::new( + &file, + "PackageManifestInvalid", + "package manifest is missing `[package]` section", + )); + } + + let Some(name) = name else { + errors.push(Diagnostic::new( + &file, + "PackageManifestInvalid", + "package manifest is missing required key `name`", + )); + return Err(errors); + }; + let Some(version) = version else { + errors.push(Diagnostic::new( + &file, + "PackageManifestInvalid", + "package manifest is missing required key `version`", + )); + return Err(errors); + }; + + if !is_project_name(&name) { + errors.push(Diagnostic::new( + &file, + "InvalidPackageName", + "package name must start with `a-z` and contain only `a-z`, `0-9`, and `-`", + )); + } + if !is_package_version(&version) { + errors.push(Diagnostic::new( + &file, + "InvalidPackageVersion", + "package version must have literal `MAJOR.MINOR.PATCH` numeric shape", + )); + } + + let source_root = source_root.unwrap_or_else(|| "src".to_string()); + if !is_safe_relative_source_root(&source_root) { + errors.push(Diagnostic::new( + &file, + "PackageSourceRootEscape", + "package source_root must be a relative path under the package root", + )); + } + + let entry = entry.unwrap_or_else(|| "main".to_string()); + if !is_flat_module_identifier(&entry) { + errors.push(Diagnostic::new( + &file, + "PackageManifestInvalid", + "package entry must be a flat module identifier", + )); + } + + if !errors.is_empty() { + return Err(errors); + } + + Ok(PackageManifest { + path, + root, + member, + name, + version, + source_root, + entry, + dependencies, + }) +} + +struct ManifestLine<'a> { + text: &'a str, + span: Span, +} + +fn manifest_lines(source: &str) -> Vec> { + let mut lines = Vec::new(); + let mut offset = 0; + for line in source.split_inclusive('\n') { + let text = line.strip_suffix('\n').unwrap_or(line); + lines.push(ManifestLine { + text, + span: Span::new(offset, offset + text.len()), + }); + offset += line.len(); + } + if source.is_empty() { + lines.push(ManifestLine { + text: "", + span: Span::new(0, 0), + }); + } + lines +} + +fn parse_manifest_string(value: &str) -> Option { + let value = value.trim(); + let inner = value.strip_prefix('"')?.strip_suffix('"')?; + if inner.contains('"') || inner.contains('\\') { + return None; + } + Some(inner.to_string()) +} + +fn parse_manifest_string_array(value: &str) -> Option> { + let inner = value.trim().strip_prefix('[')?.strip_suffix(']')?.trim(); + if inner.is_empty() { + return Some(Vec::new()); + } + let mut values = Vec::new(); + for item in inner.split(',') { + values.push(parse_manifest_string(item.trim())?); + } + Some(values) +} + +fn parse_dependency_path(value: &str) -> Option { + let inner = value.trim().strip_prefix('{')?.strip_suffix('}')?.trim(); + let (key, value) = inner.split_once('=')?; + if key.trim() != "path" { + return None; + } + parse_manifest_string(value.trim()) +} + +fn set_manifest_key( + file: &str, + errors: &mut Vec, + slot: &mut Option, + value: String, + span: Span, + key: &str, +) { + if slot.replace(value).is_some() { + errors.push( + Diagnostic::new( + file, + "ProjectManifestInvalid", + format!("duplicate project manifest key `{}`", key), + ) + .with_span(span), + ); + } +} + +fn is_package_version(value: &str) -> bool { + let mut parts = value.split('.'); + let (Some(major), Some(minor), Some(patch), None) = + (parts.next(), parts.next(), parts.next(), parts.next()) + else { + return false; + }; + [major, minor, patch] + .iter() + .all(|part| !part.is_empty() && part.bytes().all(|byte| byte.is_ascii_digit())) +} + +fn is_project_name(value: &str) -> bool { + let mut chars = value.chars(); + let Some(first) = chars.next() else { + return false; + }; + first.is_ascii_lowercase() + && chars.all(|ch| ch.is_ascii_lowercase() || ch.is_ascii_digit() || ch == '-') +} + +fn is_flat_module_identifier(value: &str) -> bool { + let mut chars = value.chars(); + let Some(first) = chars.next() else { + return false; + }; + (first.is_ascii_alphabetic() || first == '_') + && chars.all(|ch| ch.is_ascii_alphanumeric() || ch == '_' || ch == '-') +} + +fn is_safe_relative_source_root(value: &str) -> bool { + if value.is_empty() { + return false; + } + let path = Path::new(value); + if path.is_absolute() { + return false; + } + path.components().all(|component| match component { + std::path::Component::Normal(name) => name.to_str() != Some("generated-source"), + std::path::Component::CurDir => true, + _ => false, + }) +} + +fn normalize_workspace_member(value: &str) -> Option { + normalize_rel_path(value, false) +} + +fn normalize_dependency_path( + workspace: &WorkspaceManifest, + package: &PackageManifest, + value: &str, +) -> Result { + if Path::new(value).is_absolute() || value.is_empty() { + return Err(Diagnostic::new( + &package.path.display().to_string(), + "DependencyPathEscape", + format!( + "dependency path `{}` must be relative and stay under the workspace root", + value + ), + )); + } + let combined = Path::new(&package.member).join(value); + let normalized = normalize_components(&combined).ok_or_else(|| { + Diagnostic::new( + &package.path.display().to_string(), + "DependencyPathEscape", + format!( + "dependency path `{}` must stay under the workspace root", + value + ), + ) + })?; + + let target = workspace.root.join(&normalized); + let workspace_root = fs::canonicalize(&workspace.root).ok(); + let target_root = fs::canonicalize(target).ok(); + if let (Some(workspace_root), Some(target_root)) = (workspace_root, target_root) { + if !target_root.starts_with(&workspace_root) { + return Err(Diagnostic::new( + &package.path.display().to_string(), + "DependencyPathEscape", + format!( + "dependency path `{}` must not escape the workspace root", + value + ), + )); + } + } + + Ok(normalized) +} + +fn normalize_rel_path(value: &str, allow_parent_within_root: bool) -> Option { + if value.is_empty() { + return None; + } + let path = Path::new(value); + if path.is_absolute() { + return None; + } + if !allow_parent_within_root + && path + .components() + .any(|component| matches!(component, std::path::Component::ParentDir)) + { + return None; + } + normalize_components(path) +} + +fn normalize_components(path: &Path) -> Option { + let mut components = Vec::new(); + for component in path.components() { + match component { + std::path::Component::CurDir => {} + std::path::Component::Normal(value) => { + let value = value.to_str()?; + if value.is_empty() { + return None; + } + components.push(value.to_string()); + } + std::path::Component::ParentDir => { + components.pop()?; + } + _ => return None, + } + } + if components.is_empty() { + return None; + } + Some(components.join("/")) +} + +fn validate_source_root_boundary( + manifest: &Manifest, + source_root: &Path, +) -> Result<(), Diagnostic> { + let project_root = fs::canonicalize(&manifest.project_root).map_err(|err| { + Diagnostic::new( + &manifest.path.display().to_string(), + "ProjectManifestInvalid", + format!( + "cannot canonicalize project root `{}`: {}", + manifest.project_root.display(), + err + ), + ) + })?; + let source_root = fs::canonicalize(source_root).map_err(|err| { + Diagnostic::new( + &manifest.path.display().to_string(), + "ProjectManifestInvalid", + format!( + "cannot canonicalize project source root `{}`: {}", + source_root.display(), + err + ), + ) + })?; + + if !source_root.starts_with(&project_root) { + return Err(Diagnostic::new( + &manifest.path.display().to_string(), + "ProjectManifestInvalid", + "project source_root must not escape the project root", + )); + } + + Ok(()) +} + +fn validate_package_root_boundary( + workspace: &WorkspaceManifest, + manifest: &PackageManifest, +) -> Result<(), Diagnostic> { + let workspace_root = fs::canonicalize(&workspace.root).map_err(|err| { + Diagnostic::new( + &workspace.path.display().to_string(), + "WorkspaceManifestInvalid", + format!( + "cannot canonicalize workspace root `{}`: {}", + workspace.root.display(), + err + ), + ) + })?; + let package_root = fs::canonicalize(&manifest.root).map_err(|err| { + Diagnostic::new( + &workspace.path.display().to_string(), + "WorkspaceManifestInvalid", + format!( + "cannot canonicalize workspace member `{}`: {}", + manifest.member, err + ), + ) + })?; + if !package_root.starts_with(&workspace_root) { + return Err(Diagnostic::new( + &workspace.path.display().to_string(), + "WorkspaceMemberPathEscape", + format!( + "workspace member `{}` must not escape the workspace root", + manifest.member + ), + )); + } + Ok(()) +} + +fn validate_package_source_root_boundary( + manifest: &PackageManifest, + source_root: &Path, +) -> Result<(), Diagnostic> { + let package_root = fs::canonicalize(&manifest.root).map_err(|err| { + Diagnostic::new( + &manifest.path.display().to_string(), + "PackageManifestInvalid", + format!( + "cannot canonicalize package root `{}`: {}", + manifest.root.display(), + err + ), + ) + })?; + let source_root = fs::canonicalize(source_root).map_err(|err| { + Diagnostic::new( + &manifest.path.display().to_string(), + "PackageManifestInvalid", + format!( + "cannot canonicalize package source root `{}`: {}", + source_root.display(), + err + ), + ) + })?; + if !source_root.starts_with(&package_root) { + return Err(Diagnostic::new( + &manifest.path.display().to_string(), + "PackageSourceRootEscape", + "package source_root must not escape the package root", + )); + } + Ok(()) +} + +fn discover_module_paths(source_root: &Path) -> io::Result> { + let mut paths = Vec::new(); + for entry in fs::read_dir(source_root)? { + let entry = entry?; + let path = entry.path(); + if path.is_file() && path.extension().and_then(|ext| ext.to_str()) == Some("slo") { + paths.push(path); + } + } + paths.sort_by(|left, right| left.file_name().cmp(&right.file_name())); + Ok(paths) +} + +fn load_tool_standard_import_modules( + manifest: &Manifest, + modules: &mut Vec, + sources: &mut Vec, + diagnostics: &mut Vec, +) { + let mut loaded = modules + .iter() + .map(|module| module.name.clone()) + .collect::>(); + let mut queue = standard_import_queue(modules.iter().flat_map(|module| module.imports.iter())); + + while let Some(import_name) = queue.pop_front() { + if !loaded.insert(import_name.clone()) { + continue; + } + + let Some(path) = standard_module_path(&manifest.project_root, &import_name) else { + diagnostics.push(missing_standard_module(manifest, &import_name)); + continue; + }; + let file = path.display().to_string(); + match fs::read_to_string(&path) { + Ok(source) => { + let imports = tool_imports(&source); + queue.extend( + imports + .iter() + .filter(|name| standard_module_leaf(name).is_some()) + .filter(|name| !loaded.contains(*name)) + .cloned(), + ); + sources.push(SourceFile { + path: file.clone(), + source: source.clone(), + }); + let mut c_imports = tool_c_imports(&source); + for c_import in &mut c_imports { + c_import.source_module = import_name.clone(); + } + modules.push(ProjectArtifactModule { + name: import_name, + path: file, + imports, + c_imports, + }); + } + Err(err) => diagnostics.push(Diagnostic::new( + &manifest.path.display().to_string(), + "ProjectSourceReadFailed", + format!("cannot read standard-library module `{}`: {}", file, err), + )), + } + } +} + +fn load_standard_import_modules( + manifest: &Manifest, + modules: &mut Vec, + sources: &mut Vec, + diagnostics: &mut Vec, +) { + let mut loaded = modules + .iter() + .map(|module| module.name.clone()) + .collect::>(); + let mut queue = standard_import_queue( + modules + .iter() + .flat_map(|module| module.imports.iter().map(|i| &i.module)), + ); + + while let Some(import_name) = queue.pop_front() { + if !loaded.insert(import_name.clone()) { + continue; + } + + let Some(path) = standard_module_path(&manifest.project_root, &import_name) else { + diagnostics.push(missing_standard_module(manifest, &import_name)); + continue; + }; + let file = path.display().to_string(); + match fs::read_to_string(&path) { + Ok(source) => { + sources.push(SourceFile { + path: file.clone(), + source: source.clone(), + }); + match parse_module(&path, file, source) { + Ok(mut module) => { + module.program.module = import_name.clone(); + module.name = import_name.clone(); + queue.extend( + module + .imports + .iter() + .map(|import| &import.module) + .filter(|name| standard_module_leaf(name).is_some()) + .filter(|name| !loaded.contains(*name)) + .cloned(), + ); + modules.push(module); + } + Err(mut errs) => diagnostics.append(&mut errs), + } + } + Err(err) => diagnostics.push(Diagnostic::new( + &manifest.path.display().to_string(), + "ProjectSourceReadFailed", + format!("cannot read standard-library module `{}`: {}", file, err), + )), + } + } +} + +fn load_package_standard_import_modules( + manifest: &PackageManifest, + modules: &mut Vec, + sources: &mut Vec, + diagnostics: &mut Vec, +) { + let mut loaded = modules + .iter() + .map(|module| module.name.clone()) + .collect::>(); + let mut queue = standard_import_queue( + modules + .iter() + .flat_map(|module| module.imports.iter().map(|i| &i.module)), + ); + + while let Some(import_name) = queue.pop_front() { + if !loaded.insert(import_name.clone()) { + continue; + } + + let Some(path) = standard_module_path(&manifest.root, &import_name) else { + diagnostics.push(missing_package_standard_module(manifest, &import_name)); + continue; + }; + let file = path.display().to_string(); + match fs::read_to_string(&path) { + Ok(source) => { + sources.push(SourceFile { + path: file.clone(), + source: source.clone(), + }); + match parse_module(&path, file, source) { + Ok(mut module) => { + module.program.module = import_name.clone(); + module.name = import_name.clone(); + queue.extend( + module + .imports + .iter() + .map(|import| &import.module) + .filter(|name| standard_module_leaf(name).is_some()) + .filter(|name| !loaded.contains(*name)) + .cloned(), + ); + modules.push(module); + } + Err(mut errs) => diagnostics.append(&mut errs), + } + } + Err(err) => diagnostics.push(Diagnostic::new( + &manifest.path.display().to_string(), + "PackageSourceReadFailed", + format!("cannot read standard-library module `{}`: {}", file, err), + )), + } + } +} + +fn standard_import_queue<'a>(imports: impl Iterator) -> VecDeque { + imports + .filter(|name| standard_module_leaf(name).is_some()) + .cloned() + .collect::>() + .into_iter() + .collect() +} + +fn standard_module_path(project_root: &Path, import_name: &str) -> Option { + let leaf = standard_module_leaf(import_name)?; + standard_library_candidates(project_root) + .into_iter() + .map(|root| root.join(format!("{}.slo", leaf))) + .find(|path| path.is_file()) +} + +fn standard_module_leaf(import_name: &str) -> Option<&str> { + let leaf = import_name.strip_prefix("std.")?; + if leaf.is_empty() + || leaf.contains('.') + || leaf.contains('/') + || leaf.contains('\\') + || !leaf + .bytes() + .all(|byte| byte.is_ascii_alphanumeric() || byte == b'_') + { + return None; + } + Some(leaf) +} + +fn standard_library_candidates(project_root: &Path) -> Vec { + let mut candidates = Vec::new(); + if let Some(paths) = env::var_os("SLOVO_STD_PATH") { + candidates.extend(env::split_paths(&paths)); + } + candidates.extend(installed_standard_library_candidates()); + for ancestor in project_root.ancestors() { + candidates.push(ancestor.join("std")); + candidates.push(ancestor.join("lib/std")); + candidates.push(ancestor.join("slovo/std")); + } + let compiler_root = Path::new(env!("CARGO_MANIFEST_DIR")); + for ancestor in compiler_root.ancestors() { + candidates.push(ancestor.join("std")); + candidates.push(ancestor.join("lib/std")); + candidates.push(ancestor.join("slovo/std")); + } + candidates +} + +fn installed_standard_library_candidates() -> Vec { + let Ok(exe) = env::current_exe() else { + return Vec::new(); + }; + let Some(bin_dir) = exe.parent() else { + return Vec::new(); + }; + vec![ + bin_dir.join("std"), + bin_dir.join("../std"), + bin_dir.join("../share/slovo/std"), + ] +} + +fn missing_standard_module(manifest: &Manifest, import_name: &str) -> Diagnostic { + Diagnostic::new( + &manifest.path.display().to_string(), + "MissingStandardLibraryModule", + format!( + "standard-library module `{}` could not be found", + import_name + ), + ) + .hint("set SLOVO_STD_PATH, install `share/slovo/std`, or run from a checkout that has `lib/std` sources") +} + +fn missing_package_standard_module(manifest: &PackageManifest, import_name: &str) -> Diagnostic { + Diagnostic::new( + &manifest.path.display().to_string(), + "MissingStandardLibraryModule", + format!( + "standard-library module `{}` could not be found", + import_name + ), + ) + .hint("set SLOVO_STD_PATH, install `share/slovo/std`, or run from a checkout that has `lib/std` sources") +} + +fn parse_module(path: &Path, file: String, source: String) -> Result> { + let tokens = lexer::lex(&file, &source)?; + let forms = crate::sexpr::parse(&file, &tokens)?; + let mut errors = Vec::new(); + let mut module_decl = None::<(String, Vec, SExpr)>; + let mut imports = Vec::new(); + let mut decls_started = false; + let mut lower_forms = Vec::new(); + + for (index, form) in forms.iter().enumerate() { + match head_ident(form) { + Some("module") => { + if index != 0 || module_decl.is_some() { + errors.push( + Diagnostic::new( + &file, + "InvalidModule", + "project module declaration must be the first top-level form", + ) + .with_span(form.span), + ); + continue; + } + match parse_module_decl(&file, form) { + Ok((name, exports, lowered_module)) => { + module_decl = Some((name, exports, lowered_module.clone())); + lower_forms.push(lowered_module); + } + Err(mut errs) => errors.append(&mut errs), + } + } + Some("import") => { + if module_decl.is_none() || decls_started { + errors.push( + Diagnostic::new( + &file, + "InvalidImport", + "imports must appear after the module declaration and before declarations", + ) + .with_span(form.span), + ); + continue; + } + match parse_import_decl(&file, form) { + Ok(import) => imports.push(import), + Err(mut errs) => errors.append(&mut errs), + } + } + _ => { + decls_started = true; + lower_forms.push(form.clone()); + } + } + } + + let Some((name, exports, _)) = module_decl else { + errors.push(Diagnostic::new( + &file, + "InvalidModule", + "project source must start with `(module name)` or `(module name (export ...))`", + )); + return Err(errors); + }; + + let expected_name = path + .file_stem() + .and_then(|stem| stem.to_str()) + .unwrap_or(""); + if expected_name != name { + errors.push( + Diagnostic::new( + &file, + "MissingImport", + format!( + "module declaration `{}` must match source file stem `{}`", + name, expected_name + ), + ) + .with_span(module_name_span(&lower_forms[0]).unwrap_or(lower_forms[0].span)), + ); + } + + let imported_names = imports + .iter() + .flat_map(|import| import.names.iter().map(|name| name.name.clone())) + .collect::>(); + let program = + match lower::lower_program_with_imported_names(&file, &lower_forms, &imported_names) { + Ok(program) => program, + Err(mut errs) => { + errors.append(&mut errs); + Program { + module: name.clone(), + enums: Vec::new(), + structs: Vec::new(), + c_imports: Vec::new(), + functions: Vec::new(), + tests: Vec::new(), + } + } + }; + + let (local_functions, local_structs, local_enums, mut duplicate_errors) = + local_declarations(&file, &program); + errors.append(&mut duplicate_errors); + + if errors.is_empty() { + Ok(ModuleUnit { + name, + file, + exports, + imports, + program, + local_functions, + local_structs, + local_enums, + }) + } else { + Err(errors) + } +} + +fn parse_module_decl( + file: &str, + form: &SExpr, +) -> Result<(String, Vec, SExpr), Vec> { + let mut errors = Vec::new(); + let Some(items) = expect_list(form) else { + return Err(vec![Diagnostic::new( + file, + "InvalidModule", + "invalid module form", + ) + .with_span(form.span)]); + }; + + if items.len() != 2 && items.len() != 3 { + return Err(vec![Diagnostic::new( + file, + "InvalidModule", + "project module form must be `(module name)` or `(module name (export ...))`", + ) + .with_span(form.span)]); + } + + let name = match expect_ident(&items[1]) { + Some(name) => name.to_string(), + None => { + errors.push( + Diagnostic::new( + file, + "InvalidModuleName", + "module name must be an identifier", + ) + .with_span(items[1].span), + ); + "".to_string() + } + }; + + let mut exports = Vec::new(); + if let Some(export_form) = items.get(2) { + let Some(export_items) = expect_list(export_form) else { + errors.push( + Diagnostic::new( + file, + "InvalidExport", + "export list must be `(export name...)`", + ) + .with_span(export_form.span), + ); + return Err(errors); + }; + if !matches!(export_items.first().and_then(expect_ident), Some("export")) { + errors.push( + Diagnostic::new( + file, + "InvalidExport", + "module option must be an export list", + ) + .with_span(export_form.span), + ); + } else { + let mut seen = HashMap::new(); + for item in &export_items[1..] { + match expect_ident(item) { + Some(name) => { + if let Some(original) = seen.insert(name.to_string(), item.span) { + errors.push( + Diagnostic::new( + file, + "DuplicateName", + format!("duplicate exported name `{}`", name), + ) + .with_span(item.span) + .related("original exported name", original), + ); + } + if is_reserved_intrinsic(name) { + errors.push( + Diagnostic::new( + file, + "Visibility", + format!("builtin or intrinsic `{}` cannot be exported", name), + ) + .with_span(item.span), + ); + } + exports.push(NameSpan { + name: name.to_string(), + span: item.span, + }); + } + None => errors.push( + Diagnostic::new( + file, + "InvalidExport", + "exported name must be an identifier", + ) + .with_span(item.span), + ), + } + } + } + } + + let lowered_module = SExpr { + kind: SExprKind::List(vec![items[0].clone(), items[1].clone()]), + span: form.span, + }; + + if errors.is_empty() { + Ok((name, exports, lowered_module)) + } else { + Err(errors) + } +} + +fn parse_import_decl(file: &str, form: &SExpr) -> Result> { + let mut errors = Vec::new(); + let Some(items) = expect_list(form) else { + return Err(vec![Diagnostic::new( + file, + "InvalidImport", + "invalid import form", + ) + .with_span(form.span)]); + }; + + if items.len() != 3 { + return Err(vec![Diagnostic::new( + file, + "InvalidImport", + "import form must be `(import module (name...))`", + ) + .with_span(form.span)]); + } + + let module = match expect_ident(&items[1]) { + Some(name) => name.to_string(), + None => { + errors.push( + Diagnostic::new(file, "InvalidImport", "import module must be an identifier") + .with_span(items[1].span), + ); + "".to_string() + } + }; + + let mut names = Vec::new(); + let mut seen = HashMap::new(); + match expect_list(&items[2]) { + Some(import_items) if !import_items.is_empty() => { + for item in import_items { + match expect_ident(item) { + Some(name) => { + if let Some(original) = seen.insert(name.to_string(), item.span) { + errors.push( + Diagnostic::new( + file, + "DuplicateName", + format!("duplicate imported name `{}`", name), + ) + .with_span(item.span) + .related("original imported name", original), + ); + } + if is_reserved_intrinsic(name) { + errors.push( + Diagnostic::new( + file, + "Visibility", + format!("builtin or intrinsic `{}` cannot be imported", name), + ) + .with_span(item.span), + ); + } + names.push(NameSpan { + name: name.to_string(), + span: item.span, + }); + } + None => errors.push( + Diagnostic::new( + file, + "InvalidImport", + "imported name must be an identifier", + ) + .with_span(item.span), + ), + } + } + } + _ => errors.push( + Diagnostic::new( + file, + "InvalidImport", + "import list must contain imported names", + ) + .with_span(items[2].span), + ), + } + + if errors.is_empty() { + Ok(ImportDecl { + module, + module_span: items[1].span, + names, + }) + } else { + Err(errors) + } +} + +fn tool_imports(source: &str) -> Vec { + let Ok(tokens) = lexer::lex("", source) else { + return Vec::new(); + }; + let Ok(forms) = crate::sexpr::parse("", &tokens) else { + return Vec::new(); + }; + forms + .iter() + .filter_map(|form| { + let items = expect_list(form)?; + if !matches!(items.first().and_then(expect_ident), Some("import")) { + return None; + } + items.get(1).and_then(expect_ident).map(str::to_string) + }) + .collect() +} + +fn tool_c_imports(source: &str) -> Vec { + let Ok(tokens) = lexer::lex("", source) else { + return Vec::new(); + }; + let Ok(forms) = crate::sexpr::parse("", &tokens) else { + return Vec::new(); + }; + let Ok(program) = lower::lower_program("", &forms) else { + return Vec::new(); + }; + program + .c_imports + .iter() + .map(|import| project_artifact_c_import(&program.module, import)) + .collect() +} + +pub(crate) fn project_artifact_c_import( + source_module: &str, + import: &crate::ast::CImportDecl, +) -> ProjectArtifactCImport { + ProjectArtifactCImport { + source_module: source_module.to_string(), + name: import.name.clone(), + symbol: import.name.clone(), + params: import + .params + .iter() + .map(|param| param.ty.to_string()) + .collect(), + return_type: import.return_type.to_string(), + abi: "experimental-fixture-c".to_string(), + } +} + +fn external_enum_from_module(module: &ModuleUnit, name: &str) -> Option { + module + .program + .enums + .iter() + .find(|enum_decl| enum_decl.name == name) + .map(|enum_decl| ExternalEnum { + name: name.to_string(), + span: enum_decl.name_span, + variants: enum_decl + .variants + .iter() + .map(|variant| ExternalEnumVariant { + name: variant.name.clone(), + name_span: variant.name_span, + payload_ty: variant.payload_ty.clone(), + payload_ty_span: variant.payload_ty_span, + }) + .collect(), + }) +} + +fn local_declarations( + file: &str, + program: &Program, +) -> ( + HashMap, + HashMap, + HashMap, + Vec, +) { + let mut all = HashMap::::new(); + let mut functions = HashMap::new(); + let mut structs = HashMap::new(); + let mut enums = HashMap::new(); + let mut errors = Vec::new(); + + for function in &program.functions { + let decl = DeclInfo { + span: function.span, + kind: DeclKind::Function, + }; + if is_reserved_intrinsic(&function.name) { + errors.push( + Diagnostic::new( + file, + "DuplicateName", + format!("builtin or intrinsic `{}` is reserved", function.name), + ) + .with_span(function.span), + ); + } + if let Some(original) = all.insert(function.name.clone(), decl.clone()) { + errors.push(duplicate_name( + file, + &function.name, + function.span, + original.span, + )); + } + functions.insert(function.name.clone(), decl); + } + + for import in &program.c_imports { + let decl = DeclInfo { + span: import.span, + kind: DeclKind::CImport, + }; + if is_reserved_intrinsic(&import.name) { + errors.push( + Diagnostic::new( + file, + "ReservedName", + format!("builtin or intrinsic `{}` is reserved", import.name), + ) + .with_span(import.name_span), + ); + } + if let Some(original) = all.insert(import.name.clone(), decl.clone()) { + errors.push( + Diagnostic::new( + file, + "DuplicateTopLevelName", + format!("duplicate top-level name `{}`", import.name), + ) + .with_span(import.name_span) + .related("original declaration", original.span), + ); + } + functions.insert(import.name.clone(), decl); + } + + for struct_decl in &program.structs { + let decl = DeclInfo { + span: struct_decl.name_span, + kind: DeclKind::Struct, + }; + if is_reserved_intrinsic(&struct_decl.name) { + errors.push( + Diagnostic::new( + file, + "DuplicateName", + format!("builtin or intrinsic `{}` is reserved", struct_decl.name), + ) + .with_span(struct_decl.name_span), + ); + } + if let Some(original) = all.insert(struct_decl.name.clone(), decl.clone()) { + if original.kind == DeclKind::CImport { + errors.push( + Diagnostic::new( + file, + "DuplicateTopLevelName", + format!("duplicate top-level name `{}`", struct_decl.name), + ) + .with_span(struct_decl.name_span) + .related("original declaration", original.span), + ); + } else { + errors.push(duplicate_name( + file, + &struct_decl.name, + struct_decl.name_span, + original.span, + )); + } + } + structs.insert(struct_decl.name.clone(), decl); + } + + for enum_decl in &program.enums { + let decl = DeclInfo { + span: enum_decl.name_span, + kind: DeclKind::Enum, + }; + if is_reserved_intrinsic(&enum_decl.name) { + errors.push( + Diagnostic::new( + file, + "DuplicateName", + format!("builtin or intrinsic `{}` is reserved", enum_decl.name), + ) + .with_span(enum_decl.name_span), + ); + } + if let Some(original) = all.insert(enum_decl.name.clone(), decl.clone()) { + if original.kind == DeclKind::CImport { + errors.push( + Diagnostic::new( + file, + "DuplicateTopLevelName", + format!("duplicate top-level name `{}`", enum_decl.name), + ) + .with_span(enum_decl.name_span) + .related("original declaration", original.span), + ); + } else { + errors.push(duplicate_name( + file, + &enum_decl.name, + enum_decl.name_span, + original.span, + )); + } + } + enums.insert(enum_decl.name.clone(), decl); + } + + (functions, structs, enums, errors) +} + +fn validate_workspace_packages( + workspace: &WorkspaceManifest, + packages: &mut [PackageUnit], + diagnostics: &mut Vec, +) { + let mut by_member = HashMap::::new(); + for (index, package) in packages.iter().enumerate() { + by_member.insert(package.manifest.member.clone(), index); + } + + let mut by_name = BTreeMap::>::new(); + for (index, package) in packages.iter().enumerate() { + by_name + .entry(package.manifest.name.clone()) + .or_default() + .push(index); + } + for (name, indices) in by_name.iter().filter(|(_, indices)| indices.len() > 1) { + for index in indices { + diagnostics.push(Diagnostic::new( + &packages[*index].manifest.path.display().to_string(), + "DuplicatePackageName", + format!("duplicate package name `{}`", name), + )); + } + } + + let mut resolved_dependencies = vec![Vec::::new(); packages.len()]; + for (package_index, package) in packages.iter().enumerate() { + for dependency in &package.manifest.dependencies { + let target_member = + match normalize_dependency_path(workspace, &package.manifest, &dependency.path) { + Ok(member) => member, + Err(diagnostic) => { + diagnostics.push(diagnostic.with_span(dependency.span)); + continue; + } + }; + let Some(target_index) = by_member.get(&target_member).copied() else { + diagnostics.push( + Diagnostic::new( + &package.manifest.path.display().to_string(), + "MissingPackageDependency", + format!( + "dependency `{}` path `{}` is not a workspace member", + dependency.key, dependency.path + ), + ) + .with_span(dependency.span), + ); + continue; + }; + let target = &packages[target_index]; + if dependency.key != target.manifest.name { + diagnostics.push( + Diagnostic::new( + &package.manifest.path.display().to_string(), + "DependencyNameMismatch", + format!( + "dependency key `{}` must match package name `{}`", + dependency.key, target.manifest.name + ), + ) + .with_span(dependency.span), + ); + continue; + } + if !resolved_dependencies[package_index].contains(&target_index) { + resolved_dependencies[package_index].push(target_index); + } + } + } + + let package_names = packages + .iter() + .map(|package| package.manifest.name.clone()) + .collect::>(); + for (package, dependencies) in packages.iter_mut().zip(resolved_dependencies) { + package.dependency_indices = dependencies; + package + .dependency_indices + .sort_by(|left, right| package_names[*left].cmp(&package_names[*right])); + } + + detect_package_cycle(packages, diagnostics); +} + +fn detect_package_cycle(packages: &[PackageUnit], diagnostics: &mut Vec) { + let mut state = vec![0_u8; packages.len()]; + let mut stack = Vec::new(); + let mut indices = (0..packages.len()).collect::>(); + indices.sort_by(|left, right| { + packages[*left] + .manifest + .name + .cmp(&packages[*right].manifest.name) + }); + for index in indices { + if state[index] == 0 && dfs_package_cycle(index, packages, &mut state, &mut stack) { + let cycle_start = stack + .last() + .and_then(|last| { + stack[..stack.len().saturating_sub(1)] + .iter() + .position(|idx| idx == last) + }) + .unwrap_or(0); + let cycle_nodes = &stack[cycle_start..]; + let cycle = cycle_nodes + .iter() + .map(|idx| packages[*idx].manifest.name.as_str()) + .collect::>() + .join(" -> "); + let mut diagnostic = Diagnostic::new( + &packages[index].manifest.path.display().to_string(), + "PackageDependencyCycle", + format!("workspace package dependencies contain a cycle: {}", cycle), + ); + for edge in cycle_nodes.windows(2) { + let from = &packages[edge[0]]; + let to = &packages[edge[1]]; + if let Some(dep) = from + .manifest + .dependencies + .iter() + .find(|dep| dep.key == to.manifest.name) + { + diagnostic = diagnostic.related_in_file( + from.manifest.path.display().to_string(), + format!( + "package `{}` depends on `{}`", + from.manifest.name, to.manifest.name + ), + dep.span, + ); + } + } + diagnostics.push(diagnostic); + return; + } + } +} + +fn dfs_package_cycle( + index: usize, + packages: &[PackageUnit], + state: &mut [u8], + stack: &mut Vec, +) -> bool { + state[index] = 1; + stack.push(index); + for next in &packages[index].dependency_indices { + if state[*next] == 1 { + stack.push(*next); + return true; + } + if state[*next] == 0 && dfs_package_cycle(*next, packages, state, stack) { + return true; + } + } + stack.pop(); + state[index] = 2; + false +} + +fn resolve_and_check_workspace( + loaded: WorkspaceLoaded, + require_entry_wrapper: bool, +) -> Result { + let WorkspaceLoaded { + workspace, + packages, + sources, + .. + } = loaded; + + let mut diagnostics = Vec::new(); + let mut module_maps = Vec::new(); + for package in &packages { + let mut by_name = HashMap::::new(); + for (index, module) in package.modules.iter().enumerate() { + if let Some(original) = by_name.insert(module.name.clone(), index) { + diagnostics.push(duplicate_name( + &module.file, + &module.name, + Span::new(0, 0), + package.modules[original] + .program + .functions + .first() + .map_or(Span::new(0, 0), |f| f.span), + )); + } + } + module_maps.push(by_name); + } + + let export_maps = packages + .iter() + .map(|package| build_export_maps(&package.modules, &mut diagnostics)) + .collect::>(); + let import_maps = + resolve_workspace_imports(&packages, &module_maps, &export_maps, &mut diagnostics); + for (package_index, package) in packages.iter().enumerate() { + detect_import_cycle( + &package.modules, + &module_maps[package_index], + &mut diagnostics, + ); + } + + if !diagnostics.is_empty() { + return Err(ProjectLoadFailure { + diagnostics, + sources, + artifact: Some(workspace_artifact(&workspace, &packages, None, None, None)), + }); + } + + let package_order = package_topo_order(&packages); + let mut module_orders = HashMap::>::new(); + for package_index in &package_order { + module_orders.insert( + *package_index, + topo_order( + &packages[*package_index].modules, + &module_maps[*package_index], + ), + ); + } + + let mut checked_by_module = HashMap::<(usize, String), CheckedProgram>::new(); + let mut check_errors = Vec::new(); + for package_index in &package_order { + let package = &packages[*package_index]; + let module_order = module_orders + .get(package_index) + .expect("module order was computed"); + for module_index in module_order { + let module = &package.modules[*module_index]; + let imports = &import_maps[*package_index][*module_index]; + let mut external_functions = Vec::new(); + let mut external_structs = Vec::new(); + let mut external_enums = Vec::new(); + for (name, binding) in imports { + let provider = &packages[binding.provider_package].modules[binding.provider_module]; + match binding.kind { + DeclKind::Function => { + if let Some(function) = + provider.program.functions.iter().find(|f| f.name == *name) + { + external_functions.push(ExternalFunction { + name: name.clone(), + params: function.params.iter().map(|p| p.ty.clone()).collect(), + return_type: function.return_type.clone(), + foreign: false, + }); + } + } + DeclKind::CImport => { + if let Some(import) = + provider.program.c_imports.iter().find(|f| f.name == *name) + { + external_functions.push(ExternalFunction { + name: name.clone(), + params: import.params.iter().map(|p| p.ty.clone()).collect(), + return_type: import.return_type.clone(), + foreign: true, + }); + } + } + DeclKind::Struct => { + if let Some(struct_decl) = + provider.program.structs.iter().find(|s| s.name == *name) + { + external_structs.push(ExternalStruct { + name: name.clone(), + fields: struct_decl + .fields + .iter() + .map(|field| (field.name.clone(), field.ty.clone())) + .collect(), + span: struct_decl.name_span, + }); + } + } + DeclKind::Enum => { + if let Some(enum_decl) = external_enum_from_module(provider, name) { + external_enums.push(enum_decl); + } + } + } + } + + match check::check_program_with_imports( + &module.file, + module.program.clone(), + &external_functions, + &external_structs, + &external_enums, + ) { + Ok(checked) => { + checked_by_module.insert((*package_index, module.name.clone()), checked); + } + Err(mut errs) => check_errors.append(&mut errs), + } + } + } + + if !check_errors.is_empty() { + return Err(ProjectLoadFailure { + diagnostics: check_errors, + sources, + artifact: Some(workspace_artifact( + &workspace, + &packages, + Some(&package_order), + Some(&module_orders), + None, + )), + }); + } + + let mut combined = CheckedProgram { + module: workspace + .root + .file_name() + .and_then(|name| name.to_str()) + .unwrap_or("workspace") + .to_string(), + enums: Vec::new(), + structs: Vec::new(), + c_imports: Vec::new(), + functions: Vec::new(), + tests: Vec::new(), + }; + + for package_index in &package_order { + let package = &packages[*package_index]; + let module_order = module_orders + .get(package_index) + .expect("module order was computed"); + for module_index in module_order { + let module = &package.modules[*module_index]; + let mut checked = checked_by_module + .remove(&(*package_index, module.name.clone())) + .expect("module was checked"); + let symbols = workspace_symbol_map( + &packages, + *package_index, + *module_index, + &import_maps[*package_index][*module_index], + ); + rewrite_checked_program(&mut checked, &symbols); + combined.enums.append(&mut checked.enums); + combined.structs.append(&mut checked.structs); + combined.c_imports.append(&mut checked.c_imports); + combined.functions.append(&mut checked.functions); + combined.tests.append(&mut checked.tests); + } + } + + let mut selected_build_entry_package = None; + if require_entry_wrapper { + selected_build_entry_package = select_workspace_build_entry(&packages, &module_maps); + match selected_build_entry_package { + Some(package_index) => { + add_workspace_entry_wrapper( + &packages, + package_index, + &module_maps[package_index], + &mut combined, + &mut diagnostics, + ); + } + None => diagnostics.push(Diagnostic::new( + &workspace.path.display().to_string(), + "WorkspaceBuildAmbiguousEntryPackage", + "workspace build requires exactly one package with its entry module", + )), + } + if !diagnostics.is_empty() { + return Err(ProjectLoadFailure { + diagnostics, + sources, + artifact: Some(workspace_artifact( + &workspace, + &packages, + Some(&package_order), + Some(&module_orders), + selected_build_entry_package.map(|index| packages[index].manifest.name.clone()), + )), + }); + } + } + + let artifact = workspace_artifact( + &workspace, + &packages, + Some(&package_order), + Some(&module_orders), + selected_build_entry_package.map(|index| packages[index].manifest.name.clone()), + ); + + Ok(CheckedProject { + program: combined, + sources, + artifact, + }) +} + +fn resolve_and_check( + manifest: Manifest, + modules: Vec, + sources: Vec, + require_entry_wrapper: bool, +) -> Result { + let mut diagnostics = Vec::new(); + let mut by_name = HashMap::::new(); + for (index, module) in modules.iter().enumerate() { + if let Some(original) = by_name.insert(module.name.clone(), index) { + diagnostics.push(duplicate_name( + &module.file, + &module.name, + Span::new(0, 0), + modules[original] + .program + .functions + .first() + .map_or(Span::new(0, 0), |f| f.span), + )); + } + } + + if !by_name.contains_key(&manifest.entry) { + diagnostics.push(Diagnostic::new( + &manifest.path.display().to_string(), + "MissingImport", + format!("entry module `{}` does not exist", manifest.entry), + )); + } + + let export_maps = build_export_maps(&modules, &mut diagnostics); + let import_maps = resolve_imports(&modules, &by_name, &export_maps, &mut diagnostics); + detect_import_cycle(&modules, &by_name, &mut diagnostics); + + if !diagnostics.is_empty() { + return Err(ProjectLoadFailure { + diagnostics, + sources, + artifact: Some(artifact_from_modules(&manifest, &modules, None)), + }); + } + + let order = topo_order(&modules, &by_name); + let mut checked_by_module = HashMap::::new(); + let mut check_errors = Vec::new(); + for module_index in &order { + let module = &modules[*module_index]; + let imports = &import_maps[*module_index]; + let mut external_functions = Vec::new(); + let mut external_structs = Vec::new(); + let mut external_enums = Vec::new(); + for (name, binding) in imports { + let provider = &modules[by_name[&binding.provider]]; + match binding.kind { + DeclKind::Function => { + if let Some(function) = + provider.program.functions.iter().find(|f| f.name == *name) + { + external_functions.push(ExternalFunction { + name: name.clone(), + params: function.params.iter().map(|p| p.ty.clone()).collect(), + return_type: function.return_type.clone(), + foreign: false, + }); + } + } + DeclKind::CImport => { + if let Some(import) = + provider.program.c_imports.iter().find(|f| f.name == *name) + { + external_functions.push(ExternalFunction { + name: name.clone(), + params: import.params.iter().map(|p| p.ty.clone()).collect(), + return_type: import.return_type.clone(), + foreign: true, + }); + } + } + DeclKind::Struct => { + if let Some(struct_decl) = + provider.program.structs.iter().find(|s| s.name == *name) + { + external_structs.push(ExternalStruct { + name: name.clone(), + fields: struct_decl + .fields + .iter() + .map(|field| (field.name.clone(), field.ty.clone())) + .collect(), + span: struct_decl.name_span, + }); + } + } + DeclKind::Enum => { + if let Some(enum_decl) = external_enum_from_module(provider, name) { + external_enums.push(enum_decl); + } + } + } + } + + match check::check_program_with_imports( + &module.file, + module.program.clone(), + &external_functions, + &external_structs, + &external_enums, + ) { + Ok(checked) => { + checked_by_module.insert(module.name.clone(), checked); + } + Err(mut errs) => check_errors.append(&mut errs), + } + } + + if !check_errors.is_empty() { + return Err(ProjectLoadFailure { + diagnostics: check_errors, + sources, + artifact: Some(artifact_from_modules(&manifest, &modules, Some(&order))), + }); + } + + let mut combined = CheckedProgram { + module: manifest.name.clone(), + enums: Vec::new(), + structs: Vec::new(), + c_imports: Vec::new(), + functions: Vec::new(), + tests: Vec::new(), + }; + + for module_index in &order { + let module = &modules[*module_index]; + let mut checked = checked_by_module + .remove(&module.name) + .expect("module was checked"); + let symbols = symbol_map(module, &import_maps[*module_index]); + rewrite_checked_program(&mut checked, &symbols); + combined.enums.append(&mut checked.enums); + combined.structs.append(&mut checked.structs); + combined.c_imports.append(&mut checked.c_imports); + combined.functions.append(&mut checked.functions); + combined.tests.append(&mut checked.tests); + } + + if require_entry_wrapper { + add_entry_wrapper( + &manifest, + &modules, + &by_name, + &mut combined, + &mut diagnostics, + ); + if !diagnostics.is_empty() { + return Err(ProjectLoadFailure { + diagnostics, + sources, + artifact: Some(artifact_from_modules(&manifest, &modules, Some(&order))), + }); + } + } + + let artifact = artifact_from_modules(&manifest, &modules, Some(&order)); + + Ok(CheckedProject { + program: combined, + sources, + artifact, + }) +} + +fn build_export_maps( + modules: &[ModuleUnit], + diagnostics: &mut Vec, +) -> Vec> { + modules + .iter() + .map(|module| { + let mut exports = HashMap::new(); + for export in &module.exports { + let kind = module + .local_functions + .get(&export.name) + .or_else(|| module.local_structs.get(&export.name)) + .or_else(|| module.local_enums.get(&export.name)) + .map(|decl| decl.kind); + match kind { + Some(kind) => { + exports.insert(export.name.clone(), kind); + } + None => diagnostics.push( + Diagnostic::new( + &module.file, + "Visibility", + format!( + "exported name `{}` is not a local function, struct, or enum", + export.name + ), + ) + .with_span(export.span), + ), + } + } + exports + }) + .collect() +} + +fn resolve_imports( + modules: &[ModuleUnit], + by_name: &HashMap, + export_maps: &[HashMap], + diagnostics: &mut Vec, +) -> Vec> { + modules + .iter() + .map(|module| { + let mut imports = HashMap::::new(); + for import in &module.imports { + let Some(provider_index) = by_name.get(&import.module).copied() else { + diagnostics.push( + Diagnostic::new( + &module.file, + "MissingImport", + format!("imported module `{}` does not exist", import.module), + ) + .with_span(import.module_span), + ); + continue; + }; + let provider = &modules[provider_index]; + for name in &import.names { + if let Some(local) = module + .local_functions + .get(&name.name) + .or_else(|| module.local_structs.get(&name.name)) + .or_else(|| module.local_enums.get(&name.name)) + { + diagnostics.push(duplicate_name( + &module.file, + &name.name, + name.span, + local.span, + )); + continue; + } + + if let Some(existing) = imports.get(&name.name) { + if existing.provider == provider.name { + diagnostics.push( + Diagnostic::new( + &module.file, + "DuplicateName", + format!("duplicate imported name `{}`", name.name), + ) + .with_span(name.span) + .related("original imported name", existing.import_span), + ); + } else { + diagnostics.push( + Diagnostic::new( + &module.file, + "AmbiguousName", + format!( + "name `{}` is imported from multiple modules", + name.name + ), + ) + .with_span(name.span) + .related("previous imported candidate", existing.import_span), + ); + } + continue; + } + + let provider_local = provider + .local_functions + .get(&name.name) + .or_else(|| provider.local_structs.get(&name.name)) + .or_else(|| provider.local_enums.get(&name.name)); + let exported = export_maps[provider_index].get(&name.name).copied(); + match (provider_local, exported) { + (_, Some(kind)) => { + imports.insert( + name.name.clone(), + ImportBinding { + provider: provider.name.clone(), + kind, + import_span: name.span, + }, + ); + } + (Some(decl), None) => diagnostics.push( + Diagnostic::new( + &module.file, + "Visibility", + format!( + "name `{}` is not exported by module `{}`", + name.name, provider.name + ), + ) + .with_span(name.span) + .related_in_file( + provider.file.clone(), + "private declaration", + decl.span, + ), + ), + (None, None) => diagnostics.push( + Diagnostic::new( + &module.file, + "MissingImport", + format!( + "module `{}` has no exported function, struct, or enum `{}`", + provider.name, name.name + ), + ) + .with_span(name.span), + ), + } + } + } + imports + }) + .collect() +} + +fn resolve_workspace_imports( + packages: &[PackageUnit], + module_maps: &[HashMap], + export_maps: &[Vec>], + diagnostics: &mut Vec, +) -> Vec>> { + let mut package_by_name = HashMap::::new(); + for (index, package) in packages.iter().enumerate() { + package_by_name.insert(package.manifest.name.clone(), index); + } + + packages + .iter() + .enumerate() + .map(|(package_index, package)| { + package + .modules + .iter() + .enumerate() + .map(|(module_index, module)| { + let mut imports = HashMap::::new(); + for import in &module.imports { + let Some((provider_package_index, provider_module_index)) = + resolve_workspace_import_module( + packages, + module_maps, + &package_by_name, + package_index, + module, + import, + diagnostics, + ) + else { + continue; + }; + let provider = + &packages[provider_package_index].modules[provider_module_index]; + for name in &import.names { + if let Some(local) = module + .local_functions + .get(&name.name) + .or_else(|| module.local_structs.get(&name.name)) + .or_else(|| module.local_enums.get(&name.name)) + { + diagnostics.push(duplicate_name( + &module.file, + &name.name, + name.span, + local.span, + )); + continue; + } + + if let Some(existing) = imports.get(&name.name) { + if existing.provider_package == provider_package_index + && existing.provider_module == provider_module_index + { + diagnostics.push( + Diagnostic::new( + &module.file, + "DuplicateName", + format!("duplicate imported name `{}`", name.name), + ) + .with_span(name.span) + .related("original imported name", existing.import_span), + ); + } else { + diagnostics.push( + Diagnostic::new( + &module.file, + "AmbiguousName", + format!( + "name `{}` is imported from multiple modules", + name.name + ), + ) + .with_span(name.span) + .related( + "previous imported candidate", + existing.import_span, + ), + ); + } + continue; + } + + let provider_local = provider + .local_functions + .get(&name.name) + .or_else(|| provider.local_structs.get(&name.name)) + .or_else(|| provider.local_enums.get(&name.name)); + let exported = export_maps[provider_package_index] + [provider_module_index] + .get(&name.name) + .copied(); + match (provider_local, exported) { + (_, Some(kind)) => { + imports.insert( + name.name.clone(), + WorkspaceImportBinding { + provider_package: provider_package_index, + provider_module: provider_module_index, + kind, + import_span: name.span, + }, + ); + } + (Some(decl), None) => diagnostics.push( + Diagnostic::new( + &module.file, + "Visibility", + format!( + "name `{}` is not exported by module `{}`", + name.name, import.module + ), + ) + .with_span(name.span) + .related_in_file( + provider.file.clone(), + "private declaration", + decl.span, + ), + ), + (None, None) => diagnostics.push( + Diagnostic::new( + &module.file, + "MissingImport", + format!( + "module `{}` has no exported function, struct, or enum `{}`", + import.module, name.name + ), + ) + .with_span(name.span), + ), + } + } + } + let _ = module_index; + imports + }) + .collect() + }) + .collect() +} + +fn resolve_workspace_import_module( + packages: &[PackageUnit], + module_maps: &[HashMap], + package_by_name: &HashMap, + package_index: usize, + module: &ModuleUnit, + import: &ImportDecl, + diagnostics: &mut Vec, +) -> Option<(usize, usize)> { + if standard_module_leaf(&import.module).is_some() { + let Some(provider_module_index) = module_maps[package_index].get(&import.module).copied() + else { + diagnostics.push( + Diagnostic::new( + &module.file, + "MissingImport", + format!("imported module `{}` does not exist", import.module), + ) + .with_span(import.module_span), + ); + return None; + }; + return Some((package_index, provider_module_index)); + } + + if let Some((package_name, module_name)) = import.module.split_once('.') { + if module_name.contains('.') || package_name.is_empty() || module_name.is_empty() { + diagnostics.push( + Diagnostic::new( + &module.file, + "MissingPackageModule", + format!( + "package-qualified import `{}` must be `.`", + import.module + ), + ) + .with_span(import.module_span), + ); + return None; + } + let Some(provider_package_index) = package_by_name.get(package_name).copied() else { + diagnostics.push( + Diagnostic::new( + &module.file, + "MissingPackageDependency", + format!("imported package `{}` does not exist", package_name), + ) + .with_span(import.module_span), + ); + return None; + }; + if !packages[package_index] + .dependency_indices + .contains(&provider_package_index) + { + diagnostics.push( + Diagnostic::new( + &module.file, + "PackageImportNotDependency", + format!( + "package `{}` is not a direct dependency of `{}`", + package_name, packages[package_index].manifest.name + ), + ) + .with_span(import.module_span), + ); + return None; + } + let Some(provider_module_index) = module_maps[provider_package_index] + .get(module_name) + .copied() + else { + diagnostics.push( + Diagnostic::new( + &module.file, + "MissingPackageModule", + format!("package `{}` has no module `{}`", package_name, module_name), + ) + .with_span(import.module_span), + ); + return None; + }; + return Some((provider_package_index, provider_module_index)); + } + + let Some(provider_module_index) = module_maps[package_index].get(&import.module).copied() + else { + diagnostics.push( + Diagnostic::new( + &module.file, + "MissingImport", + format!("imported module `{}` does not exist", import.module), + ) + .with_span(import.module_span), + ); + return None; + }; + Some((package_index, provider_module_index)) +} + +fn detect_import_cycle( + modules: &[ModuleUnit], + by_name: &HashMap, + diagnostics: &mut Vec, +) { + let mut state = vec![0_u8; modules.len()]; + let mut stack = Vec::new(); + for index in 0..modules.len() { + if state[index] == 0 && dfs_cycle(index, modules, by_name, &mut state, &mut stack) { + let cycle_start = stack + .last() + .and_then(|last| { + stack[..stack.len().saturating_sub(1)] + .iter() + .position(|idx| idx == last) + }) + .unwrap_or(0); + let cycle_nodes = &stack[cycle_start..]; + let cycle = cycle_nodes + .iter() + .map(|idx| modules[*idx].name.as_str()) + .collect::>() + .join(" -> "); + let primary_edge = cycle_nodes + .windows(2) + .last() + .and_then(|edge| import_edge(&modules[edge[0]], &modules[edge[1]].name)); + let primary_file = primary_edge + .map(|(file, _)| file) + .unwrap_or_else(|| modules[index].file.as_str()); + let primary_span = primary_edge.map(|(_, span)| span); + let mut diagnostic = Diagnostic::new( + primary_file, + "ImportCycle", + format!("project imports contain a cycle: {}", cycle), + ); + if let Some(span) = primary_span { + diagnostic = diagnostic.with_span(span); + } + for edge in cycle_nodes.windows(2) { + if let Some((file, span)) = import_edge(&modules[edge[0]], &modules[edge[1]].name) { + diagnostic = diagnostic.related_in_file( + file.to_string(), + format!( + "module `{}` imports `{}`", + modules[edge[0]].name, modules[edge[1]].name + ), + span, + ); + } + } + diagnostics.push(diagnostic); + return; + } + } +} + +fn import_edge<'a>(from: &'a ModuleUnit, to: &str) -> Option<(&'a str, Span)> { + from.imports + .iter() + .find(|import| import.module == to) + .map(|import| (from.file.as_str(), import.module_span)) +} + +fn dfs_cycle( + index: usize, + modules: &[ModuleUnit], + by_name: &HashMap, + state: &mut [u8], + stack: &mut Vec, +) -> bool { + state[index] = 1; + stack.push(index); + for import in &modules[index].imports { + let Some(next) = by_name.get(&import.module).copied() else { + continue; + }; + if state[next] == 1 { + stack.push(next); + return true; + } + if state[next] == 0 && dfs_cycle(next, modules, by_name, state, stack) { + return true; + } + } + stack.pop(); + state[index] = 2; + false +} + +fn topo_order(modules: &[ModuleUnit], by_name: &HashMap) -> Vec { + let mut indegree = vec![0_usize; modules.len()]; + let mut dependents = vec![Vec::::new(); modules.len()]; + for (index, module) in modules.iter().enumerate() { + let mut seen = HashSet::new(); + for import in &module.imports { + if let Some(provider) = by_name.get(&import.module).copied() { + if seen.insert(provider) { + indegree[index] += 1; + dependents[provider].push(index); + } + } + } + } + for deps in &mut dependents { + deps.sort_by(|left, right| modules[*left].name.cmp(&modules[*right].name)); + } + + let mut ready = modules + .iter() + .enumerate() + .filter(|(index, _)| indegree[*index] == 0) + .map(|(index, _)| index) + .collect::>(); + ready + .make_contiguous() + .sort_by(|left, right| modules[*left].name.cmp(&modules[*right].name)); + + let mut order = Vec::new(); + while let Some(index) = ready.pop_front() { + order.push(index); + for dependent in &dependents[index] { + indegree[*dependent] -= 1; + if indegree[*dependent] == 0 { + ready.push_back(*dependent); + ready + .make_contiguous() + .sort_by(|left, right| modules[*left].name.cmp(&modules[*right].name)); + } + } + } + order +} + +fn package_topo_order(packages: &[PackageUnit]) -> Vec { + let mut indegree = vec![0_usize; packages.len()]; + let mut dependents = vec![Vec::::new(); packages.len()]; + for (index, package) in packages.iter().enumerate() { + let mut seen = HashSet::new(); + for dependency in &package.dependency_indices { + if seen.insert(*dependency) { + indegree[index] += 1; + dependents[*dependency].push(index); + } + } + } + for deps in &mut dependents { + deps.sort_by(|left, right| { + packages[*left] + .manifest + .name + .cmp(&packages[*right].manifest.name) + }); + } + + let mut ready = packages + .iter() + .enumerate() + .filter(|(index, _)| indegree[*index] == 0) + .map(|(index, _)| index) + .collect::>(); + ready.make_contiguous().sort_by(|left, right| { + packages[*left] + .manifest + .name + .cmp(&packages[*right].manifest.name) + }); + + let mut order = Vec::new(); + while let Some(index) = ready.pop_front() { + order.push(index); + for dependent in &dependents[index] { + indegree[*dependent] -= 1; + if indegree[*dependent] == 0 { + ready.push_back(*dependent); + ready.make_contiguous().sort_by(|left, right| { + packages[*left] + .manifest + .name + .cmp(&packages[*right].manifest.name) + }); + } + } + } + order +} + +fn symbol_map( + module: &ModuleUnit, + imports: &HashMap, +) -> HashMap { + let mut symbols = HashMap::new(); + for (name, decl) in &module.local_functions { + if decl.kind == DeclKind::Function { + symbols.insert(name.clone(), symbol_name(&module.name, name)); + } + } + for name in module.local_structs.keys() { + symbols.insert(name.clone(), symbol_name(&module.name, name)); + } + for name in module.local_enums.keys() { + symbols.insert(name.clone(), symbol_name(&module.name, name)); + } + for (name, binding) in imports { + if matches!( + binding.kind, + DeclKind::Function | DeclKind::Struct | DeclKind::Enum + ) { + symbols.insert(name.clone(), symbol_name(&binding.provider, name)); + } + } + symbols +} + +fn workspace_symbol_map( + packages: &[PackageUnit], + package_index: usize, + module_index: usize, + imports: &HashMap, +) -> HashMap { + let package = &packages[package_index]; + let module = &package.modules[module_index]; + let mut symbols = HashMap::new(); + for (name, decl) in &module.local_functions { + if decl.kind == DeclKind::Function { + symbols.insert( + name.clone(), + package_symbol_name(&package.manifest.name, &module.name, name), + ); + } + } + for name in module.local_structs.keys() { + symbols.insert( + name.clone(), + package_symbol_name(&package.manifest.name, &module.name, name), + ); + } + for name in module.local_enums.keys() { + symbols.insert( + name.clone(), + package_symbol_name(&package.manifest.name, &module.name, name), + ); + } + for (name, binding) in imports { + if matches!( + binding.kind, + DeclKind::Function | DeclKind::Struct | DeclKind::Enum + ) { + let provider_package = &packages[binding.provider_package]; + let provider_module = &provider_package.modules[binding.provider_module]; + symbols.insert( + name.clone(), + package_symbol_name(&provider_package.manifest.name, &provider_module.name, name), + ); + } + } + symbols +} + +fn rewrite_checked_program(program: &mut CheckedProgram, symbols: &HashMap) { + for enum_decl in &mut program.enums { + if let Some(name) = symbols.get(&enum_decl.name) { + enum_decl.name = name.clone(); + } + for variant in &mut enum_decl.variants { + if let Some(payload_ty) = &mut variant.payload_ty { + rewrite_type(payload_ty, symbols); + } + } + } + for struct_decl in &mut program.structs { + if let Some(name) = symbols.get(&struct_decl.name) { + struct_decl.name = name.clone(); + } + for (_, ty) in &mut struct_decl.fields { + rewrite_type(ty, symbols); + } + } + for function in &mut program.functions { + if let Some(name) = symbols.get(&function.name) { + function.name = name.clone(); + } + for (_, ty) in &mut function.params { + rewrite_type(ty, symbols); + } + rewrite_type(&mut function.return_type, symbols); + for expr in &mut function.body { + rewrite_expr(expr, symbols); + } + } + for test in &mut program.tests { + for expr in &mut test.body { + rewrite_expr(expr, symbols); + } + } +} + +fn rewrite_expr(expr: &mut TExpr, symbols: &HashMap) { + rewrite_type(&mut expr.ty, symbols); + match &mut expr.kind { + TExprKind::StructInit { name, fields } => { + if let Some(symbol) = symbols.get(name) { + *name = symbol.clone(); + } + for (_, value) in fields { + rewrite_expr(value, symbols); + } + } + TExprKind::ArrayInit { elements } => { + for element in elements { + rewrite_expr(element, symbols); + } + } + TExprKind::OptionSome { value } + | TExprKind::ResultOk { value } + | TExprKind::ResultErr { value } + | TExprKind::OptionIsSome { value } + | TExprKind::OptionIsNone { value } + | TExprKind::OptionUnwrapSome { value } + | TExprKind::ResultIsOk { value, .. } + | TExprKind::ResultIsErr { value, .. } + | TExprKind::ResultUnwrapOk { value, .. } + | TExprKind::ResultUnwrapErr { value, .. } + | TExprKind::FieldAccess { value, .. } => rewrite_expr(value, symbols), + TExprKind::Index { array, index } => { + rewrite_expr(array, symbols); + rewrite_expr(index, symbols); + } + TExprKind::Local { initializer, .. } => rewrite_expr(initializer, symbols), + TExprKind::Set { expr, .. } => rewrite_expr(expr, symbols), + TExprKind::Binary { left, right, .. } => { + rewrite_expr(left, symbols); + rewrite_expr(right, symbols); + } + TExprKind::If { + condition, + then_expr, + else_expr, + } => { + rewrite_expr(condition, symbols); + rewrite_expr(then_expr, symbols); + rewrite_expr(else_expr, symbols); + } + TExprKind::Match { subject, arms } => { + rewrite_expr(subject, symbols); + for arm in arms { + rewrite_match_pattern(&mut arm.pattern, symbols); + for expr in &mut arm.body { + rewrite_expr(expr, symbols); + } + } + } + TExprKind::While { condition, body } => { + rewrite_expr(condition, symbols); + for expr in body { + rewrite_expr(expr, symbols); + } + } + TExprKind::Unsafe { body } => { + for expr in body { + rewrite_expr(expr, symbols); + } + } + TExprKind::Call { name, args } => { + if let Some(symbol) = symbols.get(name) { + *name = symbol.clone(); + } + for arg in args { + rewrite_expr(arg, symbols); + } + } + TExprKind::EnumVariant { + enum_name, payload, .. + } => { + if let Some(symbol) = symbols.get(enum_name) { + *enum_name = symbol.clone(); + } + if let Some(payload) = payload { + rewrite_expr(payload, symbols); + } + } + TExprKind::Int(_) + | TExprKind::Int64(_) + | TExprKind::UInt32(_) + | TExprKind::UInt64(_) + | TExprKind::Float(_) + | TExprKind::Bool(_) + | TExprKind::String(_) + | TExprKind::Var(_) + | TExprKind::OptionNone => {} + } +} + +fn rewrite_match_pattern(pattern: &mut MatchPatternKind, symbols: &HashMap) { + if let MatchPatternKind::EnumVariant { enum_name, .. } = pattern { + if let Some(symbol) = symbols.get(enum_name) { + *enum_name = symbol.clone(); + } + } +} + +fn rewrite_type(ty: &mut Type, symbols: &HashMap) { + match ty { + Type::Named(name) => { + if let Some(symbol) = symbols.get(name) { + *name = symbol.clone(); + } + } + Type::Array(inner, _) + | Type::Vec(inner) + | Type::Option(inner) + | Type::Ptr(inner) + | Type::Slice(inner) => rewrite_type(inner, symbols), + Type::Result(ok, err) => { + rewrite_type(ok, symbols); + rewrite_type(err, symbols); + } + Type::I32 + | Type::I64 + | Type::U32 + | Type::U64 + | Type::F64 + | Type::Bool + | Type::Unit + | Type::String => {} + } +} + +fn add_entry_wrapper( + manifest: &Manifest, + modules: &[ModuleUnit], + by_name: &HashMap, + combined: &mut CheckedProgram, + diagnostics: &mut Vec, +) { + let Some(entry_index) = by_name.get(&manifest.entry).copied() else { + return; + }; + let entry_module = &modules[entry_index]; + let Some(entry_function) = entry_module + .program + .functions + .iter() + .find(|function| function.name == "main") + else { + diagnostics.push(Diagnostic::new( + &entry_module.file, + "MissingImport", + format!("entry module `{}` has no `main` function", manifest.entry), + )); + return; + }; + + if !entry_function.params.is_empty() || entry_function.return_type != Type::I32 { + diagnostics.push( + Diagnostic::new( + &entry_module.file, + "MissingImport", + "project entry `main` must have no parameters and return `i32`", + ) + .with_span(entry_function.span), + ); + return; + } + + let entry_symbol = symbol_name(&entry_module.name, "main"); + combined.functions.push(CheckedFunction { + name: "main".to_string(), + params: Vec::new(), + return_type: Type::I32, + body: vec![TExpr { + ty: Type::I32, + span: entry_function.span, + kind: TExprKind::Call { + name: entry_symbol, + args: Vec::new(), + }, + }], + span: entry_function.span, + file: entry_module.file.clone(), + }); +} + +fn select_workspace_build_entry( + packages: &[PackageUnit], + module_maps: &[HashMap], +) -> Option { + let candidates = packages + .iter() + .enumerate() + .filter(|(index, package)| module_maps[*index].contains_key(&package.manifest.entry)) + .map(|(index, _)| index) + .collect::>(); + if candidates.len() == 1 { + candidates.first().copied() + } else { + None + } +} + +fn add_workspace_entry_wrapper( + packages: &[PackageUnit], + package_index: usize, + by_name: &HashMap, + combined: &mut CheckedProgram, + diagnostics: &mut Vec, +) { + let package = &packages[package_index]; + let Some(entry_index) = by_name.get(&package.manifest.entry).copied() else { + return; + }; + let entry_module = &package.modules[entry_index]; + let Some(entry_function) = entry_module + .program + .functions + .iter() + .find(|function| function.name == "main") + else { + diagnostics.push(Diagnostic::new( + &entry_module.file, + "MissingImport", + format!( + "entry module `{}` in package `{}` has no `main` function", + package.manifest.entry, package.manifest.name + ), + )); + return; + }; + + if !entry_function.params.is_empty() || entry_function.return_type != Type::I32 { + diagnostics.push( + Diagnostic::new( + &entry_module.file, + "MissingImport", + "workspace entry `main` must have no parameters and return `i32`", + ) + .with_span(entry_function.span), + ); + return; + } + + let entry_symbol = package_symbol_name(&package.manifest.name, &entry_module.name, "main"); + combined.functions.push(CheckedFunction { + name: "main".to_string(), + params: Vec::new(), + return_type: Type::I32, + body: vec![TExpr { + ty: Type::I32, + span: entry_function.span, + kind: TExprKind::Call { + name: entry_symbol, + args: Vec::new(), + }, + }], + span: entry_function.span, + file: entry_module.file.clone(), + }); +} + +fn duplicate_name(file: &str, name: &str, span: Span, original: Span) -> Diagnostic { + Diagnostic::new(file, "DuplicateName", format!("duplicate name `{}`", name)) + .with_span(span) + .related("original declaration", original) +} + +fn is_reserved_intrinsic(name: &str) -> bool { + std_runtime::is_reserved_name(name) || crate::unsafe_ops::is_reserved_head(name) +} + +fn symbol_name(module: &str, name: &str) -> String { + format!( + "__glagol_{}_{}", + symbol_component(module), + symbol_component(name) + ) +} + +fn package_symbol_name(package: &str, module: &str, name: &str) -> String { + format!( + "__glagol_{}_{}_{}", + symbol_component(package), + symbol_component(module), + symbol_component(name) + ) +} + +fn symbol_component(value: &str) -> String { + let mut out = String::new(); + for byte in value.bytes() { + match byte { + b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_' => out.push(byte as char), + _ => out.push_str(&format!("_{:02x}", byte)), + } + } + out +} + +fn head_ident(form: &SExpr) -> Option<&str> { + let items = expect_list(form)?; + items.first().and_then(expect_ident) +} + +fn expect_list(form: &SExpr) -> Option<&[SExpr]> { + match &form.kind { + SExprKind::List(items) => Some(items), + _ => None, + } +} + +fn expect_ident(form: &SExpr) -> Option<&str> { + match &form.kind { + SExprKind::Atom(Atom::Ident(name)) => Some(name), + _ => None, + } +} + +fn module_name_span(form: &SExpr) -> Option { + expect_list(form)?.get(1).map(|expr| expr.span) +} diff --git a/compiler/src/scaffold.rs b/compiler/src/scaffold.rs new file mode 100644 index 0000000..f09fd13 --- /dev/null +++ b/compiler/src/scaffold.rs @@ -0,0 +1,147 @@ +use std::{ + fs, io, + path::{Path, PathBuf}, +}; + +use crate::diag::Diagnostic; + +pub fn create_project(target: &str, explicit_name: Option<&str>) -> Result<(), Diagnostic> { + if target.trim().is_empty() { + return Err(Diagnostic::new( + target, + "UsageError", + "project directory must not be empty", + )); + } + + let root = Path::new(target); + if root.is_file() { + return Err(Diagnostic::new( + target, + "ProjectScaffoldBlocked", + format!( + "cannot create project `{}` because a file exists there", + target + ), + )); + } + if root.is_dir() && has_entries(root)? { + return Err(Diagnostic::new( + target, + "ProjectScaffoldBlocked", + format!( + "cannot create project `{}` because the directory is not empty", + target + ), + ) + .hint("choose an empty directory or a new project path")); + } + + let raw_name = explicit_name + .map(str::to_string) + .unwrap_or_else(|| basename(root).unwrap_or_else(|| "slovo-project".to_string())); + let name = sanitize_project_name(&raw_name); + if name.is_empty() { + return Err(Diagnostic::new( + target, + "InvalidProjectName", + "project name must contain at least one ASCII letter or digit", + )); + } + + let src = root.join("src"); + create_dir_all_checked(&src, target)?; + write_checked( + &root.join("slovo.toml"), + &format!( + "[project]\nname = \"{}\"\nsource_root = \"src\"\nentry = \"main\"\n", + name + ), + target, + )?; + write_checked( + &src.join("main.slo"), + "(module main)\n\n(fn main () -> i32\n 0)\n\n(test \"main returns zero\"\n (= (main) 0))\n", + target, + ) +} + +fn has_entries(path: &Path) -> Result { + let mut entries = fs::read_dir(path).map_err(|err| io_diagnostic(path, err))?; + match entries.next() { + Some(Ok(_)) => Ok(true), + Some(Err(err)) => Err(io_diagnostic(path, err)), + None => Ok(false), + } +} + +fn basename(path: &Path) -> Option { + path.file_name() + .and_then(|name| name.to_str()) + .map(str::to_string) +} + +fn sanitize_project_name(value: &str) -> String { + let mut out = String::new(); + let mut previous_dash = false; + for ch in value.chars().flat_map(char::to_lowercase) { + let mapped = if ch.is_ascii_lowercase() || ch.is_ascii_digit() { + Some(ch) + } else if ch == '-' || ch == '_' || ch == '.' || ch.is_whitespace() { + Some('-') + } else { + None + }; + let Some(ch) = mapped else { + continue; + }; + if ch == '-' { + if !out.is_empty() && !previous_dash { + out.push('-'); + previous_dash = true; + } + } else { + out.push(ch); + previous_dash = false; + } + } + while out.ends_with('-') { + out.pop(); + } + if out + .chars() + .next() + .is_some_and(|first| first.is_ascii_digit()) + { + out.insert_str(0, "project-"); + } + out +} + +fn create_dir_all_checked(path: &PathBuf, target: &str) -> Result<(), Diagnostic> { + fs::create_dir_all(path).map_err(|err| { + Diagnostic::new( + target, + "ProjectScaffoldBlocked", + format!("cannot create directory `{}`: {}", path.display(), err), + ) + }) +} + +fn write_checked(path: &Path, text: &str, target: &str) -> Result<(), Diagnostic> { + fs::write(path, text).map_err(|err| { + Diagnostic::new( + target, + "ProjectScaffoldBlocked", + format!("cannot write `{}`: {}", path.display(), err), + ) + }) +} + +fn io_diagnostic(path: &Path, err: io::Error) -> Diagnostic { + Diagnostic::new( + path.display().to_string(), + "ProjectScaffoldBlocked", + format!("cannot inspect `{}`: {}", path.display(), err), + ) +} diff --git a/compiler/src/sexpr.rs b/compiler/src/sexpr.rs new file mode 100644 index 0000000..31b36d4 --- /dev/null +++ b/compiler/src/sexpr.rs @@ -0,0 +1,225 @@ +use crate::{ + diag::Diagnostic, + token::{Span, Token, TokenKind}, +}; + +#[derive(Debug, Clone)] +pub enum Atom { + Ident(String), + Int(i64), + I64(i64), + U32(u32), + U64(u64), + Float(f64), + String(String), + Arrow, +} + +#[derive(Debug, Clone)] +pub enum SExprKind { + Atom(Atom), + List(Vec), +} + +#[derive(Debug, Clone)] +pub struct SExpr { + pub kind: SExprKind, + pub span: Span, +} + +pub fn parse(file: &str, tokens: &[Token]) -> Result, Vec> { + let mut parser = Parser { + file, + tokens, + pos: 0, + errors: Vec::new(), + }; + let mut forms = Vec::new(); + + while parser.pos < tokens.len() { + match parser.parse_expr() { + Some(expr) => forms.push(expr), + None => break, + } + } + + if parser.errors.is_empty() { + Ok(forms) + } else { + Err(parser.errors) + } +} + +pub fn print_tree(forms: &[SExpr]) -> String { + let mut output = String::new(); + + for form in forms { + write_tree_expr(form, 0, &mut output); + } + + output +} + +fn write_tree_expr(expr: &SExpr, indent: usize, output: &mut String) { + output.push_str(&" ".repeat(indent)); + + match &expr.kind { + SExprKind::Atom(atom) => write_tree_atom(atom, output), + SExprKind::List(items) => { + output.push_str("list\n"); + for item in items { + write_tree_expr(item, indent + 1, output); + } + } + } +} + +fn write_tree_atom(atom: &Atom, output: &mut String) { + match atom { + Atom::Ident(value) => { + output.push_str("ident "); + output.push_str(value); + } + Atom::Int(value) => { + output.push_str("int "); + output.push_str(&value.to_string()); + } + Atom::I64(value) => { + output.push_str("i64 "); + output.push_str(&value.to_string()); + } + Atom::U32(value) => { + output.push_str("u32 "); + output.push_str(&value.to_string()); + } + Atom::U64(value) => { + output.push_str("u64 "); + output.push_str(&value.to_string()); + } + Atom::Float(value) => { + output.push_str("float "); + output.push_str(&value.to_string()); + } + Atom::String(value) => { + output.push_str("string \""); + for ch in value.chars() { + output.extend(ch.escape_default()); + } + output.push('"'); + } + Atom::Arrow => output.push_str("arrow ->"), + } + output.push('\n'); +} + +struct Parser<'a> { + file: &'a str, + tokens: &'a [Token], + pos: usize, + errors: Vec, +} + +impl<'a> Parser<'a> { + fn parse_expr(&mut self) -> Option { + let token = self.tokens.get(self.pos)?.clone(); + + match token.kind { + TokenKind::LParen => self.parse_list(), + TokenKind::RParen => { + self.errors.push( + Diagnostic::new(self.file, "UnexpectedRParen", "unexpected `)`") + .with_span(token.span), + ); + self.pos += 1; + None + } + TokenKind::Ident(name) => { + self.pos += 1; + Some(SExpr { + kind: SExprKind::Atom(Atom::Ident(name)), + span: token.span, + }) + } + TokenKind::Int(value) => { + self.pos += 1; + Some(SExpr { + kind: SExprKind::Atom(Atom::Int(value)), + span: token.span, + }) + } + TokenKind::I64(value) => { + self.pos += 1; + Some(SExpr { + kind: SExprKind::Atom(Atom::I64(value)), + span: token.span, + }) + } + TokenKind::U32(value) => { + self.pos += 1; + Some(SExpr { + kind: SExprKind::Atom(Atom::U32(value)), + span: token.span, + }) + } + TokenKind::U64(value) => { + self.pos += 1; + Some(SExpr { + kind: SExprKind::Atom(Atom::U64(value)), + span: token.span, + }) + } + TokenKind::Float(value) => { + self.pos += 1; + Some(SExpr { + kind: SExprKind::Atom(Atom::Float(value)), + span: token.span, + }) + } + TokenKind::String(value) => { + self.pos += 1; + Some(SExpr { + kind: SExprKind::Atom(Atom::String(value)), + span: token.span, + }) + } + TokenKind::Arrow => { + self.pos += 1; + Some(SExpr { + kind: SExprKind::Atom(Atom::Arrow), + span: token.span, + }) + } + } + } + + fn parse_list(&mut self) -> Option { + let start = self.tokens[self.pos].span.start; + self.pos += 1; + + let mut items = Vec::new(); + + while self.pos < self.tokens.len() { + if matches!(self.tokens[self.pos].kind, TokenKind::RParen) { + let end = self.tokens[self.pos].span.end; + self.pos += 1; + return Some(SExpr { + kind: SExprKind::List(items), + span: Span::new(start, end), + }); + } + + if let Some(expr) = self.parse_expr() { + items.push(expr); + } else { + break; + } + } + + self.errors.push( + Diagnostic::new(self.file, "UnclosedList", "unclosed list") + .with_span(Span::new(start, start + 1)) + .hint("add a closing `)`"), + ); + None + } +} diff --git a/compiler/src/std_runtime.rs b/compiler/src/std_runtime.rs new file mode 100644 index 0000000..a26232f --- /dev/null +++ b/compiler/src/std_runtime.rs @@ -0,0 +1,666 @@ +use crate::{diag::Diagnostic, token::Span, types::Type}; + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum RuntimeType { + I32, + I64, + U32, + U64, + F64, + Bool, + String, + ResultI32I32, + ResultI64I32, + ResultU32I32, + ResultU64I32, + ResultF64I32, + ResultBoolI32, + ResultStringI32, + VecI32, + VecI64, + VecF64, + VecBool, + VecString, + Unit, +} + +impl RuntimeType { + pub fn to_type(self) -> Type { + match self { + Self::I32 => Type::I32, + Self::I64 => Type::I64, + Self::U32 => Type::U32, + Self::U64 => Type::U64, + Self::F64 => Type::F64, + Self::Bool => Type::Bool, + Self::String => Type::String, + Self::ResultI32I32 => Type::Result(Box::new(Type::I32), Box::new(Type::I32)), + Self::ResultI64I32 => Type::Result(Box::new(Type::I64), Box::new(Type::I32)), + Self::ResultU32I32 => Type::Result(Box::new(Type::U32), Box::new(Type::I32)), + Self::ResultU64I32 => Type::Result(Box::new(Type::U64), Box::new(Type::I32)), + Self::ResultF64I32 => Type::Result(Box::new(Type::F64), Box::new(Type::I32)), + Self::ResultBoolI32 => Type::Result(Box::new(Type::Bool), Box::new(Type::I32)), + Self::ResultStringI32 => Type::Result(Box::new(Type::String), Box::new(Type::I32)), + Self::VecI32 => Type::Vec(Box::new(Type::I32)), + Self::VecI64 => Type::Vec(Box::new(Type::I64)), + Self::VecF64 => Type::Vec(Box::new(Type::F64)), + Self::VecBool => Type::Vec(Box::new(Type::Bool)), + Self::VecString => Type::Vec(Box::new(Type::String)), + Self::Unit => Type::Unit, + } + } +} + +#[derive(Debug, Copy, Clone)] +pub struct RuntimeFunction { + pub source_name: &'static str, + pub runtime_symbol: &'static str, + pub params: &'static [RuntimeType], + pub return_type: RuntimeType, + pub promoted: bool, +} + +const I32_PARAM: &[RuntimeType] = &[RuntimeType::I32]; +const I64_PARAM: &[RuntimeType] = &[RuntimeType::I64]; +const U32_PARAM: &[RuntimeType] = &[RuntimeType::U32]; +const U64_PARAM: &[RuntimeType] = &[RuntimeType::U64]; +const F64_PARAM: &[RuntimeType] = &[RuntimeType::F64]; +const BOOL_PARAM: &[RuntimeType] = &[RuntimeType::Bool]; +const STRING_PARAM: &[RuntimeType] = &[RuntimeType::String]; +const STRING_STRING_PARAMS: &[RuntimeType] = &[RuntimeType::String, RuntimeType::String]; +const VEC_I32_PARAM: &[RuntimeType] = &[RuntimeType::VecI32]; +const VEC_I32_I32_PARAMS: &[RuntimeType] = &[RuntimeType::VecI32, RuntimeType::I32]; +const VEC_I64_PARAM: &[RuntimeType] = &[RuntimeType::VecI64]; +const VEC_I64_I64_PARAMS: &[RuntimeType] = &[RuntimeType::VecI64, RuntimeType::I64]; +const VEC_I64_I32_PARAMS: &[RuntimeType] = &[RuntimeType::VecI64, RuntimeType::I32]; +const VEC_F64_PARAM: &[RuntimeType] = &[RuntimeType::VecF64]; +const VEC_F64_F64_PARAMS: &[RuntimeType] = &[RuntimeType::VecF64, RuntimeType::F64]; +const VEC_F64_I32_PARAMS: &[RuntimeType] = &[RuntimeType::VecF64, RuntimeType::I32]; +const VEC_BOOL_PARAM: &[RuntimeType] = &[RuntimeType::VecBool]; +const VEC_BOOL_BOOL_PARAMS: &[RuntimeType] = &[RuntimeType::VecBool, RuntimeType::Bool]; +const VEC_BOOL_I32_PARAMS: &[RuntimeType] = &[RuntimeType::VecBool, RuntimeType::I32]; +const VEC_STRING_PARAM: &[RuntimeType] = &[RuntimeType::VecString]; +const VEC_STRING_STRING_PARAMS: &[RuntimeType] = &[RuntimeType::VecString, RuntimeType::String]; +const VEC_STRING_I32_PARAMS: &[RuntimeType] = &[RuntimeType::VecString, RuntimeType::I32]; + +pub const FUNCTIONS: &[RuntimeFunction] = &[ + RuntimeFunction { + source_name: "print_i32", + runtime_symbol: "print_i32", + params: I32_PARAM, + return_type: RuntimeType::Unit, + promoted: false, + }, + RuntimeFunction { + source_name: "print_string", + runtime_symbol: "print_string", + params: STRING_PARAM, + return_type: RuntimeType::Unit, + promoted: false, + }, + RuntimeFunction { + source_name: "print_bool", + runtime_symbol: "print_bool", + params: BOOL_PARAM, + return_type: RuntimeType::Unit, + promoted: false, + }, + RuntimeFunction { + source_name: "string_len", + runtime_symbol: "string_len", + params: STRING_PARAM, + return_type: RuntimeType::I32, + promoted: false, + }, + RuntimeFunction { + source_name: "std.io.print_i32", + runtime_symbol: "print_i32", + params: I32_PARAM, + return_type: RuntimeType::Unit, + promoted: true, + }, + RuntimeFunction { + source_name: "std.io.print_f64", + runtime_symbol: "print_f64", + params: F64_PARAM, + return_type: RuntimeType::Unit, + promoted: true, + }, + RuntimeFunction { + source_name: "std.io.print_i64", + runtime_symbol: "print_i64", + params: I64_PARAM, + return_type: RuntimeType::Unit, + promoted: true, + }, + RuntimeFunction { + source_name: "std.io.print_u32", + runtime_symbol: "print_u32", + params: U32_PARAM, + return_type: RuntimeType::Unit, + promoted: true, + }, + RuntimeFunction { + source_name: "std.io.print_u64", + runtime_symbol: "print_u64", + params: U64_PARAM, + return_type: RuntimeType::Unit, + promoted: true, + }, + RuntimeFunction { + source_name: "std.io.print_string", + runtime_symbol: "print_string", + params: STRING_PARAM, + return_type: RuntimeType::Unit, + promoted: true, + }, + RuntimeFunction { + source_name: "std.io.print_bool", + runtime_symbol: "print_bool", + params: BOOL_PARAM, + return_type: RuntimeType::Unit, + promoted: true, + }, + RuntimeFunction { + source_name: "std.string.len", + runtime_symbol: "string_len", + params: STRING_PARAM, + return_type: RuntimeType::I32, + promoted: true, + }, + RuntimeFunction { + source_name: "std.string.concat", + runtime_symbol: "__glagol_string_concat", + params: STRING_STRING_PARAMS, + return_type: RuntimeType::String, + promoted: true, + }, + RuntimeFunction { + source_name: "std.string.parse_i32_result", + runtime_symbol: "__glagol_string_parse_i32_result", + params: STRING_PARAM, + return_type: RuntimeType::ResultI32I32, + promoted: true, + }, + RuntimeFunction { + source_name: "std.string.parse_i64_result", + runtime_symbol: "__glagol_string_parse_i64_result", + params: STRING_PARAM, + return_type: RuntimeType::ResultI64I32, + promoted: true, + }, + RuntimeFunction { + source_name: "std.string.parse_u32_result", + runtime_symbol: "__glagol_string_parse_u32_result", + params: STRING_PARAM, + return_type: RuntimeType::ResultU32I32, + promoted: true, + }, + RuntimeFunction { + source_name: "std.string.parse_u64_result", + runtime_symbol: "__glagol_string_parse_u64_result", + params: STRING_PARAM, + return_type: RuntimeType::ResultU64I32, + promoted: true, + }, + RuntimeFunction { + source_name: "std.string.parse_f64_result", + runtime_symbol: "__glagol_string_parse_f64_result", + params: STRING_PARAM, + return_type: RuntimeType::ResultF64I32, + promoted: true, + }, + RuntimeFunction { + source_name: "std.string.parse_bool_result", + runtime_symbol: "__glagol_string_parse_bool_result", + params: STRING_PARAM, + return_type: RuntimeType::ResultBoolI32, + promoted: true, + }, + RuntimeFunction { + source_name: "std.io.eprint", + runtime_symbol: "__glagol_io_eprint", + params: STRING_PARAM, + return_type: RuntimeType::Unit, + promoted: true, + }, + RuntimeFunction { + source_name: "std.io.read_stdin_result", + runtime_symbol: "__glagol_io_read_stdin_result", + params: &[], + return_type: RuntimeType::ResultStringI32, + promoted: true, + }, + RuntimeFunction { + source_name: "std.process.argc", + runtime_symbol: "__glagol_process_argc", + params: &[], + return_type: RuntimeType::I32, + promoted: true, + }, + RuntimeFunction { + source_name: "std.process.arg", + runtime_symbol: "__glagol_process_arg", + params: I32_PARAM, + return_type: RuntimeType::String, + promoted: true, + }, + RuntimeFunction { + source_name: "std.process.arg_result", + runtime_symbol: "__glagol_process_arg_result", + params: I32_PARAM, + return_type: RuntimeType::ResultStringI32, + promoted: true, + }, + RuntimeFunction { + source_name: "std.env.get", + runtime_symbol: "__glagol_env_get", + params: STRING_PARAM, + return_type: RuntimeType::String, + promoted: true, + }, + RuntimeFunction { + source_name: "std.env.get_result", + runtime_symbol: "__glagol_env_get_result", + params: STRING_PARAM, + return_type: RuntimeType::ResultStringI32, + promoted: true, + }, + RuntimeFunction { + source_name: "std.fs.read_text", + runtime_symbol: "__glagol_fs_read_text", + params: STRING_PARAM, + return_type: RuntimeType::String, + promoted: true, + }, + RuntimeFunction { + source_name: "std.fs.read_text_result", + runtime_symbol: "__glagol_fs_read_text_result", + params: STRING_PARAM, + return_type: RuntimeType::ResultStringI32, + promoted: true, + }, + RuntimeFunction { + source_name: "std.fs.write_text", + runtime_symbol: "__glagol_fs_write_text", + params: STRING_STRING_PARAMS, + return_type: RuntimeType::I32, + promoted: true, + }, + RuntimeFunction { + source_name: "std.fs.write_text_result", + runtime_symbol: "__glagol_fs_write_text_result", + params: STRING_STRING_PARAMS, + return_type: RuntimeType::ResultI32I32, + promoted: true, + }, + RuntimeFunction { + source_name: "std.vec.i32.empty", + runtime_symbol: "__glagol_vec_i32_empty", + params: &[], + return_type: RuntimeType::VecI32, + promoted: true, + }, + RuntimeFunction { + source_name: "std.vec.i32.append", + runtime_symbol: "__glagol_vec_i32_append", + params: VEC_I32_I32_PARAMS, + return_type: RuntimeType::VecI32, + promoted: true, + }, + RuntimeFunction { + source_name: "std.vec.i32.len", + runtime_symbol: "__glagol_vec_i32_len", + params: VEC_I32_PARAM, + return_type: RuntimeType::I32, + promoted: true, + }, + RuntimeFunction { + source_name: "std.vec.i32.index", + runtime_symbol: "__glagol_vec_i32_index", + params: VEC_I32_I32_PARAMS, + return_type: RuntimeType::I32, + promoted: true, + }, + RuntimeFunction { + source_name: "std.vec.i64.empty", + runtime_symbol: "__glagol_vec_i64_empty", + params: &[], + return_type: RuntimeType::VecI64, + promoted: true, + }, + RuntimeFunction { + source_name: "std.vec.i64.append", + runtime_symbol: "__glagol_vec_i64_append", + params: VEC_I64_I64_PARAMS, + return_type: RuntimeType::VecI64, + promoted: true, + }, + RuntimeFunction { + source_name: "std.vec.i64.len", + runtime_symbol: "__glagol_vec_i64_len", + params: VEC_I64_PARAM, + return_type: RuntimeType::I32, + promoted: true, + }, + RuntimeFunction { + source_name: "std.vec.i64.index", + runtime_symbol: "__glagol_vec_i64_index", + params: VEC_I64_I32_PARAMS, + return_type: RuntimeType::I64, + promoted: true, + }, + RuntimeFunction { + source_name: "std.vec.f64.empty", + runtime_symbol: "__glagol_vec_f64_empty", + params: &[], + return_type: RuntimeType::VecF64, + promoted: true, + }, + RuntimeFunction { + source_name: "std.vec.f64.append", + runtime_symbol: "__glagol_vec_f64_append", + params: VEC_F64_F64_PARAMS, + return_type: RuntimeType::VecF64, + promoted: true, + }, + RuntimeFunction { + source_name: "std.vec.f64.len", + runtime_symbol: "__glagol_vec_f64_len", + params: VEC_F64_PARAM, + return_type: RuntimeType::I32, + promoted: true, + }, + RuntimeFunction { + source_name: "std.vec.f64.index", + runtime_symbol: "__glagol_vec_f64_index", + params: VEC_F64_I32_PARAMS, + return_type: RuntimeType::F64, + promoted: true, + }, + RuntimeFunction { + source_name: "std.vec.bool.empty", + runtime_symbol: "__glagol_vec_bool_empty", + params: &[], + return_type: RuntimeType::VecBool, + promoted: true, + }, + RuntimeFunction { + source_name: "std.vec.bool.append", + runtime_symbol: "__glagol_vec_bool_append", + params: VEC_BOOL_BOOL_PARAMS, + return_type: RuntimeType::VecBool, + promoted: true, + }, + RuntimeFunction { + source_name: "std.vec.bool.len", + runtime_symbol: "__glagol_vec_bool_len", + params: VEC_BOOL_PARAM, + return_type: RuntimeType::I32, + promoted: true, + }, + RuntimeFunction { + source_name: "std.vec.bool.index", + runtime_symbol: "__glagol_vec_bool_index", + params: VEC_BOOL_I32_PARAMS, + return_type: RuntimeType::Bool, + promoted: true, + }, + RuntimeFunction { + source_name: "std.vec.string.empty", + runtime_symbol: "__glagol_vec_string_empty", + params: &[], + return_type: RuntimeType::VecString, + promoted: true, + }, + RuntimeFunction { + source_name: "std.vec.string.append", + runtime_symbol: "__glagol_vec_string_append", + params: VEC_STRING_STRING_PARAMS, + return_type: RuntimeType::VecString, + promoted: true, + }, + RuntimeFunction { + source_name: "std.vec.string.len", + runtime_symbol: "__glagol_vec_string_len", + params: VEC_STRING_PARAM, + return_type: RuntimeType::I32, + promoted: true, + }, + RuntimeFunction { + source_name: "std.vec.string.index", + runtime_symbol: "__glagol_vec_string_index", + params: VEC_STRING_I32_PARAMS, + return_type: RuntimeType::String, + promoted: true, + }, + RuntimeFunction { + source_name: "std.time.monotonic_ms", + runtime_symbol: "__glagol_time_monotonic_ms", + params: &[], + return_type: RuntimeType::I32, + promoted: true, + }, + RuntimeFunction { + source_name: "std.time.sleep_ms", + runtime_symbol: "__glagol_time_sleep_ms", + params: I32_PARAM, + return_type: RuntimeType::Unit, + promoted: true, + }, + RuntimeFunction { + source_name: "std.random.i32", + runtime_symbol: "__glagol_random_i32", + params: &[], + return_type: RuntimeType::I32, + promoted: true, + }, + RuntimeFunction { + source_name: "std.num.i32_to_i64", + runtime_symbol: "std.num.i32_to_i64", + params: I32_PARAM, + return_type: RuntimeType::I64, + promoted: true, + }, + RuntimeFunction { + source_name: "std.num.i32_to_f64", + runtime_symbol: "std.num.i32_to_f64", + params: I32_PARAM, + return_type: RuntimeType::F64, + promoted: true, + }, + RuntimeFunction { + source_name: "std.num.i64_to_f64", + runtime_symbol: "std.num.i64_to_f64", + params: I64_PARAM, + return_type: RuntimeType::F64, + promoted: true, + }, + RuntimeFunction { + source_name: "std.num.u32_to_u64", + runtime_symbol: "std.num.u32_to_u64", + params: U32_PARAM, + return_type: RuntimeType::U64, + promoted: true, + }, + RuntimeFunction { + source_name: "std.num.u32_to_i64", + runtime_symbol: "std.num.u32_to_i64", + params: U32_PARAM, + return_type: RuntimeType::I64, + promoted: true, + }, + RuntimeFunction { + source_name: "std.num.u32_to_f64", + runtime_symbol: "std.num.u32_to_f64", + params: U32_PARAM, + return_type: RuntimeType::F64, + promoted: true, + }, + RuntimeFunction { + source_name: "std.num.u64_to_f64", + runtime_symbol: "std.num.u64_to_f64", + params: U64_PARAM, + return_type: RuntimeType::F64, + promoted: true, + }, + RuntimeFunction { + source_name: "std.num.i64_to_i32_result", + runtime_symbol: "std.num.i64_to_i32_result", + params: I64_PARAM, + return_type: RuntimeType::ResultI32I32, + promoted: true, + }, + RuntimeFunction { + source_name: "std.num.i32_to_u32_result", + runtime_symbol: "std.num.i32_to_u32_result", + params: I32_PARAM, + return_type: RuntimeType::ResultU32I32, + promoted: true, + }, + RuntimeFunction { + source_name: "std.num.i64_to_u64_result", + runtime_symbol: "std.num.i64_to_u64_result", + params: I64_PARAM, + return_type: RuntimeType::ResultU64I32, + promoted: true, + }, + RuntimeFunction { + source_name: "std.num.u64_to_i64_result", + runtime_symbol: "std.num.u64_to_i64_result", + params: U64_PARAM, + return_type: RuntimeType::ResultI64I32, + promoted: true, + }, + RuntimeFunction { + source_name: "std.num.f64_to_i32_result", + runtime_symbol: "std.num.f64_to_i32_result", + params: F64_PARAM, + return_type: RuntimeType::ResultI32I32, + promoted: true, + }, + RuntimeFunction { + source_name: "std.num.f64_to_i64_result", + runtime_symbol: "std.num.f64_to_i64_result", + params: F64_PARAM, + return_type: RuntimeType::ResultI64I32, + promoted: true, + }, + RuntimeFunction { + source_name: "std.num.i32_to_string", + runtime_symbol: "__glagol_num_i32_to_string", + params: I32_PARAM, + return_type: RuntimeType::String, + promoted: true, + }, + RuntimeFunction { + source_name: "std.num.i64_to_string", + runtime_symbol: "__glagol_num_i64_to_string", + params: I64_PARAM, + return_type: RuntimeType::String, + promoted: true, + }, + RuntimeFunction { + source_name: "std.num.u32_to_string", + runtime_symbol: "__glagol_num_u32_to_string", + params: U32_PARAM, + return_type: RuntimeType::String, + promoted: true, + }, + RuntimeFunction { + source_name: "std.num.u64_to_string", + runtime_symbol: "__glagol_num_u64_to_string", + params: U64_PARAM, + return_type: RuntimeType::String, + promoted: true, + }, + RuntimeFunction { + source_name: "std.num.f64_to_string", + runtime_symbol: "__glagol_num_f64_to_string", + params: F64_PARAM, + return_type: RuntimeType::String, + promoted: true, + }, +]; + +const RESERVED_HELPER_SYMBOLS: &[&str] = &[ + "std.result.is_ok", + "std.result.is_err", + "std.result.unwrap_ok", + "std.result.unwrap_err", + "__glagol_string_eq", + "__glagol_array_bounds_trap", + "__glagol_unwrap_some_trap", + "__glagol_unwrap_ok_trap", + "__glagol_unwrap_err_trap", + "__glagol_process_init", + "__glagol_io_read_stdin_result", + "__glagol_process_arg_trap", + "__glagol_process_arg_result", + "__glagol_env_get_result", + "__glagol_fs_read_trap", + "__glagol_fs_read_text_result", + "__glagol_fs_write_text_result", + "__glagol_vec_i32_eq", + "__glagol_vec_i32_allocation_trap", + "__glagol_vec_i32_index_trap", + "__glagol_vec_i64_eq", + "__glagol_vec_i64_allocation_trap", + "__glagol_vec_i64_index_trap", + "__glagol_vec_string_eq", + "__glagol_vec_string_allocation_trap", + "__glagol_vec_string_index_trap", + "__glagol_time_monotonic_unavailable_trap", + "__glagol_time_sleep_negative_trap", + "__glagol_time_sleep_failed_trap", + "__glagol_string_parse_i32_result", + "__glagol_string_parse_i64_result", + "__glagol_string_parse_u32_result", + "__glagol_string_parse_u64_result", + "__glagol_string_parse_f64_result", + "__glagol_string_parse_bool_result", + "__glagol_num_u32_to_string", + "__glagol_num_u64_to_string", + "__glagol_num_f64_to_string", + "print_f64", + "print_u32", + "print_u64", + "print_i64", +]; + +pub fn function(source_name: &str) -> Option<&'static RuntimeFunction> { + FUNCTIONS + .iter() + .find(|function| function.source_name == source_name) +} + +pub fn is_promoted_source_name(source_name: &str) -> bool { + function(source_name).is_some_and(|function| function.promoted) +} + +pub fn is_reserved_name(name: &str) -> bool { + FUNCTIONS + .iter() + .any(|function| function.source_name == name || function.runtime_symbol == name) + || RESERVED_HELPER_SYMBOLS.contains(&name) +} + +pub fn is_standard_path(source_name: &str) -> bool { + source_name.starts_with("std.") +} + +pub fn runtime_symbol(source_name: &str) -> Option<&'static str> { + function(source_name).map(|function| function.runtime_symbol) +} + +pub fn unsupported_standard_library_call(file: &str, span: Span, source_name: &str) -> Diagnostic { + Diagnostic::new( + file, + "UnsupportedStandardLibraryCall", + format!("standard library call `{}` is not supported", source_name), + ) + .with_span(span) + .expected("std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + .found(source_name) + .hint("use a promoted standard-runtime name or a legacy intrinsic alias") +} diff --git a/compiler/src/test_runner.rs b/compiler/src/test_runner.rs new file mode 100644 index 0000000..18057a0 --- /dev/null +++ b/compiler/src/test_runner.rs @@ -0,0 +1,3630 @@ +use std::{ + collections::{HashMap, HashSet}, + env, fs, + sync::OnceLock, + time::Instant, +}; + +use crate::{ + ast::{BinaryOp, MatchPatternKind}, + check::{CheckedFunction, CheckedProgram, TExpr, TExprKind, TMatchArm}, + diag::Diagnostic, + std_runtime, + types::Type, +}; + +const MAX_TEST_CALL_DEPTH: usize = 1024; +const MAX_TEST_WHILE_ITERATIONS: usize = 1_000_000; +static MONOTONIC_START: OnceLock = OnceLock::new(); + +pub fn check_output(program: &CheckedProgram) -> String { + let mut output = String::new(); + + for test in &program.tests { + output.push_str("test "); + write_test_name(&test.name, &mut output); + output.push_str(" ... checked\n"); + } + + output.push_str(&format!("{} test(s) checked\n", program.tests.len())); + output +} + +pub struct TestRunSuccess { + pub output: String, + pub report: TestReport, +} + +pub struct TestRunFailure { + pub diagnostics: Vec, + pub report: Option, +} + +#[derive(Clone)] +pub struct TestReport { + pub total_discovered: usize, + pub selected: usize, + pub passed: usize, + pub failed: usize, + pub skipped: usize, + pub filter: Option, +} + +impl TestRunFailure { + pub fn before_execution(diagnostics: Vec) -> Self { + Self { + diagnostics, + report: None, + } + } +} + +pub fn run( + _file: &str, + program: &CheckedProgram, + filter: Option<&str>, +) -> Result { + let functions = program + .functions + .iter() + .map(|function| (function.name.as_str(), function)) + .collect::>(); + let foreign_imports = program + .c_imports + .iter() + .map(|import| import.name.as_str()) + .collect::>(); + let mut output = String::new(); + let mut errors = Vec::new(); + let mut report = TestReport { + total_discovered: program.tests.len(), + selected: 0, + passed: 0, + failed: 0, + skipped: 0, + filter: filter.map(str::to_string), + }; + + for test in &program.tests { + if let Some(filter) = filter { + if !test.name.contains(filter) { + report.skipped += 1; + output.push_str("test "); + write_test_name(&test.name, &mut output); + output.push_str(" ... skipped\n"); + continue; + } + } + + report.selected += 1; + let mut locals = HashMap::new(); + let test_file = test.file.as_str(); + match eval_body( + test_file, + &test.body, + &mut locals, + &functions, + &foreign_imports, + 0, + ) { + Ok(Value::Bool(true)) => { + report.passed += 1; + output.push_str("test "); + write_test_name(&test.name, &mut output); + output.push_str(" ... ok\n"); + } + Ok(Value::Bool(false)) => { + report.failed += 1; + errors.push( + Diagnostic::new( + test_file, + "TestFailed", + format!("test `{}` failed", test.name), + ) + .with_span(test.span) + .expected("true") + .found("false"), + ); + } + Ok(value) => { + report.failed += 1; + errors.push( + Diagnostic::new( + test_file, + "UnsupportedTestExpression", + format!( + "test `{}` produced unsupported value `{}`", + test.name, + value.ty() + ), + ) + .with_span(test.span), + ); + } + Err(err) => { + report.failed += 1; + errors.push(err); + } + } + } + + if errors.is_empty() { + output.push_str(&format!("{} test(s) passed", report.passed)); + if filter.is_some() { + write_report_suffix(&report, &mut output); + } + output.push('\n'); + Ok(TestRunSuccess { output, report }) + } else { + Err(TestRunFailure { + diagnostics: errors, + report: Some(report), + }) + } +} + +fn write_report_suffix(report: &TestReport, output: &mut String) { + output.push_str(&format!( + " (total_discovered {}, selected {}, passed {}, failed {}, skipped {}", + report.total_discovered, report.selected, report.passed, report.failed, report.skipped + )); + if let Some(filter) = report.filter.as_deref() { + output.push_str(", filter "); + write_test_name(filter, output); + } + output.push(')'); +} + +#[derive(Debug, Clone, PartialEq)] +enum Value { + I32(i32), + I64(i64), + U32(u32), + U64(u64), + F64(f64), + Bool(bool), + String(String), + Array(ArrayValue), + VecI32(Vec), + VecI64(Vec), + VecF64(Vec), + VecBool(Vec), + VecString(Vec), + OptionI32 { + is_some: bool, + payload: i32, + }, + OptionI64 { + is_some: bool, + payload: i64, + }, + OptionU32 { + is_some: bool, + payload: u32, + }, + OptionU64 { + is_some: bool, + payload: u64, + }, + OptionF64 { + is_some: bool, + payload: f64, + }, + OptionBool { + is_some: bool, + payload: bool, + }, + OptionString { + is_some: bool, + payload: String, + }, + ResultI32 { + is_ok: bool, + payload: i32, + }, + ResultI64I32 { + is_ok: bool, + ok_payload: i64, + err_payload: i32, + }, + ResultU32I32 { + is_ok: bool, + payload: u32, + }, + ResultU64I32 { + is_ok: bool, + ok_payload: u64, + err_payload: i32, + }, + ResultF64I32 { + is_ok: bool, + ok_payload: f64, + err_payload: i32, + }, + ResultBoolI32 { + is_ok: bool, + ok_payload: bool, + err_payload: i32, + }, + ResultStringI32 { + is_ok: bool, + ok_payload: String, + err_payload: i32, + }, + Enum { + name: String, + variant: String, + discriminant: i32, + payload: Option, + }, + Struct { + name: String, + fields: HashMap, + }, + Unit, +} + +#[derive(Debug, Clone, PartialEq)] +enum ArrayValue { + Values { + element_ty: Type, + values: Vec, + }, +} + +impl ArrayValue { + fn ty(&self) -> Type { + match self { + Self::Values { element_ty, values } => { + Type::Array(Box::new(element_ty.clone()), values.len()) + } + } + } + + fn index_value(&self, index: usize) -> Option { + match self { + Self::Values { values, .. } => values.get(index).cloned(), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +enum EnumPayloadValue { + I32(i32), + I64(i64), + U32(u32), + U64(u64), + F64(f64), + Bool(bool), + String(String), + Struct { + name: String, + fields: HashMap, + }, +} + +impl EnumPayloadValue { + fn from_value(value: Value) -> Option { + match value { + Value::I32(value) => Some(Self::I32(value)), + Value::I64(value) => Some(Self::I64(value)), + Value::U32(value) => Some(Self::U32(value)), + Value::U64(value) => Some(Self::U64(value)), + Value::F64(value) => Some(Self::F64(value)), + Value::Bool(value) => Some(Self::Bool(value)), + Value::String(value) => Some(Self::String(value)), + Value::Struct { name, fields } => Some(Self::Struct { name, fields }), + _ => None, + } + } + + fn into_value(self) -> Value { + match self { + Self::I32(value) => Value::I32(value), + Self::I64(value) => Value::I64(value), + Self::U32(value) => Value::U32(value), + Self::U64(value) => Value::U64(value), + Self::F64(value) => Value::F64(value), + Self::Bool(value) => Value::Bool(value), + Self::String(value) => Value::String(value), + Self::Struct { name, fields } => Value::Struct { name, fields }, + } + } +} + +impl Value { + fn ty(&self) -> Type { + match self { + Self::I32(_) => Type::I32, + Self::I64(_) => Type::I64, + Self::U32(_) => Type::U32, + Self::U64(_) => Type::U64, + Self::F64(_) => Type::F64, + Self::Bool(_) => Type::Bool, + Self::String(_) => Type::String, + Self::Array(values) => values.ty(), + Self::VecI32(_) => Type::Vec(Box::new(Type::I32)), + Self::VecI64(_) => Type::Vec(Box::new(Type::I64)), + Self::VecF64(_) => Type::Vec(Box::new(Type::F64)), + Self::VecBool(_) => Type::Vec(Box::new(Type::Bool)), + Self::VecString(_) => Type::Vec(Box::new(Type::String)), + Self::OptionI32 { .. } => Type::Option(Box::new(Type::I32)), + Self::OptionI64 { .. } => Type::Option(Box::new(Type::I64)), + Self::OptionU32 { .. } => Type::Option(Box::new(Type::U32)), + Self::OptionU64 { .. } => Type::Option(Box::new(Type::U64)), + Self::OptionF64 { .. } => Type::Option(Box::new(Type::F64)), + Self::OptionBool { .. } => Type::Option(Box::new(Type::Bool)), + Self::OptionString { .. } => Type::Option(Box::new(Type::String)), + Self::ResultI32 { .. } => Type::Result(Box::new(Type::I32), Box::new(Type::I32)), + Self::ResultI64I32 { .. } => Type::Result(Box::new(Type::I64), Box::new(Type::I32)), + Self::ResultU32I32 { .. } => Type::Result(Box::new(Type::U32), Box::new(Type::I32)), + Self::ResultU64I32 { .. } => Type::Result(Box::new(Type::U64), Box::new(Type::I32)), + Self::ResultF64I32 { .. } => Type::Result(Box::new(Type::F64), Box::new(Type::I32)), + Self::ResultBoolI32 { .. } => Type::Result(Box::new(Type::Bool), Box::new(Type::I32)), + Self::ResultStringI32 { .. } => { + Type::Result(Box::new(Type::String), Box::new(Type::I32)) + } + Self::Enum { name, .. } => Type::Named(name.clone()), + Self::Struct { name, .. } => Type::Named(name.clone()), + Self::Unit => Type::Unit, + } + } + + fn as_i32(&self) -> Option { + match self { + Self::I32(value) => Some(*value), + _ => None, + } + } + + fn as_i64(&self) -> Option { + match self { + Self::I64(value) => Some(*value), + _ => None, + } + } + + fn as_u32(&self) -> Option { + match self { + Self::U32(value) => Some(*value), + _ => None, + } + } + + fn as_u64(&self) -> Option { + match self { + Self::U64(value) => Some(*value), + _ => None, + } + } + + fn as_f64(&self) -> Option { + match self { + Self::F64(value) => Some(*value), + _ => None, + } + } + + fn as_bool(&self) -> Option { + match self { + Self::Bool(value) => Some(*value), + _ => None, + } + } + + fn as_string(&self) -> Option<&str> { + match self { + Self::String(value) => Some(value), + _ => None, + } + } + + fn as_vec_i32(&self) -> Option<&[i32]> { + match self { + Self::VecI32(values) => Some(values), + _ => None, + } + } + + fn as_vec_i64(&self) -> Option<&[i64]> { + match self { + Self::VecI64(values) => Some(values), + _ => None, + } + } + + fn as_vec_f64(&self) -> Option<&[f64]> { + match self { + Self::VecF64(values) => Some(values), + _ => None, + } + } + + fn as_vec_bool(&self) -> Option<&[bool]> { + match self { + Self::VecBool(values) => Some(values), + _ => None, + } + } + + fn as_vec_string(&self) -> Option<&[String]> { + match self { + Self::VecString(values) => Some(values), + _ => None, + } + } +} + +fn parse_i32_result_value(value: &str) -> Value { + match parse_i32_strict_ascii(value.as_bytes()) { + Some(payload) => Value::ResultI32 { + is_ok: true, + payload, + }, + None => Value::ResultI32 { + is_ok: false, + payload: 1, + }, + } +} + +fn parse_i32_strict_ascii(bytes: &[u8]) -> Option { + if bytes.is_empty() { + return None; + } + + let mut index = 0; + let negative = bytes[index] == b'-'; + if negative { + index += 1; + if index == bytes.len() { + return None; + } + } + + let limit = if negative { + 2_147_483_648_i64 + } else { + 2_147_483_647_i64 + }; + let mut value = 0_i64; + + for &byte in &bytes[index..] { + if !byte.is_ascii_digit() { + return None; + } + + let digit = i64::from(byte - b'0'); + if value > (limit - digit) / 10 { + return None; + } + value = value * 10 + digit; + } + + if negative { + if value == 2_147_483_648_i64 { + Some(i32::MIN) + } else { + Some(-(value as i32)) + } + } else { + Some(value as i32) + } +} + +fn parse_i64_result_value(value: &str) -> Value { + match parse_i64_strict_ascii(value.as_bytes()) { + Some(payload) => Value::ResultI64I32 { + is_ok: true, + ok_payload: payload, + err_payload: 0, + }, + None => Value::ResultI64I32 { + is_ok: false, + ok_payload: 0, + err_payload: 1, + }, + } +} + +fn parse_i64_strict_ascii(bytes: &[u8]) -> Option { + if bytes.is_empty() { + return None; + } + + let mut index = 0; + let negative = bytes[index] == b'-'; + if negative { + index += 1; + if index == bytes.len() { + return None; + } + } + + let limit = if negative { + 9_223_372_036_854_775_808_u64 + } else { + 9_223_372_036_854_775_807_u64 + }; + let mut value = 0_u64; + + for &byte in &bytes[index..] { + if !byte.is_ascii_digit() { + return None; + } + + let digit = u64::from(byte - b'0'); + if value > (limit - digit) / 10 { + return None; + } + value = value * 10 + digit; + } + + if negative { + if value == 9_223_372_036_854_775_808_u64 { + Some(i64::MIN) + } else { + Some(-(value as i64)) + } + } else { + Some(value as i64) + } +} + +fn parse_u32_result_value(value: &str) -> Value { + match parse_u32_strict_ascii(value.as_bytes()) { + Some(payload) => Value::ResultU32I32 { + is_ok: true, + payload, + }, + None => Value::ResultU32I32 { + is_ok: false, + payload: 1, + }, + } +} + +fn parse_u32_strict_ascii(bytes: &[u8]) -> Option { + if bytes.is_empty() { + return None; + } + + let mut value = 0_u64; + for &byte in bytes { + if !byte.is_ascii_digit() { + return None; + } + + let digit = u64::from(byte - b'0'); + if value > ((u64::from(u32::MAX)) - digit) / 10 { + return None; + } + value = value * 10 + digit; + } + + Some(value as u32) +} + +fn parse_u64_result_value(value: &str) -> Value { + match parse_u64_strict_ascii(value.as_bytes()) { + Some(payload) => Value::ResultU64I32 { + is_ok: true, + ok_payload: payload, + err_payload: 0, + }, + None => Value::ResultU64I32 { + is_ok: false, + ok_payload: 0, + err_payload: 1, + }, + } +} + +fn parse_u64_strict_ascii(bytes: &[u8]) -> Option { + if bytes.is_empty() { + return None; + } + + let mut value = 0_u64; + for &byte in bytes { + if !byte.is_ascii_digit() { + return None; + } + + let digit = u64::from(byte - b'0'); + if value > (u64::MAX - digit) / 10 { + return None; + } + value = value * 10 + digit; + } + + Some(value) +} + +fn parse_f64_result_value(value: &str) -> Value { + match parse_f64_strict_ascii(value) { + Some(payload) => Value::ResultF64I32 { + is_ok: true, + ok_payload: payload, + err_payload: 0, + }, + None => Value::ResultF64I32 { + is_ok: false, + ok_payload: 0.0, + err_payload: 1, + }, + } +} + +fn parse_bool_result_value(value: &str) -> Value { + match value { + "true" => Value::ResultBoolI32 { + is_ok: true, + ok_payload: true, + err_payload: 0, + }, + "false" => Value::ResultBoolI32 { + is_ok: true, + ok_payload: false, + err_payload: 0, + }, + _ => Value::ResultBoolI32 { + is_ok: false, + ok_payload: false, + err_payload: 1, + }, + } +} + +fn parse_f64_strict_ascii(text: &str) -> Option { + let bytes = text.as_bytes(); + if !is_ascii_decimal_f64(bytes) { + return None; + } + + text.parse::().ok().filter(|value| value.is_finite()) +} + +fn is_ascii_decimal_f64(bytes: &[u8]) -> bool { + if bytes.is_empty() { + return false; + } + + let mut index = 0; + if bytes[index] == b'-' { + index += 1; + if index == bytes.len() { + return false; + } + } + + let whole_digits = consume_ascii_digits(bytes, &mut index); + if whole_digits == 0 || index >= bytes.len() || bytes[index] != b'.' { + return false; + } + index += 1; + let fractional_digits = consume_ascii_digits(bytes, &mut index); + if fractional_digits == 0 { + return false; + } + + index == bytes.len() +} + +fn consume_ascii_digits(bytes: &[u8], index: &mut usize) -> usize { + let start = *index; + while *index < bytes.len() && bytes[*index].is_ascii_digit() { + *index += 1; + } + *index - start +} + +fn format_f64_to_string(value: f64) -> String { + let mut text = format!("{value:.17}"); + if let Some(dot) = text.find('.') { + while text.len() > dot + 2 && text.ends_with('0') { + text.pop(); + } + } + text +} + +fn eval_expr( + file: &str, + expr: &TExpr, + locals: &mut HashMap, + functions: &HashMap<&str, &CheckedFunction>, + foreign_imports: &HashSet<&str>, + depth: usize, +) -> Result { + match &expr.kind { + TExprKind::Int(value) => Ok(Value::I32(*value)), + TExprKind::Int64(value) => Ok(Value::I64(*value)), + TExprKind::UInt32(value) => Ok(Value::U32(*value)), + TExprKind::UInt64(value) => Ok(Value::U64(*value)), + TExprKind::Float(value) => Ok(Value::F64(*value)), + TExprKind::Bool(value) => Ok(Value::Bool(*value)), + TExprKind::String(value) => Ok(Value::String(value.clone())), + TExprKind::EnumVariant { + enum_name, + variant, + discriminant, + payload, + } => { + let payload = match payload { + Some(payload) => { + let value = + eval_expr(file, payload, locals, functions, foreign_imports, depth)?; + let Some(value) = EnumPayloadValue::from_value(value) else { + return Err(unsupported_test_expr( + file, + expr, + "enum payloads outside the released direct scalar/string/struct families", + )); + }; + Some(value) + } + None => None, + }; + Ok(Value::Enum { + name: enum_name.clone(), + variant: variant.clone(), + discriminant: *discriminant, + payload, + }) + } + TExprKind::StructInit { name, fields } => { + let mut values = HashMap::new(); + for (field, value) in fields { + values.insert( + field.clone(), + eval_expr(file, value, locals, functions, foreign_imports, depth)?, + ); + } + Ok(Value::Struct { + name: name.clone(), + fields: values, + }) + } + TExprKind::ArrayInit { elements } => match &expr.ty { + Type::Array(inner, _) => { + let mut values = Vec::new(); + for element in elements { + let value = + eval_expr(file, element, locals, functions, foreign_imports, depth)?; + if value.ty() != **inner { + return Err(unsupported_test_expr( + file, + element, + "mismatched fixed array elements", + )); + } + values.push(value); + } + Ok(Value::Array(ArrayValue::Values { + element_ty: (**inner).clone(), + values, + })) + } + _ => Err(unsupported_test_expr( + file, + expr, + "unsupported fixed array elements", + )), + }, + TExprKind::OptionSome { value } => { + let value = eval_expr(file, value, locals, functions, foreign_imports, depth)?; + match &expr.ty { + Type::Option(inner) if **inner == Type::I32 => { + let Some(value) = value.as_i32() else { + return Err(unsupported_test_expr(file, expr, "non-i32 option payloads")); + }; + Ok(Value::OptionI32 { + is_some: true, + payload: value, + }) + } + Type::Option(inner) if **inner == Type::I64 => { + let Some(value) = value.as_i64() else { + return Err(unsupported_test_expr(file, expr, "non-i64 option payloads")); + }; + Ok(Value::OptionI64 { + is_some: true, + payload: value, + }) + } + Type::Option(inner) if **inner == Type::U32 => { + let Some(value) = value.as_u32() else { + return Err(unsupported_test_expr(file, expr, "non-u32 option payloads")); + }; + Ok(Value::OptionU32 { + is_some: true, + payload: value, + }) + } + Type::Option(inner) if **inner == Type::U64 => { + let Some(value) = value.as_u64() else { + return Err(unsupported_test_expr(file, expr, "non-u64 option payloads")); + }; + Ok(Value::OptionU64 { + is_some: true, + payload: value, + }) + } + Type::Option(inner) if **inner == Type::F64 => { + let Some(value) = value.as_f64() else { + return Err(unsupported_test_expr(file, expr, "non-f64 option payloads")); + }; + Ok(Value::OptionF64 { + is_some: true, + payload: value, + }) + } + Type::Option(inner) if **inner == Type::Bool => { + let Some(value) = value.as_bool() else { + return Err(unsupported_test_expr( + file, + expr, + "non-bool option payloads", + )); + }; + Ok(Value::OptionBool { + is_some: true, + payload: value, + }) + } + Type::Option(inner) if **inner == Type::String => { + let Some(value) = value.as_string() else { + return Err(unsupported_test_expr( + file, + expr, + "non-string option payloads", + )); + }; + Ok(Value::OptionString { + is_some: true, + payload: value.to_string(), + }) + } + _ => Err(unsupported_test_expr( + file, + expr, + "unsupported option payloads", + )), + } + } + TExprKind::OptionNone => match &expr.ty { + Type::Option(inner) if **inner == Type::I32 => Ok(Value::OptionI32 { + is_some: false, + payload: 0, + }), + Type::Option(inner) if **inner == Type::I64 => Ok(Value::OptionI64 { + is_some: false, + payload: 0, + }), + Type::Option(inner) if **inner == Type::U32 => Ok(Value::OptionU32 { + is_some: false, + payload: 0, + }), + Type::Option(inner) if **inner == Type::U64 => Ok(Value::OptionU64 { + is_some: false, + payload: 0, + }), + Type::Option(inner) if **inner == Type::F64 => Ok(Value::OptionF64 { + is_some: false, + payload: 0.0, + }), + Type::Option(inner) if **inner == Type::Bool => Ok(Value::OptionBool { + is_some: false, + payload: false, + }), + Type::Option(inner) if **inner == Type::String => Ok(Value::OptionString { + is_some: false, + payload: String::new(), + }), + _ => Err(unsupported_test_expr( + file, + expr, + "unsupported option payloads", + )), + }, + TExprKind::ResultOk { value } => { + let value = eval_expr(file, value, locals, functions, foreign_imports, depth)?; + match &expr.ty { + Type::Result(ok, err) if **ok == Type::I32 && **err == Type::I32 => { + let Some(value) = value.as_i32() else { + return Err(unsupported_test_expr(file, expr, "non-i32 result payloads")); + }; + Ok(Value::ResultI32 { + is_ok: true, + payload: value, + }) + } + Type::Result(ok, err) if **ok == Type::I64 && **err == Type::I32 => { + let Some(value) = value.as_i64() else { + return Err(unsupported_test_expr(file, expr, "non-i64 result payloads")); + }; + Ok(Value::ResultI64I32 { + is_ok: true, + ok_payload: value, + err_payload: 0, + }) + } + Type::Result(ok, err) if **ok == Type::U32 && **err == Type::I32 => { + let Some(value) = value.as_u32() else { + return Err(unsupported_test_expr(file, expr, "non-u32 result payloads")); + }; + Ok(Value::ResultU32I32 { + is_ok: true, + payload: value, + }) + } + Type::Result(ok, err) if **ok == Type::U64 && **err == Type::I32 => { + let Some(value) = value.as_u64() else { + return Err(unsupported_test_expr(file, expr, "non-u64 result payloads")); + }; + Ok(Value::ResultU64I32 { + is_ok: true, + ok_payload: value, + err_payload: 0, + }) + } + Type::Result(ok, err) if **ok == Type::F64 && **err == Type::I32 => { + let Some(value) = value.as_f64() else { + return Err(unsupported_test_expr(file, expr, "non-f64 result payloads")); + }; + Ok(Value::ResultF64I32 { + is_ok: true, + ok_payload: value, + err_payload: 0, + }) + } + Type::Result(ok, err) if **ok == Type::Bool && **err == Type::I32 => { + let Some(value) = value.as_bool() else { + return Err(unsupported_test_expr( + file, + expr, + "non-bool result payloads", + )); + }; + Ok(Value::ResultBoolI32 { + is_ok: true, + ok_payload: value, + err_payload: 0, + }) + } + Type::Result(ok, err) if **ok == Type::String && **err == Type::I32 => { + let Some(value) = value.as_string() else { + return Err(unsupported_test_expr( + file, + expr, + "non-string result ok payloads", + )); + }; + Ok(Value::ResultStringI32 { + is_ok: true, + ok_payload: value.to_string(), + err_payload: 0, + }) + } + _ => Err(unsupported_test_expr( + file, + expr, + "unsupported result payloads", + )), + } + } + TExprKind::ResultErr { value } => { + let value = eval_expr(file, value, locals, functions, foreign_imports, depth)?; + let Some(value) = value.as_i32() else { + return Err(unsupported_test_expr( + file, + expr, + "non-i32 result err payloads", + )); + }; + match &expr.ty { + Type::Result(ok, err) if **ok == Type::I32 && **err == Type::I32 => { + Ok(Value::ResultI32 { + is_ok: false, + payload: value, + }) + } + Type::Result(ok, err) if **ok == Type::I64 && **err == Type::I32 => { + Ok(Value::ResultI64I32 { + is_ok: false, + ok_payload: 0, + err_payload: value, + }) + } + Type::Result(ok, err) if **ok == Type::U32 && **err == Type::I32 => { + Ok(Value::ResultU32I32 { + is_ok: false, + payload: value as u32, + }) + } + Type::Result(ok, err) if **ok == Type::U64 && **err == Type::I32 => { + Ok(Value::ResultU64I32 { + is_ok: false, + ok_payload: 0, + err_payload: value, + }) + } + Type::Result(ok, err) if **ok == Type::F64 && **err == Type::I32 => { + Ok(Value::ResultF64I32 { + is_ok: false, + ok_payload: 0.0, + err_payload: value, + }) + } + Type::Result(ok, err) if **ok == Type::Bool && **err == Type::I32 => { + Ok(Value::ResultBoolI32 { + is_ok: false, + ok_payload: false, + err_payload: value, + }) + } + Type::Result(ok, err) if **ok == Type::String && **err == Type::I32 => { + Ok(Value::ResultStringI32 { + is_ok: false, + ok_payload: String::new(), + err_payload: value, + }) + } + _ => Err(unsupported_test_expr( + file, + expr, + "unsupported result payloads", + )), + } + } + TExprKind::OptionIsSome { value } => { + let value = eval_expr(file, value, locals, functions, foreign_imports, depth)?; + match value { + Value::OptionI32 { is_some, payload } => { + let _ = payload; + Ok(Value::Bool(is_some)) + } + Value::OptionI64 { is_some, payload } => { + let _ = payload; + Ok(Value::Bool(is_some)) + } + Value::OptionU32 { is_some, payload } => { + let _ = payload; + Ok(Value::Bool(is_some)) + } + Value::OptionU64 { is_some, payload } => { + let _ = payload; + Ok(Value::Bool(is_some)) + } + Value::OptionF64 { is_some, payload } => { + let _ = payload; + Ok(Value::Bool(is_some)) + } + Value::OptionBool { is_some, payload } => { + let _ = payload; + Ok(Value::Bool(is_some)) + } + Value::OptionString { is_some, payload } => { + let _ = payload; + Ok(Value::Bool(is_some)) + } + _ => Err(unsupported_test_expr( + file, + expr, + "option observation on non-option values", + )), + } + } + TExprKind::OptionIsNone { value } => { + let value = eval_expr(file, value, locals, functions, foreign_imports, depth)?; + match value { + Value::OptionI32 { is_some, payload } => { + let _ = payload; + Ok(Value::Bool(!is_some)) + } + Value::OptionI64 { is_some, payload } => { + let _ = payload; + Ok(Value::Bool(!is_some)) + } + Value::OptionU32 { is_some, payload } => { + let _ = payload; + Ok(Value::Bool(!is_some)) + } + Value::OptionU64 { is_some, payload } => { + let _ = payload; + Ok(Value::Bool(!is_some)) + } + Value::OptionF64 { is_some, payload } => { + let _ = payload; + Ok(Value::Bool(!is_some)) + } + Value::OptionBool { is_some, payload } => { + let _ = payload; + Ok(Value::Bool(!is_some)) + } + Value::OptionString { is_some, payload } => { + let _ = payload; + Ok(Value::Bool(!is_some)) + } + _ => Err(unsupported_test_expr( + file, + expr, + "option observation on non-option values", + )), + } + } + TExprKind::OptionUnwrapSome { value } => { + let value = eval_expr(file, value, locals, functions, foreign_imports, depth)?; + match value { + Value::OptionI32 { is_some, payload } => { + if !is_some { + return Err(runtime_trap( + file, + expr, + "slovo runtime error: unwrap_some on none", + )); + } + Ok(Value::I32(payload)) + } + Value::OptionI64 { is_some, payload } => { + if !is_some { + return Err(runtime_trap( + file, + expr, + "slovo runtime error: unwrap_some on none", + )); + } + Ok(Value::I64(payload)) + } + Value::OptionU32 { is_some, payload } => { + if !is_some { + return Err(runtime_trap( + file, + expr, + "slovo runtime error: unwrap_some on none", + )); + } + Ok(Value::U32(payload)) + } + Value::OptionU64 { is_some, payload } => { + if !is_some { + return Err(runtime_trap( + file, + expr, + "slovo runtime error: unwrap_some on none", + )); + } + Ok(Value::U64(payload)) + } + Value::OptionF64 { is_some, payload } => { + if !is_some { + return Err(runtime_trap( + file, + expr, + "slovo runtime error: unwrap_some on none", + )); + } + Ok(Value::F64(payload)) + } + Value::OptionBool { is_some, payload } => { + if !is_some { + return Err(runtime_trap( + file, + expr, + "slovo runtime error: unwrap_some on none", + )); + } + Ok(Value::Bool(payload)) + } + Value::OptionString { is_some, payload } => { + if !is_some { + return Err(runtime_trap( + file, + expr, + "slovo runtime error: unwrap_some on none", + )); + } + Ok(Value::String(payload)) + } + _ => Err(unsupported_test_expr( + file, + expr, + "option payload access on non-option values", + )), + } + } + TExprKind::ResultIsOk { value, .. } => { + let value = eval_expr(file, value, locals, functions, foreign_imports, depth)?; + match value { + Value::ResultI32 { is_ok, payload } => { + let _ = payload; + Ok(Value::Bool(is_ok)) + } + Value::ResultI64I32 { + is_ok, + ok_payload, + err_payload, + } => { + let _ = (ok_payload, err_payload); + Ok(Value::Bool(is_ok)) + } + Value::ResultU32I32 { is_ok, payload } => { + let _ = payload; + Ok(Value::Bool(is_ok)) + } + Value::ResultU64I32 { + is_ok, + ok_payload, + err_payload, + } => { + let _ = (ok_payload, err_payload); + Ok(Value::Bool(is_ok)) + } + Value::ResultF64I32 { + is_ok, + ok_payload, + err_payload, + } => { + let _ = (ok_payload, err_payload); + Ok(Value::Bool(is_ok)) + } + Value::ResultBoolI32 { + is_ok, + ok_payload, + err_payload, + } => { + let _ = (ok_payload, err_payload); + Ok(Value::Bool(is_ok)) + } + Value::ResultStringI32 { + is_ok, + ok_payload, + err_payload, + } => { + let _ = (ok_payload, err_payload); + Ok(Value::Bool(is_ok)) + } + _ => Err(unsupported_test_expr( + file, + expr, + "result observation on non-result values", + )), + } + } + TExprKind::ResultIsErr { value, .. } => { + let value = eval_expr(file, value, locals, functions, foreign_imports, depth)?; + match value { + Value::ResultI32 { is_ok, payload } => { + let _ = payload; + Ok(Value::Bool(!is_ok)) + } + Value::ResultI64I32 { + is_ok, + ok_payload, + err_payload, + } => { + let _ = (ok_payload, err_payload); + Ok(Value::Bool(!is_ok)) + } + Value::ResultU32I32 { is_ok, payload } => { + let _ = payload; + Ok(Value::Bool(!is_ok)) + } + Value::ResultU64I32 { + is_ok, + ok_payload, + err_payload, + } => { + let _ = (ok_payload, err_payload); + Ok(Value::Bool(!is_ok)) + } + Value::ResultF64I32 { + is_ok, + ok_payload, + err_payload, + } => { + let _ = (ok_payload, err_payload); + Ok(Value::Bool(!is_ok)) + } + Value::ResultBoolI32 { + is_ok, + ok_payload, + err_payload, + } => { + let _ = (ok_payload, err_payload); + Ok(Value::Bool(!is_ok)) + } + Value::ResultStringI32 { + is_ok, + ok_payload, + err_payload, + } => { + let _ = (ok_payload, err_payload); + Ok(Value::Bool(!is_ok)) + } + _ => Err(unsupported_test_expr( + file, + expr, + "result observation on non-result values", + )), + } + } + TExprKind::ResultUnwrapOk { value, .. } => { + let value = eval_expr(file, value, locals, functions, foreign_imports, depth)?; + match value { + Value::ResultI32 { is_ok, payload } => { + if !is_ok { + return Err(runtime_trap( + file, + expr, + "slovo runtime error: unwrap_ok on err", + )); + } + Ok(Value::I32(payload)) + } + Value::ResultI64I32 { + is_ok, + ok_payload, + err_payload, + } => { + let _ = err_payload; + if !is_ok { + return Err(runtime_trap( + file, + expr, + "slovo runtime error: unwrap_ok on err", + )); + } + Ok(Value::I64(ok_payload)) + } + Value::ResultU32I32 { is_ok, payload } => { + if !is_ok { + return Err(runtime_trap( + file, + expr, + "slovo runtime error: unwrap_ok on err", + )); + } + Ok(Value::U32(payload)) + } + Value::ResultU64I32 { + is_ok, + ok_payload, + err_payload, + } => { + let _ = err_payload; + if !is_ok { + return Err(runtime_trap( + file, + expr, + "slovo runtime error: unwrap_ok on err", + )); + } + Ok(Value::U64(ok_payload)) + } + Value::ResultF64I32 { + is_ok, + ok_payload, + err_payload, + } => { + let _ = err_payload; + if !is_ok { + return Err(runtime_trap( + file, + expr, + "slovo runtime error: unwrap_ok on err", + )); + } + Ok(Value::F64(ok_payload)) + } + Value::ResultBoolI32 { + is_ok, + ok_payload, + err_payload, + } => { + let _ = err_payload; + if !is_ok { + return Err(runtime_trap( + file, + expr, + "slovo runtime error: unwrap_ok on err", + )); + } + Ok(Value::Bool(ok_payload)) + } + Value::ResultStringI32 { + is_ok, + ok_payload, + err_payload, + } => { + let _ = err_payload; + if !is_ok { + return Err(runtime_trap( + file, + expr, + "slovo runtime error: unwrap_ok on err", + )); + } + Ok(Value::String(ok_payload)) + } + _ => Err(unsupported_test_expr( + file, + expr, + "result payload access on non-result values", + )), + } + } + TExprKind::ResultUnwrapErr { value, .. } => { + let value = eval_expr(file, value, locals, functions, foreign_imports, depth)?; + match value { + Value::ResultI32 { is_ok, payload } => { + if is_ok { + return Err(runtime_trap( + file, + expr, + "slovo runtime error: unwrap_err on ok", + )); + } + Ok(Value::I32(payload)) + } + Value::ResultI64I32 { + is_ok, + ok_payload, + err_payload, + } => { + let _ = ok_payload; + if is_ok { + return Err(runtime_trap( + file, + expr, + "slovo runtime error: unwrap_err on ok", + )); + } + Ok(Value::I32(err_payload)) + } + Value::ResultU32I32 { is_ok, payload } => { + if is_ok { + return Err(runtime_trap( + file, + expr, + "slovo runtime error: unwrap_err on ok", + )); + } + Ok(Value::I32(payload as i32)) + } + Value::ResultU64I32 { + is_ok, + ok_payload, + err_payload, + } => { + let _ = ok_payload; + if is_ok { + return Err(runtime_trap( + file, + expr, + "slovo runtime error: unwrap_err on ok", + )); + } + Ok(Value::I32(err_payload)) + } + Value::ResultF64I32 { + is_ok, + ok_payload, + err_payload, + } => { + let _ = ok_payload; + if is_ok { + return Err(runtime_trap( + file, + expr, + "slovo runtime error: unwrap_err on ok", + )); + } + Ok(Value::I32(err_payload)) + } + Value::ResultBoolI32 { + is_ok, + ok_payload, + err_payload, + } => { + let _ = ok_payload; + if is_ok { + return Err(runtime_trap( + file, + expr, + "slovo runtime error: unwrap_err on ok", + )); + } + Ok(Value::I32(err_payload)) + } + Value::ResultStringI32 { + is_ok, + ok_payload, + err_payload, + } => { + let _ = ok_payload; + if is_ok { + return Err(runtime_trap( + file, + expr, + "slovo runtime error: unwrap_err on ok", + )); + } + Ok(Value::I32(err_payload)) + } + _ => Err(unsupported_test_expr( + file, + expr, + "result payload access on non-result values", + )), + } + } + TExprKind::FieldAccess { value, field } => { + let value = eval_expr(file, value, locals, functions, foreign_imports, depth)?; + let Value::Struct { name, fields } = value else { + return Err(unsupported_test_expr( + file, + expr, + "field access on non-struct values", + )); + }; + fields.get(field).cloned().ok_or_else(|| { + Diagnostic::new( + file, + "TestRuntimeError", + format!("struct `{}` has no test field `{}`", name, field), + ) + .with_span(expr.span) + }) + } + TExprKind::Index { array, index } => { + let array = eval_expr(file, array, locals, functions, foreign_imports, depth)?; + let index = eval_expr(file, index, locals, functions, foreign_imports, depth)?; + let Value::Array(values) = array else { + return Err(unsupported_test_expr( + file, + expr, + "indexing non-array values", + )); + }; + let Some(index) = index.as_i32() else { + return Err(unsupported_test_expr(file, expr, "non-i32 array indices")); + }; + let Ok(index) = usize::try_from(index) else { + return Err(runtime_trap( + file, + expr, + "slovo runtime error: array index out of bounds", + )); + }; + values.index_value(index).ok_or_else(|| { + runtime_trap(file, expr, "slovo runtime error: array index out of bounds") + }) + } + TExprKind::Var(name) => locals.get(name).cloned().ok_or_else(|| { + Diagnostic::new( + file, + "TestRuntimeError", + format!("unknown test local `{}`", name), + ) + .with_span(expr.span) + }), + TExprKind::Local { + name, initializer, .. + } => { + let value = eval_expr(file, initializer, locals, functions, foreign_imports, depth)?; + locals.insert(name.clone(), value); + Ok(Value::Unit) + } + TExprKind::Set { name, expr: value } => { + let value = eval_expr(file, value, locals, functions, foreign_imports, depth)?; + if !locals.contains_key(name) { + return Err(Diagnostic::new( + file, + "TestRuntimeError", + format!("unknown test local `{}`", name), + ) + .with_span(expr.span)); + } + locals.insert(name.clone(), value); + Ok(Value::Unit) + } + TExprKind::Binary { op, left, right } => { + let left = eval_expr(file, left, locals, functions, foreign_imports, depth)?; + let right = eval_expr(file, right, locals, functions, foreign_imports, depth)?; + eval_binary(file, expr, *op, left, right) + } + TExprKind::If { + condition, + then_expr, + else_expr, + } => { + let condition = eval_expr(file, condition, locals, functions, foreign_imports, depth)?; + let Some(condition) = condition.as_bool() else { + return Err(Diagnostic::new( + file, + "TestRuntimeError", + "`if` condition was not bool", + ) + .with_span(expr.span)); + }; + + if condition { + eval_expr(file, then_expr, locals, functions, foreign_imports, depth) + } else { + eval_expr(file, else_expr, locals, functions, foreign_imports, depth) + } + } + TExprKind::Match { subject, arms } => { + let subject_value = + eval_expr(file, subject, locals, functions, foreign_imports, depth)?; + eval_match( + file, + expr, + subject_value, + arms, + locals, + functions, + foreign_imports, + depth, + ) + } + TExprKind::While { condition, body } => { + for _ in 0..MAX_TEST_WHILE_ITERATIONS { + let condition = + eval_expr(file, condition, locals, functions, foreign_imports, depth)?; + let Some(condition) = condition.as_bool() else { + return Err(Diagnostic::new( + file, + "TestRuntimeError", + "`while` condition was not bool", + ) + .with_span(expr.span)); + }; + + if !condition { + return Ok(Value::Unit); + } + + for body_expr in body { + eval_expr(file, body_expr, locals, functions, foreign_imports, depth)?; + } + } + + Err(Diagnostic::new( + file, + "TestRuntimeError", + "test runner exceeded maximum while iteration count", + ) + .with_span(expr.span) + .hint("check for an unbounded `while` loop in the test expression")) + } + TExprKind::Unsafe { body } => { + let outer_names = locals.keys().cloned().collect::>(); + let value = eval_body(file, body, locals, functions, foreign_imports, depth)?; + locals.retain(|name, _| outer_names.contains(name)); + Ok(value) + } + TExprKind::Call { name, args } => { + let runtime_symbol = std_runtime::runtime_symbol(name).unwrap_or(name); + if runtime_symbol == "std.num.i32_to_i64" { + let Some(arg) = args.first() else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.num.i32_to_i64` calls", + )); + }; + let value = eval_expr(file, arg, locals, functions, foreign_imports, depth)?; + let Some(value) = value.as_i32() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.num.i32_to_i64` on non-i32 values", + )); + }; + return Ok(Value::I64(i64::from(value))); + } + if runtime_symbol == "std.num.i32_to_f64" { + let Some(arg) = args.first() else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.num.i32_to_f64` calls", + )); + }; + let value = eval_expr(file, arg, locals, functions, foreign_imports, depth)?; + let Some(value) = value.as_i32() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.num.i32_to_f64` on non-i32 values", + )); + }; + return Ok(Value::F64(f64::from(value))); + } + if runtime_symbol == "std.num.i64_to_f64" { + let Some(arg) = args.first() else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.num.i64_to_f64` calls", + )); + }; + let value = eval_expr(file, arg, locals, functions, foreign_imports, depth)?; + let Some(value) = value.as_i64() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.num.i64_to_f64` on non-i64 values", + )); + }; + return Ok(Value::F64(value as f64)); + } + if runtime_symbol == "std.num.i64_to_i32_result" { + let Some(arg) = args.first() else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.num.i64_to_i32_result` calls", + )); + }; + let value = eval_expr(file, arg, locals, functions, foreign_imports, depth)?; + let Some(value) = value.as_i64() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.num.i64_to_i32_result` on non-i64 values", + )); + }; + if i32::try_from(value).is_ok() { + return Ok(Value::ResultI32 { + is_ok: true, + payload: value as i32, + }); + } + return Ok(Value::ResultI32 { + is_ok: false, + payload: 1, + }); + } + if runtime_symbol == "std.num.f64_to_i32_result" { + let Some(arg) = args.first() else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.num.f64_to_i32_result` calls", + )); + }; + let value = eval_expr(file, arg, locals, functions, foreign_imports, depth)?; + let Some(value) = value.as_f64() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.num.f64_to_i32_result` on non-f64 values", + )); + }; + if value.is_finite() + && value.fract() == 0.0 + && value >= f64::from(i32::MIN) + && value <= f64::from(i32::MAX) + { + return Ok(Value::ResultI32 { + is_ok: true, + payload: value as i32, + }); + } + return Ok(Value::ResultI32 { + is_ok: false, + payload: 1, + }); + } + if runtime_symbol == "std.num.f64_to_i64_result" { + let Some(arg) = args.first() else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.num.f64_to_i64_result` calls", + )); + }; + let value = eval_expr(file, arg, locals, functions, foreign_imports, depth)?; + let Some(value) = value.as_f64() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.num.f64_to_i64_result` on non-f64 values", + )); + }; + if value.is_finite() + && value.fract() == 0.0 + && value >= i64::MIN as f64 + && value < 9_223_372_036_854_775_808.0 + { + return Ok(Value::ResultI64I32 { + is_ok: true, + ok_payload: value as i64, + err_payload: 0, + }); + } + return Ok(Value::ResultI64I32 { + is_ok: false, + ok_payload: 0, + err_payload: 1, + }); + } + if runtime_symbol == "__glagol_num_i32_to_string" { + let Some(arg) = args.first() else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.num.i32_to_string` calls", + )); + }; + let value = eval_expr(file, arg, locals, functions, foreign_imports, depth)?; + let Some(value) = value.as_i32() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.num.i32_to_string` on non-i32 values", + )); + }; + return Ok(Value::String(value.to_string())); + } + if runtime_symbol == "__glagol_num_u32_to_string" { + let Some(arg) = args.first() else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.num.u32_to_string` calls", + )); + }; + let value = eval_expr(file, arg, locals, functions, foreign_imports, depth)?; + let Some(value) = value.as_u32() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.num.u32_to_string` on non-u32 values", + )); + }; + return Ok(Value::String(value.to_string())); + } + if runtime_symbol == "__glagol_num_i64_to_string" { + let Some(arg) = args.first() else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.num.i64_to_string` calls", + )); + }; + let value = eval_expr(file, arg, locals, functions, foreign_imports, depth)?; + let Some(value) = value.as_i64() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.num.i64_to_string` on non-i64 values", + )); + }; + return Ok(Value::String(value.to_string())); + } + if runtime_symbol == "__glagol_num_u64_to_string" { + let Some(arg) = args.first() else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.num.u64_to_string` calls", + )); + }; + let value = eval_expr(file, arg, locals, functions, foreign_imports, depth)?; + let Some(value) = value.as_u64() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.num.u64_to_string` on non-u64 values", + )); + }; + return Ok(Value::String(value.to_string())); + } + if runtime_symbol == "__glagol_num_f64_to_string" { + let Some(arg) = args.first() else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.num.f64_to_string` calls", + )); + }; + let value = eval_expr(file, arg, locals, functions, foreign_imports, depth)?; + let Some(value) = value.as_f64() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.num.f64_to_string` on non-f64 values", + )); + }; + return Ok(Value::String(format_f64_to_string(value))); + } + if runtime_symbol == "print_i32" { + return Err(unsupported_test_expr( + file, + expr, + "`print_i32` calls while running tests", + )); + } + if runtime_symbol == "print_f64" { + let [arg] = args.as_slice() else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.io.print_f64` calls", + )); + }; + let value = eval_expr(file, arg, locals, functions, foreign_imports, depth)?; + if value.as_f64().is_none() { + return Err(unsupported_test_expr( + file, + expr, + "`std.io.print_f64` on non-f64 values", + )); + } + return Ok(Value::Unit); + } + if runtime_symbol == "print_i64" { + let [arg] = args.as_slice() else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.io.print_i64` calls", + )); + }; + let value = eval_expr(file, arg, locals, functions, foreign_imports, depth)?; + if value.as_i64().is_none() { + return Err(unsupported_test_expr( + file, + expr, + "`std.io.print_i64` on non-i64 values", + )); + } + return Ok(Value::Unit); + } + if runtime_symbol == "print_u32" { + let [arg] = args.as_slice() else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.io.print_u32` calls", + )); + }; + let value = eval_expr(file, arg, locals, functions, foreign_imports, depth)?; + if value.as_u32().is_none() { + return Err(unsupported_test_expr( + file, + expr, + "`std.io.print_u32` on non-u32 values", + )); + } + return Ok(Value::Unit); + } + if runtime_symbol == "print_u64" { + let [arg] = args.as_slice() else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.io.print_u64` calls", + )); + }; + let value = eval_expr(file, arg, locals, functions, foreign_imports, depth)?; + if value.as_u64().is_none() { + return Err(unsupported_test_expr( + file, + expr, + "`std.io.print_u64` on non-u64 values", + )); + } + return Ok(Value::Unit); + } + if runtime_symbol == "print_string" || runtime_symbol == "print_bool" { + return Err(unsupported_test_expr( + file, + expr, + "print calls while running tests", + )); + } + if runtime_symbol == "__glagol_io_eprint" { + return Err(unsupported_test_expr( + file, + expr, + "`std.io.eprint` calls while running tests", + )); + } + if runtime_symbol == "__glagol_io_read_stdin_result" { + return Ok(Value::ResultStringI32 { + is_ok: true, + ok_payload: String::new(), + err_payload: 0, + }); + } + if runtime_symbol == "string_len" { + let Some(arg) = args.first() else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `string_len` calls", + )); + }; + let value = eval_expr(file, arg, locals, functions, foreign_imports, depth)?; + let Some(value) = value.as_string() else { + return Err(unsupported_test_expr( + file, + expr, + "`string_len` on non-string values", + )); + }; + let len = i32::try_from(value.len()).map_err(|_| { + Diagnostic::new(file, "TestRuntimeError", "string length exceeded i32") + .with_span(expr.span) + })?; + return Ok(Value::I32(len)); + } + if runtime_symbol == "__glagol_string_concat" { + let Some(left) = args.first() else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.string.concat` calls", + )); + }; + let Some(right) = args.get(1) else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.string.concat` calls", + )); + }; + let left = eval_expr(file, left, locals, functions, foreign_imports, depth)?; + let right = eval_expr(file, right, locals, functions, foreign_imports, depth)?; + let Some(left) = left.as_string() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.string.concat` on non-string values", + )); + }; + let Some(right) = right.as_string() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.string.concat` on non-string values", + )); + }; + return Ok(Value::String(format!("{}{}", left, right))); + } + if runtime_symbol == "__glagol_string_parse_i32_result" { + let Some(arg) = args.first() else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.string.parse_i32_result` calls", + )); + }; + let value = eval_expr(file, arg, locals, functions, foreign_imports, depth)?; + let Some(value) = value.as_string() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.string.parse_i32_result` on non-string values", + )); + }; + return Ok(parse_i32_result_value(value)); + } + if runtime_symbol == "__glagol_string_parse_i64_result" { + let Some(arg) = args.first() else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.string.parse_i64_result` calls", + )); + }; + let value = eval_expr(file, arg, locals, functions, foreign_imports, depth)?; + let Some(value) = value.as_string() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.string.parse_i64_result` on non-string values", + )); + }; + return Ok(parse_i64_result_value(value)); + } + if runtime_symbol == "__glagol_string_parse_u32_result" { + let Some(arg) = args.first() else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.string.parse_u32_result` calls", + )); + }; + let value = eval_expr(file, arg, locals, functions, foreign_imports, depth)?; + let Some(value) = value.as_string() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.string.parse_u32_result` on non-string values", + )); + }; + return Ok(parse_u32_result_value(value)); + } + if runtime_symbol == "__glagol_string_parse_u64_result" { + let Some(arg) = args.first() else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.string.parse_u64_result` calls", + )); + }; + let value = eval_expr(file, arg, locals, functions, foreign_imports, depth)?; + let Some(value) = value.as_string() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.string.parse_u64_result` on non-string values", + )); + }; + return Ok(parse_u64_result_value(value)); + } + if runtime_symbol == "__glagol_string_parse_f64_result" { + let Some(arg) = args.first() else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.string.parse_f64_result` calls", + )); + }; + let value = eval_expr(file, arg, locals, functions, foreign_imports, depth)?; + let Some(value) = value.as_string() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.string.parse_f64_result` on non-string values", + )); + }; + return Ok(parse_f64_result_value(value)); + } + if runtime_symbol == "__glagol_string_parse_bool_result" { + let Some(arg) = args.first() else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.string.parse_bool_result` calls", + )); + }; + let value = eval_expr(file, arg, locals, functions, foreign_imports, depth)?; + let Some(value) = value.as_string() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.string.parse_bool_result` on non-string values", + )); + }; + return Ok(parse_bool_result_value(value)); + } + if runtime_symbol == "__glagol_process_argc" { + let argc = i32::try_from(env::args().count()).map_err(|_| { + Diagnostic::new( + file, + "TestRuntimeError", + "process argument count exceeded i32", + ) + .with_span(expr.span) + })?; + return Ok(Value::I32(argc)); + } + if runtime_symbol == "__glagol_process_arg" { + let Some(index) = args.first() else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.process.arg` calls", + )); + }; + let index = eval_expr(file, index, locals, functions, foreign_imports, depth)?; + let Some(index) = index.as_i32() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.process.arg` with non-i32 index", + )); + }; + let Ok(index) = usize::try_from(index) else { + return Err(runtime_trap( + file, + expr, + "slovo runtime error: process argument index out of bounds", + )); + }; + return env::args().nth(index).map(Value::String).ok_or_else(|| { + runtime_trap( + file, + expr, + "slovo runtime error: process argument index out of bounds", + ) + }); + } + if runtime_symbol == "__glagol_process_arg_result" { + let Some(index) = args.first() else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.process.arg_result` calls", + )); + }; + let index = eval_expr(file, index, locals, functions, foreign_imports, depth)?; + let Some(index) = index.as_i32() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.process.arg_result` with non-i32 index", + )); + }; + let value = usize::try_from(index) + .ok() + .and_then(|index| env::args().nth(index)); + return Ok(match value { + Some(value) => Value::ResultStringI32 { + is_ok: true, + ok_payload: value, + err_payload: 0, + }, + None => Value::ResultStringI32 { + is_ok: false, + ok_payload: String::new(), + err_payload: 1, + }, + }); + } + if runtime_symbol == "__glagol_env_get" { + let Some(name) = args.first() else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.env.get` calls", + )); + }; + let name = eval_expr(file, name, locals, functions, foreign_imports, depth)?; + let Some(name) = name.as_string() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.env.get` on non-string values", + )); + }; + return Ok(Value::String(env::var(name).unwrap_or_default())); + } + if runtime_symbol == "__glagol_env_get_result" { + let Some(name) = args.first() else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.env.get_result` calls", + )); + }; + let name = eval_expr(file, name, locals, functions, foreign_imports, depth)?; + let Some(name) = name.as_string() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.env.get_result` on non-string values", + )); + }; + return Ok(match env::var(name) { + Ok(value) => Value::ResultStringI32 { + is_ok: true, + ok_payload: value, + err_payload: 0, + }, + Err(_) => Value::ResultStringI32 { + is_ok: false, + ok_payload: String::new(), + err_payload: 1, + }, + }); + } + if runtime_symbol == "__glagol_fs_read_text" { + let Some(path) = args.first() else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.fs.read_text` calls", + )); + }; + let path = eval_expr(file, path, locals, functions, foreign_imports, depth)?; + let Some(path) = path.as_string() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.fs.read_text` on non-string values", + )); + }; + return fs::read_to_string(path).map(Value::String).map_err(|_| { + runtime_trap(file, expr, "slovo runtime error: file read failed") + }); + } + if runtime_symbol == "__glagol_fs_read_text_result" { + let Some(path) = args.first() else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.fs.read_text_result` calls", + )); + }; + let path = eval_expr(file, path, locals, functions, foreign_imports, depth)?; + let Some(path) = path.as_string() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.fs.read_text_result` on non-string values", + )); + }; + return Ok(match fs::read_to_string(path) { + Ok(value) => Value::ResultStringI32 { + is_ok: true, + ok_payload: value, + err_payload: 0, + }, + Err(_) => Value::ResultStringI32 { + is_ok: false, + ok_payload: String::new(), + err_payload: 1, + }, + }); + } + if runtime_symbol == "__glagol_fs_write_text" { + let Some(path) = args.first() else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.fs.write_text` calls", + )); + }; + let Some(text) = args.get(1) else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.fs.write_text` calls", + )); + }; + let path = eval_expr(file, path, locals, functions, foreign_imports, depth)?; + let text = eval_expr(file, text, locals, functions, foreign_imports, depth)?; + let Some(path) = path.as_string() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.fs.write_text` path on non-string values", + )); + }; + let Some(text) = text.as_string() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.fs.write_text` text on non-string values", + )); + }; + return Ok(Value::I32(if fs::write(path, text).is_ok() { + 0 + } else { + 1 + })); + } + if runtime_symbol == "__glagol_fs_write_text_result" { + let Some(path) = args.first() else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.fs.write_text_result` calls", + )); + }; + let Some(text) = args.get(1) else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.fs.write_text_result` calls", + )); + }; + let path = eval_expr(file, path, locals, functions, foreign_imports, depth)?; + let text = eval_expr(file, text, locals, functions, foreign_imports, depth)?; + let Some(path) = path.as_string() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.fs.write_text_result` path on non-string values", + )); + }; + let Some(text) = text.as_string() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.fs.write_text_result` text on non-string values", + )); + }; + let status = if fs::write(path, text).is_ok() { 0 } else { 1 }; + return Ok(Value::ResultI32 { + is_ok: status == 0, + payload: status, + }); + } + if runtime_symbol == "__glagol_vec_i32_empty" { + return Ok(Value::VecI32(Vec::new())); + } + if runtime_symbol == "__glagol_vec_i32_append" { + let Some(values) = args.first() else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.vec.i32.append` calls", + )); + }; + let Some(element) = args.get(1) else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.vec.i32.append` calls", + )); + }; + let values = eval_expr(file, values, locals, functions, foreign_imports, depth)?; + let element = eval_expr(file, element, locals, functions, foreign_imports, depth)?; + let Some(values) = values.as_vec_i32() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.vec.i32.append` on non-vector values", + )); + }; + let Some(element) = element.as_i32() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.vec.i32.append` with non-i32 elements", + )); + }; + let mut appended = values.to_vec(); + appended.push(element); + return Ok(Value::VecI32(appended)); + } + if runtime_symbol == "__glagol_vec_i32_len" { + let Some(values) = args.first() else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.vec.i32.len` calls", + )); + }; + let values = eval_expr(file, values, locals, functions, foreign_imports, depth)?; + let Some(values) = values.as_vec_i32() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.vec.i32.len` on non-vector values", + )); + }; + let len = i32::try_from(values.len()).map_err(|_| { + Diagnostic::new(file, "TestRuntimeError", "vector length exceeded i32") + .with_span(expr.span) + })?; + return Ok(Value::I32(len)); + } + if runtime_symbol == "__glagol_vec_i32_index" { + let Some(values) = args.first() else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.vec.i32.index` calls", + )); + }; + let Some(index) = args.get(1) else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.vec.i32.index` calls", + )); + }; + let values = eval_expr(file, values, locals, functions, foreign_imports, depth)?; + let index = eval_expr(file, index, locals, functions, foreign_imports, depth)?; + let Some(values) = values.as_vec_i32() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.vec.i32.index` on non-vector values", + )); + }; + let Some(index) = index.as_i32() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.vec.i32.index` with non-i32 index", + )); + }; + let Ok(index) = usize::try_from(index) else { + return Err(runtime_trap( + file, + expr, + "slovo runtime error: vector index out of bounds", + )); + }; + return values.get(index).copied().map(Value::I32).ok_or_else(|| { + runtime_trap( + file, + expr, + "slovo runtime error: vector index out of bounds", + ) + }); + } + if runtime_symbol == "__glagol_vec_i64_empty" { + return Ok(Value::VecI64(Vec::new())); + } + if runtime_symbol == "__glagol_vec_i64_append" { + let Some(values) = args.first() else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.vec.i64.append` calls", + )); + }; + let Some(element) = args.get(1) else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.vec.i64.append` calls", + )); + }; + let values = eval_expr(file, values, locals, functions, foreign_imports, depth)?; + let element = eval_expr(file, element, locals, functions, foreign_imports, depth)?; + let Some(values) = values.as_vec_i64() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.vec.i64.append` on non-vector values", + )); + }; + let Some(element) = element.as_i64() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.vec.i64.append` with non-i64 elements", + )); + }; + let mut appended = values.to_vec(); + appended.push(element); + return Ok(Value::VecI64(appended)); + } + if runtime_symbol == "__glagol_vec_i64_len" { + let Some(values) = args.first() else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.vec.i64.len` calls", + )); + }; + let values = eval_expr(file, values, locals, functions, foreign_imports, depth)?; + let Some(values) = values.as_vec_i64() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.vec.i64.len` on non-vector values", + )); + }; + let len = i32::try_from(values.len()).map_err(|_| { + Diagnostic::new(file, "TestRuntimeError", "vector length exceeded i32") + .with_span(expr.span) + })?; + return Ok(Value::I32(len)); + } + if runtime_symbol == "__glagol_vec_i64_index" { + let Some(values) = args.first() else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.vec.i64.index` calls", + )); + }; + let Some(index) = args.get(1) else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.vec.i64.index` calls", + )); + }; + let values = eval_expr(file, values, locals, functions, foreign_imports, depth)?; + let index = eval_expr(file, index, locals, functions, foreign_imports, depth)?; + let Some(values) = values.as_vec_i64() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.vec.i64.index` on non-vector values", + )); + }; + let Some(index) = index.as_i32() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.vec.i64.index` with non-i32 index", + )); + }; + let Ok(index) = usize::try_from(index) else { + return Err(runtime_trap( + file, + expr, + "slovo runtime error: vector index out of bounds", + )); + }; + return values.get(index).copied().map(Value::I64).ok_or_else(|| { + runtime_trap( + file, + expr, + "slovo runtime error: vector index out of bounds", + ) + }); + } + if runtime_symbol == "__glagol_vec_f64_empty" { + return Ok(Value::VecF64(Vec::new())); + } + if runtime_symbol == "__glagol_vec_f64_append" { + let Some(values) = args.first() else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.vec.f64.append` calls", + )); + }; + let Some(element) = args.get(1) else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.vec.f64.append` calls", + )); + }; + let values = eval_expr(file, values, locals, functions, foreign_imports, depth)?; + let element = eval_expr(file, element, locals, functions, foreign_imports, depth)?; + let Some(values) = values.as_vec_f64() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.vec.f64.append` on non-vector values", + )); + }; + let Some(element) = element.as_f64() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.vec.f64.append` with non-f64 elements", + )); + }; + let mut appended = values.to_vec(); + appended.push(element); + return Ok(Value::VecF64(appended)); + } + if runtime_symbol == "__glagol_vec_f64_len" { + let Some(values) = args.first() else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.vec.f64.len` calls", + )); + }; + let values = eval_expr(file, values, locals, functions, foreign_imports, depth)?; + let Some(values) = values.as_vec_f64() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.vec.f64.len` on non-vector values", + )); + }; + let len = i32::try_from(values.len()).map_err(|_| { + Diagnostic::new(file, "TestRuntimeError", "vector length exceeded i32") + .with_span(expr.span) + })?; + return Ok(Value::I32(len)); + } + if runtime_symbol == "__glagol_vec_f64_index" { + let Some(values) = args.first() else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.vec.f64.index` calls", + )); + }; + let Some(index) = args.get(1) else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.vec.f64.index` calls", + )); + }; + let values = eval_expr(file, values, locals, functions, foreign_imports, depth)?; + let index = eval_expr(file, index, locals, functions, foreign_imports, depth)?; + let Some(values) = values.as_vec_f64() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.vec.f64.index` on non-vector values", + )); + }; + let Some(index) = index.as_i32() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.vec.f64.index` with non-i32 index", + )); + }; + let Ok(index) = usize::try_from(index) else { + return Err(runtime_trap( + file, + expr, + "slovo runtime error: vector index out of bounds", + )); + }; + return values.get(index).copied().map(Value::F64).ok_or_else(|| { + runtime_trap( + file, + expr, + "slovo runtime error: vector index out of bounds", + ) + }); + } + if runtime_symbol == "__glagol_vec_bool_empty" { + return Ok(Value::VecBool(Vec::new())); + } + if runtime_symbol == "__glagol_vec_bool_append" { + let Some(values) = args.first() else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.vec.bool.append` calls", + )); + }; + let Some(element) = args.get(1) else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.vec.bool.append` calls", + )); + }; + let values = eval_expr(file, values, locals, functions, foreign_imports, depth)?; + let element = eval_expr(file, element, locals, functions, foreign_imports, depth)?; + let Some(values) = values.as_vec_bool() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.vec.bool.append` on non-vector values", + )); + }; + let Some(element) = element.as_bool() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.vec.bool.append` with non-bool elements", + )); + }; + let mut appended = values.to_vec(); + appended.push(element); + return Ok(Value::VecBool(appended)); + } + if runtime_symbol == "__glagol_vec_bool_len" { + let Some(values) = args.first() else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.vec.bool.len` calls", + )); + }; + let values = eval_expr(file, values, locals, functions, foreign_imports, depth)?; + let Some(values) = values.as_vec_bool() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.vec.bool.len` on non-vector values", + )); + }; + let len = i32::try_from(values.len()).map_err(|_| { + Diagnostic::new(file, "TestRuntimeError", "vector length exceeded i32") + .with_span(expr.span) + })?; + return Ok(Value::I32(len)); + } + if runtime_symbol == "__glagol_vec_bool_index" { + let Some(values) = args.first() else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.vec.bool.index` calls", + )); + }; + let Some(index) = args.get(1) else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.vec.bool.index` calls", + )); + }; + let values = eval_expr(file, values, locals, functions, foreign_imports, depth)?; + let index = eval_expr(file, index, locals, functions, foreign_imports, depth)?; + let Some(values) = values.as_vec_bool() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.vec.bool.index` on non-vector values", + )); + }; + let Some(index) = index.as_i32() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.vec.bool.index` with non-i32 index", + )); + }; + let Ok(index) = usize::try_from(index) else { + return Err(runtime_trap( + file, + expr, + "slovo runtime error: vector index out of bounds", + )); + }; + return values.get(index).copied().map(Value::Bool).ok_or_else(|| { + runtime_trap( + file, + expr, + "slovo runtime error: vector index out of bounds", + ) + }); + } + if runtime_symbol == "__glagol_vec_string_empty" { + return Ok(Value::VecString(Vec::new())); + } + if runtime_symbol == "__glagol_vec_string_append" { + let Some(values) = args.first() else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.vec.string.append` calls", + )); + }; + let Some(element) = args.get(1) else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.vec.string.append` calls", + )); + }; + let values = eval_expr(file, values, locals, functions, foreign_imports, depth)?; + let element = eval_expr(file, element, locals, functions, foreign_imports, depth)?; + let Some(values) = values.as_vec_string() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.vec.string.append` on non-vector values", + )); + }; + let Some(element) = element.as_string() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.vec.string.append` with non-string elements", + )); + }; + let mut appended = values.to_vec(); + appended.push(element.to_string()); + return Ok(Value::VecString(appended)); + } + if runtime_symbol == "__glagol_vec_string_len" { + let Some(values) = args.first() else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.vec.string.len` calls", + )); + }; + let values = eval_expr(file, values, locals, functions, foreign_imports, depth)?; + let Some(values) = values.as_vec_string() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.vec.string.len` on non-vector values", + )); + }; + let len = i32::try_from(values.len()).map_err(|_| { + Diagnostic::new(file, "TestRuntimeError", "vector length exceeded i32") + .with_span(expr.span) + })?; + return Ok(Value::I32(len)); + } + if runtime_symbol == "__glagol_vec_string_index" { + let Some(values) = args.first() else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.vec.string.index` calls", + )); + }; + let Some(index) = args.get(1) else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.vec.string.index` calls", + )); + }; + let values = eval_expr(file, values, locals, functions, foreign_imports, depth)?; + let index = eval_expr(file, index, locals, functions, foreign_imports, depth)?; + let Some(values) = values.as_vec_string() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.vec.string.index` on non-vector values", + )); + }; + let Some(index) = index.as_i32() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.vec.string.index` with non-i32 index", + )); + }; + let Ok(index) = usize::try_from(index) else { + return Err(runtime_trap( + file, + expr, + "slovo runtime error: vector index out of bounds", + )); + }; + return values + .get(index) + .cloned() + .map(Value::String) + .ok_or_else(|| { + runtime_trap( + file, + expr, + "slovo runtime error: vector index out of bounds", + ) + }); + } + if runtime_symbol == "__glagol_time_monotonic_ms" { + let start = MONOTONIC_START.get_or_init(Instant::now); + let elapsed_ms = start.elapsed().as_millis(); + let value = i32::try_from(elapsed_ms).unwrap_or(i32::MAX); + return Ok(Value::I32(value)); + } + if runtime_symbol == "__glagol_time_sleep_ms" { + let Some(ms) = args.first() else { + return Err(unsupported_test_expr( + file, + expr, + "malformed `std.time.sleep_ms` calls", + )); + }; + let ms = eval_expr(file, ms, locals, functions, foreign_imports, depth)?; + let Some(ms) = ms.as_i32() else { + return Err(unsupported_test_expr( + file, + expr, + "`std.time.sleep_ms` with non-i32 duration", + )); + }; + if ms < 0 { + return Err(runtime_trap( + file, + expr, + "slovo runtime error: sleep_ms negative duration", + )); + } + if ms != 0 { + return Err(unsupported_test_expr( + file, + expr, + "positive `std.time.sleep_ms` calls while running tests", + )); + } + return Ok(Value::Unit); + } + if runtime_symbol == "__glagol_random_i32" { + return Ok(Value::I32(0)); + } + + if depth >= MAX_TEST_CALL_DEPTH { + return Err(Diagnostic::new( + file, + "TestRuntimeError", + "test runner exceeded maximum call depth", + ) + .with_span(expr.span) + .hint("check for unbounded recursion in the test expression")); + } + + if foreign_imports.contains(name.as_str()) { + return Err(Diagnostic::new( + file, + "UnsupportedTestExpression", + format!("test runner cannot execute C import `{}`", name), + ) + .with_span(expr.span) + .hint("use `glagol build --link-c ` for hosted C FFI smoke tests")); + } + + let function = functions.get(name.as_str()).ok_or_else(|| { + Diagnostic::new( + file, + "TestRuntimeError", + format!("unknown test function `{}`", name), + ) + .with_span(expr.span) + })?; + let mut call_locals = HashMap::new(); + + for ((param_name, _), arg) in function.params.iter().zip(args) { + let value = eval_expr(file, arg, locals, functions, foreign_imports, depth)?; + call_locals.insert(param_name.clone(), value); + } + + eval_function_body( + function.file.as_str(), + function, + &mut call_locals, + functions, + foreign_imports, + depth + 1, + ) + } + } +} + +fn eval_match( + file: &str, + expr: &TExpr, + subject: Value, + arms: &[TMatchArm], + locals: &mut HashMap, + functions: &HashMap<&str, &CheckedFunction>, + foreign_imports: &HashSet<&str>, + depth: usize, +) -> Result { + let (pattern, payload) = match subject { + Value::OptionI32 { is_some, payload } => { + if is_some { + (MatchPatternKind::Some, Some(Value::I32(payload))) + } else { + (MatchPatternKind::None, None) + } + } + Value::OptionI64 { is_some, payload } => { + if is_some { + (MatchPatternKind::Some, Some(Value::I64(payload))) + } else { + (MatchPatternKind::None, None) + } + } + Value::OptionU32 { is_some, payload } => { + if is_some { + (MatchPatternKind::Some, Some(Value::U32(payload))) + } else { + (MatchPatternKind::None, None) + } + } + Value::OptionU64 { is_some, payload } => { + if is_some { + (MatchPatternKind::Some, Some(Value::U64(payload))) + } else { + (MatchPatternKind::None, None) + } + } + Value::OptionF64 { is_some, payload } => { + if is_some { + (MatchPatternKind::Some, Some(Value::F64(payload))) + } else { + (MatchPatternKind::None, None) + } + } + Value::OptionBool { is_some, payload } => { + if is_some { + (MatchPatternKind::Some, Some(Value::Bool(payload))) + } else { + (MatchPatternKind::None, None) + } + } + Value::OptionString { is_some, payload } => { + if is_some { + (MatchPatternKind::Some, Some(Value::String(payload))) + } else { + (MatchPatternKind::None, None) + } + } + Value::ResultI32 { is_ok, payload } => { + if is_ok { + (MatchPatternKind::Ok, Some(Value::I32(payload))) + } else { + (MatchPatternKind::Err, Some(Value::I32(payload))) + } + } + Value::ResultI64I32 { + is_ok, + ok_payload, + err_payload, + } => { + if is_ok { + (MatchPatternKind::Ok, Some(Value::I64(ok_payload))) + } else { + (MatchPatternKind::Err, Some(Value::I32(err_payload))) + } + } + Value::ResultU32I32 { is_ok, payload } => { + if is_ok { + (MatchPatternKind::Ok, Some(Value::U32(payload))) + } else { + (MatchPatternKind::Err, Some(Value::I32(payload as i32))) + } + } + Value::ResultU64I32 { + is_ok, + ok_payload, + err_payload, + } => { + if is_ok { + (MatchPatternKind::Ok, Some(Value::U64(ok_payload))) + } else { + (MatchPatternKind::Err, Some(Value::I32(err_payload))) + } + } + Value::ResultF64I32 { + is_ok, + ok_payload, + err_payload, + } => { + if is_ok { + (MatchPatternKind::Ok, Some(Value::F64(ok_payload))) + } else { + (MatchPatternKind::Err, Some(Value::I32(err_payload))) + } + } + Value::ResultBoolI32 { + is_ok, + ok_payload, + err_payload, + } => { + if is_ok { + (MatchPatternKind::Ok, Some(Value::Bool(ok_payload))) + } else { + (MatchPatternKind::Err, Some(Value::I32(err_payload))) + } + } + Value::ResultStringI32 { + is_ok, + ok_payload, + err_payload, + } => { + if is_ok { + (MatchPatternKind::Ok, Some(Value::String(ok_payload))) + } else { + (MatchPatternKind::Err, Some(Value::I32(err_payload))) + } + } + Value::Enum { + name, + variant, + payload, + .. + } => ( + MatchPatternKind::EnumVariant { + enum_name: name, + variant, + }, + payload.map(EnumPayloadValue::into_value), + ), + other => { + return Err(unsupported_test_expr( + file, + expr, + &format!("matching values of type `{}`", other.ty()), + )); + } + }; + + let Some(arm) = arms.iter().find(|arm| arm.pattern == pattern) else { + return Err(Diagnostic::new( + file, + "TestRuntimeError", + "checked match did not contain the selected arm", + ) + .with_span(expr.span)); + }; + + let outer_names = locals.keys().cloned().collect::>(); + if let (Some(binding), Some(payload)) = (&arm.binding, payload) { + locals.insert(binding.clone(), payload); + } + + let result = eval_body(file, &arm.body, locals, functions, foreign_imports, depth); + locals.retain(|name, _| outer_names.contains(name)); + result +} + +fn eval_body( + file: &str, + body: &[TExpr], + locals: &mut HashMap, + functions: &HashMap<&str, &CheckedFunction>, + foreign_imports: &HashSet<&str>, + depth: usize, +) -> Result { + let mut value = Value::Unit; + + for expr in body { + value = eval_expr(file, expr, locals, functions, foreign_imports, depth)?; + } + + Ok(value) +} + +fn eval_function_body( + file: &str, + function: &CheckedFunction, + locals: &mut HashMap, + functions: &HashMap<&str, &CheckedFunction>, + foreign_imports: &HashSet<&str>, + depth: usize, +) -> Result { + eval_body( + file, + &function.body, + locals, + functions, + foreign_imports, + depth, + ) +} + +fn eval_binary( + file: &str, + expr: &TExpr, + op: BinaryOp, + left: Value, + right: Value, +) -> Result { + if let (Some(left), Some(right)) = (left.as_f64(), right.as_f64()) { + return match op { + BinaryOp::Add => Ok(Value::F64(left + right)), + BinaryOp::Sub => Ok(Value::F64(left - right)), + BinaryOp::Mul => Ok(Value::F64(left * right)), + BinaryOp::Div => Ok(Value::F64(left / right)), + BinaryOp::Rem => Err(unsupported_test_expr( + file, + expr, + "f64 remainder is not supported", + )), + BinaryOp::BitAnd | BinaryOp::BitOr | BinaryOp::BitXor => Err(unsupported_test_expr( + file, + expr, + "f64 bitwise operations are not supported", + )), + BinaryOp::Eq => Ok(Value::Bool(left == right)), + BinaryOp::Lt => Ok(Value::Bool(left < right)), + BinaryOp::Gt => Ok(Value::Bool(left > right)), + BinaryOp::Le => Ok(Value::Bool(left <= right)), + BinaryOp::Ge => Ok(Value::Bool(left >= right)), + }; + } + + if left.as_f64().is_some() || right.as_f64().is_some() { + return Err(unsupported_test_expr( + file, + expr, + "mixed i32/i64/u32/u64/f64 binary operands", + )); + } + + if let (Some(left), Some(right)) = (left.as_u64(), right.as_u64()) { + return match op { + BinaryOp::Add => checked_u64(file, expr, left.checked_add(right), "addition"), + BinaryOp::Sub => checked_u64(file, expr, left.checked_sub(right), "subtraction"), + BinaryOp::Mul => checked_u64(file, expr, left.checked_mul(right), "multiplication"), + BinaryOp::Div => { + if right == 0 { + return Err(Diagnostic::new( + file, + "TestRuntimeError", + "division by zero in test", + ) + .with_span(expr.span)); + } + checked_u64(file, expr, left.checked_div(right), "division") + } + BinaryOp::Rem => { + if right == 0 { + return Err(Diagnostic::new( + file, + "TestRuntimeError", + "remainder by zero in test", + ) + .with_span(expr.span)); + } + checked_u64(file, expr, left.checked_rem(right), "remainder") + } + BinaryOp::BitAnd => Ok(Value::U64(left & right)), + BinaryOp::BitOr => Ok(Value::U64(left | right)), + BinaryOp::BitXor => Ok(Value::U64(left ^ right)), + BinaryOp::Eq => Ok(Value::Bool(left == right)), + BinaryOp::Lt => Ok(Value::Bool(left < right)), + BinaryOp::Gt => Ok(Value::Bool(left > right)), + BinaryOp::Le => Ok(Value::Bool(left <= right)), + BinaryOp::Ge => Ok(Value::Bool(left >= right)), + }; + } + + if left.as_u64().is_some() || right.as_u64().is_some() { + return Err(unsupported_test_expr( + file, + expr, + "mixed i32/i64/u32/u64/f64 binary operands", + )); + } + + if let (Some(left), Some(right)) = (left.as_i64(), right.as_i64()) { + return match op { + BinaryOp::Add => checked_i64(file, expr, left.checked_add(right), "addition"), + BinaryOp::Sub => checked_i64(file, expr, left.checked_sub(right), "subtraction"), + BinaryOp::Mul => checked_i64(file, expr, left.checked_mul(right), "multiplication"), + BinaryOp::Div => { + if right == 0 { + return Err(Diagnostic::new( + file, + "TestRuntimeError", + "division by zero in test", + ) + .with_span(expr.span)); + } + checked_i64(file, expr, left.checked_div(right), "division") + } + BinaryOp::Rem => { + if right == 0 { + return Err(Diagnostic::new( + file, + "TestRuntimeError", + "remainder by zero in test", + ) + .with_span(expr.span)); + } + checked_i64(file, expr, left.checked_rem(right), "remainder") + } + BinaryOp::BitAnd => Ok(Value::I64(left & right)), + BinaryOp::BitOr => Ok(Value::I64(left | right)), + BinaryOp::BitXor => Ok(Value::I64(left ^ right)), + BinaryOp::Eq => Ok(Value::Bool(left == right)), + BinaryOp::Lt => Ok(Value::Bool(left < right)), + BinaryOp::Gt => Ok(Value::Bool(left > right)), + BinaryOp::Le => Ok(Value::Bool(left <= right)), + BinaryOp::Ge => Ok(Value::Bool(left >= right)), + }; + } + + if left.as_i64().is_some() || right.as_i64().is_some() { + return Err(unsupported_test_expr( + file, + expr, + "mixed i32/i64/u32/u64/f64 binary operands", + )); + } + + if let (Some(left), Some(right)) = (left.as_u32(), right.as_u32()) { + return match op { + BinaryOp::Add => checked_u32(file, expr, left.checked_add(right), "addition"), + BinaryOp::Sub => checked_u32(file, expr, left.checked_sub(right), "subtraction"), + BinaryOp::Mul => checked_u32(file, expr, left.checked_mul(right), "multiplication"), + BinaryOp::Div => { + if right == 0 { + return Err(Diagnostic::new( + file, + "TestRuntimeError", + "division by zero in test", + ) + .with_span(expr.span)); + } + checked_u32(file, expr, left.checked_div(right), "division") + } + BinaryOp::Rem => { + if right == 0 { + return Err(Diagnostic::new( + file, + "TestRuntimeError", + "remainder by zero in test", + ) + .with_span(expr.span)); + } + checked_u32(file, expr, left.checked_rem(right), "remainder") + } + BinaryOp::BitAnd => Ok(Value::U32(left & right)), + BinaryOp::BitOr => Ok(Value::U32(left | right)), + BinaryOp::BitXor => Ok(Value::U32(left ^ right)), + BinaryOp::Eq => Ok(Value::Bool(left == right)), + BinaryOp::Lt => Ok(Value::Bool(left < right)), + BinaryOp::Gt => Ok(Value::Bool(left > right)), + BinaryOp::Le => Ok(Value::Bool(left <= right)), + BinaryOp::Ge => Ok(Value::Bool(left >= right)), + }; + } + + if left.as_u32().is_some() || right.as_u32().is_some() { + return Err(unsupported_test_expr( + file, + expr, + "mixed i32/i64/u32/u64/f64 binary operands", + )); + } + + if op == BinaryOp::Eq { + if let (Some(left), Some(right)) = (left.as_bool(), right.as_bool()) { + return Ok(Value::Bool(left == right)); + } + if let (Some(left), Some(right)) = (left.as_string(), right.as_string()) { + return Ok(Value::Bool(left == right)); + } + if let (Some(left), Some(right)) = (left.as_vec_i32(), right.as_vec_i32()) { + return Ok(Value::Bool(left == right)); + } + if let (Some(left), Some(right)) = (left.as_vec_i64(), right.as_vec_i64()) { + return Ok(Value::Bool(left == right)); + } + if let (Some(left), Some(right)) = (left.as_vec_f64(), right.as_vec_f64()) { + return Ok(Value::Bool(left == right)); + } + if let (Some(left), Some(right)) = (left.as_vec_bool(), right.as_vec_bool()) { + return Ok(Value::Bool(left == right)); + } + if let (Some(left), Some(right)) = (left.as_vec_string(), right.as_vec_string()) { + return Ok(Value::Bool(left == right)); + } + if let ( + Value::Enum { + name: left_name, + discriminant: left_discriminant, + payload: left_payload, + .. + }, + Value::Enum { + name: right_name, + discriminant: right_discriminant, + payload: right_payload, + .. + }, + ) = (&left, &right) + { + return Ok(Value::Bool( + left_name == right_name + && left_discriminant == right_discriminant + && left_payload == right_payload, + )); + } + } + + let Some(left) = left.as_i32() else { + return Err(unsupported_test_expr(file, expr, "non-i32 binary operands")); + }; + let Some(right) = right.as_i32() else { + return Err(unsupported_test_expr(file, expr, "non-i32 binary operands")); + }; + + match op { + BinaryOp::Add => checked_i32(file, expr, left.checked_add(right), "addition"), + BinaryOp::Sub => checked_i32(file, expr, left.checked_sub(right), "subtraction"), + BinaryOp::Mul => checked_i32(file, expr, left.checked_mul(right), "multiplication"), + BinaryOp::Div => { + if right == 0 { + return Err( + Diagnostic::new(file, "TestRuntimeError", "division by zero in test") + .with_span(expr.span), + ); + } + checked_i32(file, expr, left.checked_div(right), "division") + } + BinaryOp::Rem => { + if right == 0 { + return Err( + Diagnostic::new(file, "TestRuntimeError", "remainder by zero in test") + .with_span(expr.span), + ); + } + checked_i32(file, expr, left.checked_rem(right), "remainder") + } + BinaryOp::BitAnd => Ok(Value::I32(left & right)), + BinaryOp::BitOr => Ok(Value::I32(left | right)), + BinaryOp::BitXor => Ok(Value::I32(left ^ right)), + BinaryOp::Eq => Ok(Value::Bool(left == right)), + BinaryOp::Lt => Ok(Value::Bool(left < right)), + BinaryOp::Gt => Ok(Value::Bool(left > right)), + BinaryOp::Le => Ok(Value::Bool(left <= right)), + BinaryOp::Ge => Ok(Value::Bool(left >= right)), + } +} + +fn runtime_trap(file: &str, expr: &TExpr, message: &str) -> Diagnostic { + Diagnostic::new( + file, + "TestRuntimeTrap", + format!("test trapped: {}", message), + ) + .with_span(expr.span) +} + +fn checked_i32( + file: &str, + expr: &TExpr, + value: Option, + operation: &'static str, +) -> Result { + value.map(Value::I32).ok_or_else(|| { + Diagnostic::new( + file, + "TestRuntimeError", + format!("integer overflow during test {}", operation), + ) + .with_span(expr.span) + }) +} + +fn checked_i64( + file: &str, + expr: &TExpr, + value: Option, + operation: &'static str, +) -> Result { + value.map(Value::I64).ok_or_else(|| { + Diagnostic::new( + file, + "TestRuntimeError", + format!("integer overflow during test {}", operation), + ) + .with_span(expr.span) + }) +} + +fn checked_u32( + file: &str, + expr: &TExpr, + value: Option, + operation: &'static str, +) -> Result { + value.map(Value::U32).ok_or_else(|| { + Diagnostic::new( + file, + "TestRuntimeError", + format!("integer overflow during test {}", operation), + ) + .with_span(expr.span) + }) +} + +fn checked_u64( + file: &str, + expr: &TExpr, + value: Option, + operation: &'static str, +) -> Result { + value.map(Value::U64).ok_or_else(|| { + Diagnostic::new( + file, + "TestRuntimeError", + format!("integer overflow during test {}", operation), + ) + .with_span(expr.span) + }) +} + +fn unsupported_test_expr(file: &str, expr: &TExpr, feature: &str) -> Diagnostic { + Diagnostic::new( + file, + "UnsupportedTestExpression", + format!("test runner does not support {}", feature), + ) + .with_span(expr.span) +} + +fn write_test_name(name: &str, output: &mut String) { + output.push('"'); + for ch in name.chars() { + output.extend(ch.escape_default()); + } + output.push('"'); +} diff --git a/compiler/src/token.rs b/compiler/src/token.rs new file mode 100644 index 0000000..c4d2a77 --- /dev/null +++ b/compiler/src/token.rs @@ -0,0 +1,31 @@ +#[derive(Debug, Clone, PartialEq)] +pub enum TokenKind { + LParen, + RParen, + Arrow, + Ident(String), + Int(i64), + I64(i64), + U32(u32), + U64(u64), + Float(f64), + String(String), +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Token { + pub kind: TokenKind, + pub span: Span, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct Span { + pub start: usize, + pub end: usize, +} + +impl Span { + pub fn new(start: usize, end: usize) -> Self { + Self { start, end } + } +} diff --git a/compiler/src/types.rs b/compiler/src/types.rs new file mode 100644 index 0000000..adbc58c --- /dev/null +++ b/compiler/src/types.rs @@ -0,0 +1,82 @@ +use std::fmt; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Type { + I32, + I64, + U32, + U64, + F64, + Bool, + Unit, + String, + Named(String), + Ptr(Box), + Array(Box, usize), + Vec(Box), + Slice(Box), + Option(Box), + Result(Box, Box), +} + +impl Type { + pub fn llvm(&self) -> &'static str { + match self { + Type::I32 => "i32", + Type::I64 => "i64", + Type::U32 => "i32", + Type::U64 => "i64", + Type::F64 => "double", + Type::Bool => "i1", + Type::Unit => "void", + Type::String => "ptr", + Type::Option(inner) if **inner == Type::I32 => "{ i1, i32 }", + Type::Option(inner) if **inner == Type::I64 => "{ i1, i64 }", + Type::Option(inner) if **inner == Type::U32 => "{ i1, i32 }", + Type::Option(inner) if **inner == Type::U64 => "{ i1, i64 }", + Type::Option(inner) if **inner == Type::F64 => "{ i1, double }", + Type::Option(inner) if **inner == Type::Bool => "{ i1, i1 }", + Type::Option(inner) if **inner == Type::String => "{ i1, ptr }", + Type::Result(ok, err) if **ok == Type::I32 && **err == Type::I32 => "{ i1, i32 }", + // exp-25 keeps `(result i64 i32)` as a single concrete family for + // `std.string.parse_i64_result`; this is not a generic result ABI. + Type::Result(ok, err) if **ok == Type::I64 && **err == Type::I32 => "{ i1, i64, i32 }", + Type::Result(ok, err) if **ok == Type::U32 && **err == Type::I32 => "{ i1, i32 }", + Type::Result(ok, err) if **ok == Type::U64 && **err == Type::I32 => "{ i1, i64, i32 }", + // exp-28 keeps `(result f64 i32)` as a single concrete family for + // `std.string.parse_f64_result`; this is not a generic result ABI. + Type::Result(ok, err) if **ok == Type::F64 && **err == Type::I32 => { + "{ i1, double, i32 }" + } + // exp-34 keeps `(result bool i32)` as a single concrete family for + // `std.string.parse_bool_result`; this is not a generic result ABI. + Type::Result(ok, err) if **ok == Type::Bool && **err == Type::I32 => "{ i1, i1, i32 }", + Type::Result(ok, err) if **ok == Type::String && **err == Type::I32 => { + "{ i1, ptr, i32 }" + } + _ => "ptr", + } + } +} + +impl fmt::Display for Type { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Type::I32 => write!(f, "i32"), + Type::I64 => write!(f, "i64"), + Type::U32 => write!(f, "u32"), + Type::U64 => write!(f, "u64"), + Type::F64 => write!(f, "f64"), + Type::Bool => write!(f, "bool"), + Type::Unit => write!(f, "unit"), + Type::String => write!(f, "string"), + Type::Named(name) => write!(f, "{}", name), + Type::Ptr(inner) => write!(f, "(ptr {})", inner), + Type::Array(inner, n) => write!(f, "(array {} {})", inner, n), + Type::Vec(inner) => write!(f, "(vec {})", inner), + Type::Slice(inner) => write!(f, "(slice {})", inner), + Type::Option(inner) => write!(f, "(option {})", inner), + Type::Result(ok, err) => write!(f, "(result {} {})", ok, err), + } + } +} diff --git a/compiler/src/unsafe_ops.rs b/compiler/src/unsafe_ops.rs new file mode 100644 index 0000000..28a8514 --- /dev/null +++ b/compiler/src/unsafe_ops.rs @@ -0,0 +1,41 @@ +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct UnsafeOperation { + pub source_name: &'static str, +} + +pub const HEADS: &[UnsafeOperation] = &[ + UnsafeOperation { + source_name: "alloc", + }, + UnsafeOperation { + source_name: "dealloc", + }, + UnsafeOperation { + source_name: "load", + }, + UnsafeOperation { + source_name: "store", + }, + UnsafeOperation { + source_name: "ptr_add", + }, + UnsafeOperation { + source_name: "unchecked_index", + }, + UnsafeOperation { + source_name: "reinterpret", + }, + UnsafeOperation { + source_name: "ffi_call", + }, +]; + +pub fn operation(source_name: &str) -> Option<&'static UnsafeOperation> { + HEADS + .iter() + .find(|operation| operation.source_name == source_name) +} + +pub fn is_reserved_head(source_name: &str) -> bool { + operation(source_name).is_some() +} diff --git a/compiler/tests/add_fixture.rs b/compiler/tests/add_fixture.rs new file mode 100644 index 0000000..8751dd4 --- /dev/null +++ b/compiler/tests/add_fixture.rs @@ -0,0 +1,40 @@ +use std::{fs, path::Path, process::Command}; + +#[test] +fn add_fixture_emits_expected_llvm_shape() { + let compiler = env!("CARGO_BIN_EXE_glagol"); + let fixture = Path::new("../examples/add.slo"); + let expected = fs::read_to_string("../tests/add.expected.ll").expect("read add.expected.ll"); + + let output = Command::new(compiler) + .arg(fixture) + .output() + .expect("run glagol on add fixture"); + + assert!( + output.status.success(), + "compiler failed\nstdout:\n{}\nstderr:\n{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr), + ); + + let actual = String::from_utf8(output.stdout).expect("LLVM output is UTF-8"); + + for required in required_shapes(&expected) { + assert!( + actual.contains(required), + "LLVM output did not contain expected shape `{}`\nactual:\n{}", + required, + actual, + ); + } +} + +fn required_shapes(expected: &str) -> Vec<&str> { + expected + .lines() + .map(str::trim) + .filter(|line| !line.is_empty()) + .filter(|line| !line.starts_with(';')) + .collect() +} diff --git a/compiler/tests/array_direct_scalars_alpha.rs b/compiler/tests/array_direct_scalars_alpha.rs new file mode 100644 index 0000000..9b1d38e --- /dev/null +++ b/compiler/tests/array_direct_scalars_alpha.rs @@ -0,0 +1,306 @@ +use std::{ + fs, + path::Path, + process::{Command, Output}, +}; + +#[test] +fn direct_scalar_array_fixture_emits_direct_scalar_llvm_shapes() { + let output = run_glagol(["../examples/array-direct-scalars.slo"]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "compiler rejected direct-scalar array fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define i32 @i32_second()") + && stdout.contains("define i64 @i64_local_pick()") + && stdout.contains("define double @f64_third()") + && stdout.contains("define i1 @bool_local_pick()") + && stdout.contains("insertvalue [3 x i32]") + && stdout.contains("insertvalue [3 x double]") + && stdout.contains("alloca [3 x i64]") + && stdout.contains("alloca [3 x i1]"), + "LLVM output did not contain the widened direct-scalar array shapes\nstdout:\n{}", + stdout + ); + assert!( + !stdout.contains("i32 direct scalar array index") + && !stdout.contains("bool local direct scalar array index"), + "compiler emitted test metadata into LLVM output\nstdout:\n{}", + stdout + ); + assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr); +} + +#[test] +fn direct_scalar_array_fixture_runs_tests_and_formats_stably() { + let run = run_glagol(["--run-tests", "../examples/array-direct-scalars.slo"]); + assert_success_stdout( + run, + concat!( + "test \"i32 direct scalar array index\" ... ok\n", + "test \"i64 local direct scalar array index\" ... ok\n", + "test \"f64 direct scalar array index\" ... ok\n", + "test \"bool local direct scalar array index\" ... ok\n", + "4 test(s) passed\n", + ), + "direct-scalar array test runner output", + ); + + let expected = fs::read_to_string("../tests/array-direct-scalars.slo").expect("read fixture"); + let format = run_glagol(["--format", "../tests/array-direct-scalars.slo"]); + assert_success_stdout(format, &expected, "direct-scalar array formatter output"); +} + +#[test] +fn direct_scalar_array_fixture_prints_lowered_shape() { + let surface = run_glagol([ + "--inspect-lowering=surface", + "../examples/array-direct-scalars.slo", + ]); + let surface_stdout = String::from_utf8_lossy(&surface.stdout); + let surface_stderr = String::from_utf8_lossy(&surface.stderr); + assert!( + surface.status.success(), + "surface lowering rejected direct-scalar array fixture\nstdout:\n{}\nstderr:\n{}", + surface_stdout, + surface_stderr + ); + assert!( + surface_stdout.contains("array i64") + && surface_stdout.contains("array f64") + && surface_stdout.contains("array bool") + && surface_stdout.contains("local let values: (array i64 3)") + && surface_stdout.contains("local let flags: (array bool 3)"), + "surface lowering output lost direct-scalar array shape\nstdout:\n{}", + surface_stdout + ); + assert!( + surface_stderr.is_empty(), + "surface lowering wrote stderr:\n{}", + surface_stderr + ); + + let checked = run_glagol([ + "--inspect-lowering=checked", + "../examples/array-direct-scalars.slo", + ]); + let checked_stdout = String::from_utf8_lossy(&checked.stdout); + let checked_stderr = String::from_utf8_lossy(&checked.stderr); + assert!( + checked.status.success(), + "checked lowering rejected direct-scalar array fixture\nstdout:\n{}\nstderr:\n{}", + checked_stdout, + checked_stderr + ); + assert!( + checked_stdout.contains("fn i32_second() -> i32") + && checked_stdout.contains("fn i64_local_pick() -> i64") + && checked_stdout.contains("fn f64_third() -> f64") + && checked_stdout.contains("fn bool_local_pick() -> bool") + && checked_stdout.contains("index : i32") + && checked_stdout.contains("index : i64") + && checked_stdout.contains("index : f64") + && checked_stdout.contains("index : bool"), + "checked lowering output lost typed direct-scalar array shape\nstdout:\n{}", + checked_stdout + ); + assert!( + checked_stderr.is_empty(), + "checked lowering wrote stderr:\n{}", + checked_stderr + ); +} + +#[test] +fn direct_scalar_array_value_flow_fixture_emits_llvm_value_flow_and_bounds_shape() { + let output = run_glagol(["../examples/array-direct-scalars-value-flow.slo"]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "compiler rejected direct-scalar array value-flow fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define [3 x i32] @make_i32_values(i32 %base)") + && stdout.contains("define [3 x i64] @make_i64_values(i64 %base)") + && stdout.contains("define [3 x double] @make_f64_values(double %base)") + && stdout.contains("define [3 x i1] @make_flags(i1 %first)") + && stdout.contains("define i32 @i32_at([3 x i32] %values, i32 %i)") + && stdout.contains("define i64 @i64_at([3 x i64] %values, i32 %i)") + && stdout.contains("define double @f64_at([3 x double] %values, i32 %i)") + && stdout.contains("define [3 x i1] @echo_flags([3 x i1] %values)") + && stdout.contains("call void @__glagol_array_bounds_trap()") + && stdout.contains("array.index.trap") + && stdout.contains("getelementptr inbounds [3 x i64]") + && stdout.contains("getelementptr inbounds [3 x double]"), + "LLVM output did not contain expected direct-scalar array value-flow shape\nstdout:\n{}", + stdout + ); + let dynamic_index_function = + llvm_function(&stdout, "define i64 @i64_at([3 x i64] %values, i32 %i)"); + assert_contains_in_order( + dynamic_index_function, + &[ + "icmp sge i32 %i, 0", + "icmp slt i32 %i, 3", + "array.index.trap", + "call void @__glagol_array_bounds_trap()", + "array.index.ok", + "getelementptr inbounds [3 x i64]", + "load i64, ptr %", + ], + "LLVM output did not emit bounds-check shape before direct-scalar array indexing", + ); + assert!( + !stdout.contains("i64 array local call value flow") + && !stdout.contains("bool array dynamic index"), + "compiler emitted test metadata into LLVM output\nstdout:\n{}", + stdout + ); + assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr); +} + +#[test] +fn direct_scalar_array_value_flow_fixture_runs_tests_and_formats_stably() { + let run = run_glagol([ + "--run-tests", + "../examples/array-direct-scalars-value-flow.slo", + ]); + assert_success_stdout( + run, + concat!( + "test \"i32 array parameter value flow\" ... ok\n", + "test \"i64 array local call value flow\" ... ok\n", + "test \"f64 array parameter local copy\" ... ok\n", + "test \"bool array dynamic index\" ... ok\n", + "test \"bool array return call value flow\" ... ok\n", + "5 test(s) passed\n", + ), + "direct-scalar array value-flow test runner output", + ); + + let expected = + fs::read_to_string("../tests/array-direct-scalars-value-flow.slo").expect("read fixture"); + let format = run_glagol(["--format", "../tests/array-direct-scalars-value-flow.slo"]); + assert_success_stdout( + format, + &expected, + "direct-scalar array value-flow formatter output", + ); +} + +#[test] +fn direct_scalar_array_value_flow_fixture_prints_lowered_shape() { + let surface = run_glagol([ + "--inspect-lowering=surface", + "../examples/array-direct-scalars-value-flow.slo", + ]); + let surface_stdout = String::from_utf8_lossy(&surface.stdout); + let surface_stderr = String::from_utf8_lossy(&surface.stderr); + assert!( + surface.status.success(), + "surface lowering rejected direct-scalar array value-flow fixture\nstdout:\n{}\nstderr:\n{}", + surface_stdout, + surface_stderr + ); + assert!( + surface_stdout.contains("fn make_i32_values(base: i32) -> (array i32 3)") + && surface_stdout.contains("fn make_i64_values(base: i64) -> (array i64 3)") + && surface_stdout.contains("fn make_f64_values(base: f64) -> (array f64 3)") + && surface_stdout.contains("fn make_flags(first: bool) -> (array bool 3)") + && surface_stdout.contains("fn echo_flags(values: (array bool 3)) -> (array bool 3)") + && surface_stdout.contains("local let copy: (array f64 3)"), + "surface lowering output lost direct-scalar array value-flow shape\nstdout:\n{}", + surface_stdout + ); + assert!( + surface_stderr.is_empty(), + "surface lowering wrote stderr:\n{}", + surface_stderr + ); + + let checked = run_glagol([ + "--inspect-lowering=checked", + "../examples/array-direct-scalars-value-flow.slo", + ]); + let checked_stdout = String::from_utf8_lossy(&checked.stdout); + let checked_stderr = String::from_utf8_lossy(&checked.stderr); + assert!( + checked.status.success(), + "checked lowering rejected direct-scalar array value-flow fixture\nstdout:\n{}\nstderr:\n{}", + checked_stdout, + checked_stderr + ); + assert!( + checked_stdout.contains("fn make_i32_values(base: i32) -> (array i32 3)") + && checked_stdout.contains("fn make_i64_values(base: i64) -> (array i64 3)") + && checked_stdout.contains("fn f64_at(values: (array f64 3), i: i32) -> f64") + && checked_stdout.contains("fn echo_flags(values: (array bool 3)) -> (array bool 3)") + && checked_stdout.contains("call make_i64_values : (array i64 3)") + && checked_stdout.contains("index : i64") + && checked_stdout.contains("index : f64") + && checked_stdout.contains("index : bool"), + "checked lowering output lost typed direct-scalar array value-flow shape\nstdout:\n{}", + checked_stdout + ); + assert!( + checked_stderr.is_empty(), + "checked lowering wrote stderr:\n{}", + checked_stderr + ); +} + +fn run_glagol(args: [&str; N]) -> Output { + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn assert_success_stdout(output: Output, expected: &str, context: &str) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert_eq!(stdout, expected, "{} drifted", context); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} + +fn llvm_function<'a>(source: &'a str, signature: &str) -> &'a str { + let start = source + .find(signature) + .unwrap_or_else(|| panic!("LLVM output omitted `{}`\nstdout:\n{}", signature, source)); + let tail = &source[start..]; + let end = tail.find("\n}\n").unwrap_or_else(|| { + panic!( + "LLVM function `{}` was not closed\nstdout:\n{}", + signature, source + ) + }); + &tail[..end + "\n}\n".len()] +} + +fn assert_contains_in_order(source: &str, needles: &[&str], message: &str) { + let mut cursor = 0; + for needle in needles { + let relative = source[cursor..] + .find(needle) + .unwrap_or_else(|| panic!("{}\nmissing `{}`\nsource:\n{}", message, needle, source)); + cursor += relative + needle.len(); + } +} diff --git a/compiler/tests/array_enum_alpha.rs b/compiler/tests/array_enum_alpha.rs new file mode 100644 index 0000000..d0ebb9f --- /dev/null +++ b/compiler/tests/array_enum_alpha.rs @@ -0,0 +1,86 @@ +use std::{ + fs, + path::Path, + process::{Command, Output}, +}; + +#[test] +fn array_enum_fixture_emits_enum_array_llvm_shapes() { + let output = run_glagol(["../examples/array-enum.slo"]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "compiler rejected array-enum fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define [3 x i32] @make_palette()") + && stdout.contains("define i32 @at([3 x i32] %colors, i32 %i)") + && stdout.contains("alloca [3 x i32]") + && stdout.contains("call void @__glagol_array_bounds_trap()"), + "LLVM output did not contain expected enum-array shapes\nstdout:\n{}", + stdout + ); + assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr); +} + +#[test] +fn array_enum_fixture_runs_tests_and_formats_stably() { + let run = run_glagol(["--run-tests", "../examples/array-enum.slo"]); + assert_success_stdout( + run, + concat!( + "test \"enum array immediate index\" ... ok\n", + "test \"enum array local index\" ... ok\n", + "test \"enum array param return dynamic index\" ... ok\n", + "3 test(s) passed\n", + ), + "array-enum test runner output", + ); + + let expected = fs::read_to_string("../tests/array-enum.slo").expect("read fixture"); + let format = run_glagol(["--format", "../tests/array-enum.slo"]); + assert_success_stdout(format, &expected, "array-enum formatter output"); +} + +#[test] +fn array_enum_fixture_lowering_snapshots_are_stable() { + let surface = run_glagol(["--inspect-lowering=surface", "../tests/array-enum.slo"]); + assert_success_stdout( + surface, + &fs::read_to_string("../tests/array-enum.surface.lower").expect("read surface snapshot"), + "array-enum surface lowering output", + ); + + let checked = run_glagol(["--inspect-lowering=checked", "../tests/array-enum.slo"]); + assert_success_stdout( + checked, + &fs::read_to_string("../tests/array-enum.checked.lower").expect("read checked snapshot"), + "array-enum checked lowering output", + ); +} + +fn run_glagol(args: [&str; N]) -> Output { + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn assert_success_stdout(output: Output, expected: &str, context: &str) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert_eq!(stdout, expected, "{} drifted", context); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} diff --git a/compiler/tests/array_index.rs b/compiler/tests/array_index.rs new file mode 100644 index 0000000..375d027 --- /dev/null +++ b/compiler/tests/array_index.rs @@ -0,0 +1,412 @@ +use std::{ + fs, + path::{Path, PathBuf}, + process::Command, +}; + +#[test] +fn array_fixture_emits_llvm_index_shape() { + let output = run_glagol(["../examples/array.slo"]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "compiler rejected array fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define i32 @immediate_second()") + && stdout.contains("insertvalue [3 x i32]") + && stdout.contains("extractvalue [3 x i32]") + && stdout.contains("%values.addr = alloca [3 x i32]") + && stdout.contains("getelementptr inbounds [3 x i32]") + && stdout.contains("load i32"), + "LLVM output did not contain expected array index shape\nstdout:\n{}", + stdout + ); + assert!( + !stdout.contains("immediate array index") && !stdout.contains("array local index"), + "compiler emitted test metadata into LLVM output\nstdout:\n{}", + stdout + ); + assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr); +} + +#[test] +fn immediate_literal_array_index_evaluates_all_element_expressions_in_order() { + let fixture = write_fixture( + "immediate-literal-array-index-evaluation", + r#"(module main) + +(fn first_value () -> i32 + 11) + +(fn second_value () -> i32 + 22) + +(fn third_value () -> i32 + 33) + +(fn selected_immediate () -> i32 + (index (array i32 (first_value) (second_value) (third_value)) 1)) + +(test "immediate literal index evaluates array" + (= (selected_immediate) 22)) + +(fn main () -> i32 + (selected_immediate)) +"#, + ); + let fixture_arg = fixture.to_string_lossy().into_owned(); + let output = run_glagol([fixture_arg.as_str()]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "compiler rejected immediate literal array index regression fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + + let first_call = single_occurrence_index(&stdout, "call i32 @first_value()"); + let second_call = single_occurrence_index(&stdout, "call i32 @second_value()"); + let third_call = single_occurrence_index(&stdout, "call i32 @third_value()"); + assert!( + first_call < second_call && second_call < third_call, + "LLVM output did not emit array element calls in source order\nstdout:\n{}", + stdout + ); + assert!( + stdout.contains("extractvalue [3 x i32]"), + "LLVM output did not select from the constructed aggregate\nstdout:\n{}", + stdout + ); + assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr); + + let run = run_glagol(["--run-tests", fixture_arg.as_str()]); + let run_stdout = String::from_utf8_lossy(&run.stdout); + let run_stderr = String::from_utf8_lossy(&run.stderr); + assert!( + run.status.success(), + "test runner rejected immediate literal array index regression fixture\nstdout:\n{}\nstderr:\n{}", + run_stdout, + run_stderr + ); + assert_eq!( + run_stdout, + concat!( + "test \"immediate literal index evaluates array\" ... ok\n", + "1 test(s) passed\n", + ), + "test runner output drifted" + ); + assert!( + run_stderr.is_empty(), + "test runner wrote stderr:\n{}", + run_stderr + ); +} + +#[test] +fn array_fixture_runs_top_level_tests() { + let output = run_glagol(["--run-tests", "../examples/array.slo"]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "test runner rejected array fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert_eq!( + stdout, + concat!( + "test \"immediate array index\" ... ok\n", + "test \"array local index\" ... ok\n", + "2 test(s) passed\n", + ), + "test runner output drifted" + ); + assert!(stderr.is_empty(), "test runner wrote stderr:\n{}", stderr); +} + +#[test] +fn array_fixture_is_formatter_stable() { + let expected = fs::read_to_string("../tests/array.slo").expect("read fixture"); + let output = run_glagol(["--format", "../tests/array.slo"]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "formatter rejected array fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert_eq!(stdout, expected, "formatter output drifted"); + assert!(stderr.is_empty(), "formatter wrote stderr:\n{}", stderr); +} + +#[test] +fn array_fixture_prints_lowered_shape() { + let surface = run_glagol(["--inspect-lowering=surface", "../examples/array.slo"]); + let surface_stdout = String::from_utf8_lossy(&surface.stdout); + let surface_stderr = String::from_utf8_lossy(&surface.stderr); + assert!( + surface.status.success(), + "surface lowering rejected array fixture\nstdout:\n{}\nstderr:\n{}", + surface_stdout, + surface_stderr + ); + assert!( + surface_stdout.contains("array i32") + && surface_stdout.contains("index") + && surface_stdout.contains("local let values: (array i32 3)"), + "surface lowering output lost array shape\nstdout:\n{}", + surface_stdout + ); + assert!( + surface_stderr.is_empty(), + "surface lowering wrote stderr:\n{}", + surface_stderr + ); + + let checked = run_glagol(["--inspect-lowering=checked", "../examples/array.slo"]); + let checked_stdout = String::from_utf8_lossy(&checked.stdout); + let checked_stderr = String::from_utf8_lossy(&checked.stderr); + assert!( + checked.status.success(), + "checked lowering rejected array fixture\nstdout:\n{}\nstderr:\n{}", + checked_stdout, + checked_stderr + ); + assert!( + checked_stdout.contains("array : (array i32 3)") + && checked_stdout.contains("index : i32") + && checked_stdout.contains("var values : (array i32 3)"), + "checked lowering output lost typed array shape\nstdout:\n{}", + checked_stdout + ); + assert!( + checked_stderr.is_empty(), + "checked lowering wrote stderr:\n{}", + checked_stderr + ); +} + +#[test] +fn array_value_flow_fixture_emits_llvm_value_flow_and_bounds_shape() { + let output = run_glagol(["../examples/array-value-flow.slo"]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "compiler rejected array value-flow fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define [3 x i32] @make_values(i32 %base)") + && stdout.contains("define i32 @first([3 x i32] %values)") + && stdout.contains("define i32 @at([3 x i32] %values, i32 %i)") + && stdout.contains("define [3 x i32] @echo([3 x i32] %values)") + && stdout.contains("define i32 @parameter_local_copy([3 x i32] %values, i32 %i)") + && stdout.contains("call [3 x i32] @make_values(i32 20)") + && stdout.contains("call i32 @at([3 x i32]") + && stdout.contains("load [3 x i32], ptr %values.addr"), + "LLVM output did not contain expected array value-flow shape\nstdout:\n{}", + stdout + ); + assert!( + stdout.contains("declare void @__glagol_array_bounds_trap()"), + "LLVM output did not declare the array bounds trap\nstdout:\n{}", + stdout + ); + let dynamic_index_function = + llvm_function(&stdout, "define i32 @at([3 x i32] %values, i32 %i)"); + assert_contains_in_order( + dynamic_index_function, + &[ + "icmp sge i32 %i, 0", + "icmp slt i32 %i, 3", + "br i1 %", + "array.index.trap", + "call void @__glagol_array_bounds_trap()", + "unreachable", + "array.index.ok", + "getelementptr inbounds [3 x i32]", + "i32 %i", + "load i32, ptr %", + ], + "LLVM output did not emit bounds branch/trap before the dynamic access", + ); + assert!( + !stdout.contains("array dynamic index") && !stdout.contains("array parameter local copy"), + "compiler emitted test metadata into LLVM output\nstdout:\n{}", + stdout + ); + assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr); +} + +#[test] +fn array_value_flow_fixture_runs_top_level_tests() { + let output = run_glagol(["--run-tests", "../examples/array-value-flow.slo"]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "test runner rejected array value-flow fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert_eq!( + stdout, + concat!( + "test \"array parameter value flow\" ... ok\n", + "test \"array dynamic index\" ... ok\n", + "test \"array local call value flow\" ... ok\n", + "test \"array parameter local copy\" ... ok\n", + "test \"array return call value flow\" ... ok\n", + "5 test(s) passed\n", + ), + "test runner output drifted" + ); + assert!(stderr.is_empty(), "test runner wrote stderr:\n{}", stderr); +} + +#[test] +fn array_value_flow_fixture_is_formatter_stable() { + let expected = fs::read_to_string("../tests/array-value-flow.slo").expect("read fixture"); + let output = run_glagol(["--format", "../tests/array-value-flow.slo"]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "formatter rejected array value-flow fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert_eq!(stdout, expected, "formatter output drifted"); + assert!(stderr.is_empty(), "formatter wrote stderr:\n{}", stderr); +} + +#[test] +fn array_value_flow_fixture_prints_lowered_shape() { + let surface = run_glagol([ + "--inspect-lowering=surface", + "../examples/array-value-flow.slo", + ]); + let surface_stdout = String::from_utf8_lossy(&surface.stdout); + let surface_stderr = String::from_utf8_lossy(&surface.stderr); + assert!( + surface.status.success(), + "surface lowering rejected array value-flow fixture\nstdout:\n{}\nstderr:\n{}", + surface_stdout, + surface_stderr + ); + assert!( + surface_stdout.contains("fn make_values(base: i32) -> (array i32 3)") + && surface_stdout.contains("fn at(values: (array i32 3), i: i32) -> i32") + && surface_stdout.contains("fn echo(values: (array i32 3)) -> (array i32 3)") + && surface_stdout.contains("local let values: (array i32 3)") + && surface_stdout.contains("index"), + "surface lowering output lost array value-flow shape\nstdout:\n{}", + surface_stdout + ); + assert!( + surface_stderr.is_empty(), + "surface lowering wrote stderr:\n{}", + surface_stderr + ); + + let checked = run_glagol([ + "--inspect-lowering=checked", + "../examples/array-value-flow.slo", + ]); + let checked_stdout = String::from_utf8_lossy(&checked.stdout); + let checked_stderr = String::from_utf8_lossy(&checked.stderr); + assert!( + checked.status.success(), + "checked lowering rejected array value-flow fixture\nstdout:\n{}\nstderr:\n{}", + checked_stdout, + checked_stderr + ); + assert!( + checked_stdout.contains("fn make_values(base: i32) -> (array i32 3)") + && checked_stdout.contains("fn at(values: (array i32 3), i: i32) -> i32") + && checked_stdout.contains("call make_values : (array i32 3)") + && checked_stdout.contains("local let values : unit") + && checked_stdout.contains("index : i32"), + "checked lowering output lost typed array value-flow shape\nstdout:\n{}", + checked_stdout + ); + assert!( + checked_stderr.is_empty(), + "checked lowering wrote stderr:\n{}", + checked_stderr + ); +} + +fn run_glagol(args: [&str; N]) -> std::process::Output { + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn write_fixture(name: &str, source: &str) -> PathBuf { + let mut path = std::env::temp_dir(); + path.push(format!( + "glagol-array-index-{}-{}.slo", + name, + std::process::id() + )); + fs::write(&path, source).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err)); + path +} + +fn single_occurrence_index(source: &str, needle: &str) -> usize { + let mut matches = source.match_indices(needle); + let (index, _) = matches + .next() + .unwrap_or_else(|| panic!("LLVM output omitted `{}`\nstdout:\n{}", needle, source)); + assert!( + matches.next().is_none(), + "LLVM output contained `{}` more than once\nstdout:\n{}", + needle, + source + ); + index +} + +fn llvm_function<'a>(source: &'a str, signature: &str) -> &'a str { + let start = source + .find(signature) + .unwrap_or_else(|| panic!("LLVM output omitted `{}`\nstdout:\n{}", signature, source)); + let tail = &source[start..]; + let end = tail.find("\n}\n").unwrap_or_else(|| { + panic!( + "LLVM function `{}` was not closed\nstdout:\n{}", + signature, source + ) + }); + &tail[..end + "\n}\n".len()] +} + +fn assert_contains_in_order(source: &str, needles: &[&str], message: &str) { + let mut cursor = 0; + for needle in needles { + let relative = source[cursor..] + .find(needle) + .unwrap_or_else(|| panic!("{}\nmissing `{}`\nsource:\n{}", message, needle, source)); + cursor += relative + needle.len(); + } +} diff --git a/compiler/tests/array_string_alpha.rs b/compiler/tests/array_string_alpha.rs new file mode 100644 index 0000000..779ae0d --- /dev/null +++ b/compiler/tests/array_string_alpha.rs @@ -0,0 +1,272 @@ +use std::{ + fs, + path::Path, + process::{Command, Output}, +}; + +#[test] +fn string_array_fixture_emits_string_array_llvm_shapes() { + let output = run_glagol(["../examples/array-string.slo"]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "compiler rejected string-array fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define ptr @immediate_second()") + && stdout.contains("define ptr @local_pick()") + && stdout.contains("insertvalue [3 x ptr]") + && stdout.contains("alloca [3 x ptr]"), + "LLVM output did not contain the string-array shapes\nstdout:\n{}", + stdout + ); + assert!( + !stdout.contains("string immediate array index") + && !stdout.contains("string local array index"), + "compiler emitted test metadata into LLVM output\nstdout:\n{}", + stdout + ); + assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr); +} + +#[test] +fn string_array_fixture_runs_tests_and_formats_stably() { + let run = run_glagol(["--run-tests", "../examples/array-string.slo"]); + assert_success_stdout( + run, + concat!( + "test \"string immediate array index\" ... ok\n", + "test \"string local array index\" ... ok\n", + "2 test(s) passed\n", + ), + "string-array test runner output", + ); + + let expected = fs::read_to_string("../tests/array-string.slo").expect("read fixture"); + let format = run_glagol(["--format", "../tests/array-string.slo"]); + assert_success_stdout(format, &expected, "string-array formatter output"); +} + +#[test] +fn string_array_fixture_prints_lowered_shape() { + let surface = run_glagol(["--inspect-lowering=surface", "../examples/array-string.slo"]); + let surface_stdout = String::from_utf8_lossy(&surface.stdout); + let surface_stderr = String::from_utf8_lossy(&surface.stderr); + assert!( + surface.status.success(), + "surface lowering rejected string-array fixture\nstdout:\n{}\nstderr:\n{}", + surface_stdout, + surface_stderr + ); + assert!( + surface_stdout.contains("array string") + && surface_stdout.contains("local let words: (array string 3)"), + "surface lowering output lost string-array shape\nstdout:\n{}", + surface_stdout + ); + assert!( + surface_stderr.is_empty(), + "surface lowering wrote stderr:\n{}", + surface_stderr + ); + + let checked = run_glagol(["--inspect-lowering=checked", "../examples/array-string.slo"]); + let checked_stdout = String::from_utf8_lossy(&checked.stdout); + let checked_stderr = String::from_utf8_lossy(&checked.stderr); + assert!( + checked.status.success(), + "checked lowering rejected string-array fixture\nstdout:\n{}\nstderr:\n{}", + checked_stdout, + checked_stderr + ); + assert!( + checked_stdout.contains("fn immediate_second() -> string") + && checked_stdout.contains("fn local_pick() -> string") + && checked_stdout.contains("index : string"), + "checked lowering output lost typed string-array shape\nstdout:\n{}", + checked_stdout + ); + assert!( + checked_stderr.is_empty(), + "checked lowering wrote stderr:\n{}", + checked_stderr + ); +} + +#[test] +fn string_array_value_flow_fixture_emits_llvm_value_flow_and_bounds_shape() { + let output = run_glagol(["../examples/array-string-value-flow.slo"]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "compiler rejected string-array value-flow fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define [3 x ptr] @make_words(ptr %head)") + && stdout.contains("define ptr @at([3 x ptr] %values, i32 %i)") + && stdout.contains("define [3 x ptr] @echo([3 x ptr] %values)") + && stdout.contains("call void @__glagol_array_bounds_trap()") + && stdout.contains("array.index.trap") + && stdout.contains("getelementptr inbounds [3 x ptr]"), + "LLVM output did not contain expected string-array value-flow shape\nstdout:\n{}", + stdout + ); + let dynamic_index_function = + llvm_function(&stdout, "define ptr @at([3 x ptr] %values, i32 %i)"); + assert_contains_in_order( + dynamic_index_function, + &[ + "icmp sge i32 %i, 0", + "icmp slt i32 %i, 3", + "array.index.trap", + "call void @__glagol_array_bounds_trap()", + "array.index.ok", + "getelementptr inbounds [3 x ptr]", + "load ptr, ptr %", + ], + "LLVM output did not emit bounds-check shape before string-array indexing", + ); + assert!( + !stdout.contains("string array local call value flow") + && !stdout.contains("string array dynamic index"), + "compiler emitted test metadata into LLVM output\nstdout:\n{}", + stdout + ); + assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr); +} + +#[test] +fn string_array_value_flow_fixture_runs_tests_and_formats_stably() { + let run = run_glagol(["--run-tests", "../examples/array-string-value-flow.slo"]); + assert_success_stdout( + run, + concat!( + "test \"string array parameter value flow\" ... ok\n", + "test \"string array dynamic index\" ... ok\n", + "test \"string array local call value flow\" ... ok\n", + "test \"string array parameter local copy\" ... ok\n", + "test \"string array return call value flow\" ... ok\n", + "5 test(s) passed\n", + ), + "string-array value-flow test runner output", + ); + + let expected = + fs::read_to_string("../tests/array-string-value-flow.slo").expect("read fixture"); + let format = run_glagol(["--format", "../tests/array-string-value-flow.slo"]); + assert_success_stdout( + format, + &expected, + "string-array value-flow formatter output", + ); +} + +#[test] +fn string_array_value_flow_fixture_prints_lowered_shape() { + let surface = run_glagol([ + "--inspect-lowering=surface", + "../examples/array-string-value-flow.slo", + ]); + let surface_stdout = String::from_utf8_lossy(&surface.stdout); + let surface_stderr = String::from_utf8_lossy(&surface.stderr); + assert!( + surface.status.success(), + "surface lowering rejected string-array value-flow fixture\nstdout:\n{}\nstderr:\n{}", + surface_stdout, + surface_stderr + ); + assert!( + surface_stdout.contains("fn make_words(head: string) -> (array string 3)") + && surface_stdout.contains("fn at(values: (array string 3), i: i32) -> string") + && surface_stdout.contains("fn echo(values: (array string 3)) -> (array string 3)") + && surface_stdout.contains("local let copy: (array string 3)"), + "surface lowering output lost string-array value-flow shape\nstdout:\n{}", + surface_stdout + ); + assert!( + surface_stderr.is_empty(), + "surface lowering wrote stderr:\n{}", + surface_stderr + ); + + let checked = run_glagol([ + "--inspect-lowering=checked", + "../examples/array-string-value-flow.slo", + ]); + let checked_stdout = String::from_utf8_lossy(&checked.stdout); + let checked_stderr = String::from_utf8_lossy(&checked.stderr); + assert!( + checked.status.success(), + "checked lowering rejected string-array value-flow fixture\nstdout:\n{}\nstderr:\n{}", + checked_stdout, + checked_stderr + ); + assert!( + checked_stdout.contains("fn make_words(head: string) -> (array string 3)") + && checked_stdout.contains("fn at(values: (array string 3), i: i32) -> string") + && checked_stdout.contains("fn echo(values: (array string 3)) -> (array string 3)") + && checked_stdout.contains("call make_words : (array string 3)") + && checked_stdout.contains("index : string"), + "checked lowering output lost typed string-array value-flow shape\nstdout:\n{}", + checked_stdout + ); + assert!( + checked_stderr.is_empty(), + "checked lowering wrote stderr:\n{}", + checked_stderr + ); +} + +fn run_glagol(args: [&str; N]) -> Output { + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn assert_success_stdout(output: Output, expected: &str, context: &str) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert_eq!(stdout, expected, "{} drifted", context); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} + +fn llvm_function<'a>(source: &'a str, signature: &str) -> &'a str { + let start = source + .find(signature) + .unwrap_or_else(|| panic!("LLVM output omitted `{}`\nstdout:\n{}", signature, source)); + let tail = &source[start..]; + let end = tail.find("\n}\n").unwrap_or_else(|| { + panic!( + "LLVM function `{}` was not closed\nstdout:\n{}", + signature, source + ) + }); + &tail[..end + "\n}\n".len()] +} + +fn assert_contains_in_order(source: &str, needles: &[&str], message: &str) { + let mut cursor = 0; + for needle in needles { + let relative = source[cursor..] + .find(needle) + .unwrap_or_else(|| panic!("{}\nmissing `{}`\nsource:\n{}", message, needle, source)); + cursor += relative + needle.len(); + } +} diff --git a/compiler/tests/array_struct_elements_alpha.rs b/compiler/tests/array_struct_elements_alpha.rs new file mode 100644 index 0000000..ab4e438 --- /dev/null +++ b/compiler/tests/array_struct_elements_alpha.rs @@ -0,0 +1,95 @@ +use std::{ + fs, + path::Path, + process::{Command, Output}, +}; + +#[test] +fn array_struct_elements_fixture_emits_struct_array_llvm_shapes() { + let output = run_glagol(["../examples/array-struct-elements.slo"]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "compiler rejected array-struct-elements fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define [3 x { i32, ptr, i32 }] @make_pixels(ptr %head)") + && stdout + .contains("define { i32, ptr, i32 } @at([3 x { i32, ptr, i32 }] %pixels, i32 %i)") + && stdout.contains("extractvalue { i32, ptr, i32 }") + && stdout.contains("call void @__glagol_array_bounds_trap()"), + "LLVM output did not contain expected struct-array shapes\nstdout:\n{}", + stdout + ); + assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr); +} + +#[test] +fn array_struct_elements_fixture_runs_tests_and_formats_stably() { + let run = run_glagol(["--run-tests", "../examples/array-struct-elements.slo"]); + assert_success_stdout( + run, + concat!( + "test \"struct array string field access\" ... ok\n", + "test \"struct array nested enum field access\" ... ok\n", + "test \"struct array local param return call flow\" ... ok\n", + "3 test(s) passed\n", + ), + "array-struct-elements test runner output", + ); + + let expected = fs::read_to_string("../tests/array-struct-elements.slo").expect("read fixture"); + let format = run_glagol(["--format", "../tests/array-struct-elements.slo"]); + assert_success_stdout(format, &expected, "array-struct-elements formatter output"); +} + +#[test] +fn array_struct_elements_fixture_lowering_snapshots_are_stable() { + let surface = run_glagol([ + "--inspect-lowering=surface", + "../tests/array-struct-elements.slo", + ]); + assert_success_stdout( + surface, + &fs::read_to_string("../tests/array-struct-elements.surface.lower") + .expect("read surface snapshot"), + "array-struct-elements surface lowering output", + ); + + let checked = run_glagol([ + "--inspect-lowering=checked", + "../tests/array-struct-elements.slo", + ]); + assert_success_stdout( + checked, + &fs::read_to_string("../tests/array-struct-elements.checked.lower") + .expect("read checked snapshot"), + "array-struct-elements checked lowering output", + ); +} + +fn run_glagol(args: [&str; N]) -> Output { + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn assert_success_stdout(output: Output, expected: &str, context: &str) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert_eq!(stdout, expected, "{} drifted", context); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} diff --git a/compiler/tests/array_struct_fields_alpha.rs b/compiler/tests/array_struct_fields_alpha.rs new file mode 100644 index 0000000..32f2c2f --- /dev/null +++ b/compiler/tests/array_struct_fields_alpha.rs @@ -0,0 +1,108 @@ +use std::{ + fs, + path::Path, + process::{Command, Output}, +}; + +#[test] +fn array_struct_fields_fixture_emits_array_field_struct_llvm_shapes() { + let output = run_glagol(["../examples/array-struct-fields.slo"]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "compiler rejected array-struct-fields fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define { [3 x i32], [2 x i64], [3 x double], [3 x i1], [3 x ptr] } @make_record(i32 %base, ptr %head)") + && stdout.contains("define i32 @int_at(") + && stdout.contains("define i64 @wide_at(") + && stdout.contains("define double @ratio_at(") + && stdout.contains("define i1 @flag_at(") + && stdout.contains("define ptr @word_at(") + && stdout.contains("extractvalue { [3 x i32], [2 x i64], [3 x double], [3 x i1], [3 x ptr] }") + && stdout.contains("call void @__glagol_array_bounds_trap()") + && stdout.contains("getelementptr inbounds [3 x ptr]"), + "LLVM output did not contain expected array-struct-field shape\nstdout:\n{}", + stdout + ); + assert!( + !stdout.contains("struct array i32 field access") + && !stdout.contains("struct array local param return call flow"), + "compiler emitted test metadata into LLVM output\nstdout:\n{}", + stdout + ); + assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr); +} + +#[test] +fn array_struct_fields_fixture_runs_tests_and_formats_stably() { + let run = run_glagol(["--run-tests", "../examples/array-struct-fields.slo"]); + assert_success_stdout( + run, + concat!( + "test \"struct array i32 field access\" ... ok\n", + "test \"struct array i64 field access\" ... ok\n", + "test \"struct array f64 field access\" ... ok\n", + "test \"struct array bool field access\" ... ok\n", + "test \"struct array string field access\" ... ok\n", + "test \"struct array local param return call flow\" ... ok\n", + "6 test(s) passed\n", + ), + "array-struct-fields test runner output", + ); + + let expected = fs::read_to_string("../tests/array-struct-fields.slo").expect("read fixture"); + let format = run_glagol(["--format", "../tests/array-struct-fields.slo"]); + assert_success_stdout(format, &expected, "array-struct-fields formatter output"); +} + +#[test] +fn array_struct_fields_fixture_lowering_snapshots_are_stable() { + let surface = run_glagol([ + "--inspect-lowering=surface", + "../tests/array-struct-fields.slo", + ]); + assert_success_stdout( + surface, + &fs::read_to_string("../tests/array-struct-fields.surface.lower") + .expect("read surface snapshot"), + "array-struct-fields surface lowering output", + ); + + let checked = run_glagol([ + "--inspect-lowering=checked", + "../tests/array-struct-fields.slo", + ]); + assert_success_stdout( + checked, + &fs::read_to_string("../tests/array-struct-fields.checked.lower") + .expect("read checked snapshot"), + "array-struct-fields checked lowering output", + ); +} + +fn run_glagol(args: [&str; N]) -> Output { + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn assert_success_stdout(output: Output, expected: &str, context: &str) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert_eq!(stdout, expected, "{} drifted", context); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} diff --git a/compiler/tests/benchmark_math_loop_scaffold.rs b/compiler/tests/benchmark_math_loop_scaffold.rs new file mode 100644 index 0000000..01c84f5 --- /dev/null +++ b/compiler/tests/benchmark_math_loop_scaffold.rs @@ -0,0 +1,224 @@ +use std::{ + env, + ffi::OsStr, + path::{Path, PathBuf}, + process::{Command, Output}, +}; + +const SCAFFOLD_FILES: &[&str] = &[ + "slovo.toml", + "README.md", + "benchmark.json", + "src/main.slo", + "clojure", + "common-lisp", + "c", + "rust", + "python", + "run.py", +]; + +#[test] +fn benchmark_scaffolds_check_format_and_run_python_checksums() { + let roots = benchmark_roots(); + + for root in roots { + assert_benchmark_scaffold_checks_formats_and_lists_metadata(&root); + } +} + +fn assert_benchmark_scaffold_checks_formats_and_lists_metadata(root: &Path) { + let benchmark_name = root + .file_name() + .and_then(|name| name.to_str()) + .unwrap_or("benchmark"); + + for relative in SCAFFOLD_FILES { + let path = root.join(relative); + assert!(path.exists(), "missing scaffold path `{}`", path.display()); + } + + let fmt = run_glagol([OsStr::new("fmt"), OsStr::new("--check"), root.as_os_str()]); + assert_success_stdout(&format!("{benchmark_name} fmt --check"), fmt, ""); + + let check = run_glagol([OsStr::new("check"), root.as_os_str()]); + assert_success_stdout(&format!("{benchmark_name} project check"), check, ""); + + let python = python_command(); + let list = Command::new(&python) + .arg("run.py") + .arg("--list") + .arg("--json") + .current_dir(&root) + .output() + .unwrap_or_else(|err| panic!("run `{}` run.py --list --json: {}", python, err)); + assert_success(&format!("{benchmark_name} runner --list --json"), &list); + + let stdout = String::from_utf8_lossy(&list.stdout); + for needle in [ + r#""loop_count": 1000000"#, + r#""hot_loop_count": 10000000"#, + r#""loop_count_source": "stdin""#, + r#""timing_scope": "local-machine comparison only""#, + r#""hot-loop""#, + r#""cold-process""#, + r#""name": "slovo""#, + r#""name": "c""#, + r#""name": "rust""#, + r#""name": "python""#, + r#""name": "clojure""#, + r#""name": "common_lisp""#, + ] { + assert!( + stdout.contains(needle), + "runner metadata missing `{}`\nstdout:\n{}", + needle, + stdout + ); + } + + let run = Command::new(&python) + .arg("run.py") + .arg("--only") + .arg("python") + .arg("--repeats") + .arg("1") + .arg("--warmups") + .arg("0") + .arg("--json") + .current_dir(&root) + .output() + .unwrap_or_else(|err| panic!("run `{}` run.py --only python: {}", python, err)); + assert_success(&format!("{benchmark_name} runner python checksum"), &run); + + let stdout = String::from_utf8_lossy(&run.stdout); + for needle in [ + r#""name": "python""#, + r#""status": "ok""#, + r#""checksum": ""#, + r#""warmups": 0"#, + r#""repeats": 1"#, + ] { + assert!( + stdout.contains(needle), + "runner checksum execution missing `{}`\nstdout:\n{}", + needle, + stdout + ); + } + + let hot_run = Command::new(&python) + .arg("run.py") + .arg("--mode") + .arg("hot-loop") + .arg("--only") + .arg("python") + .arg("--repeats") + .arg("1") + .arg("--warmups") + .arg("0") + .arg("--json") + .current_dir(root) + .output() + .unwrap_or_else(|err| { + panic!( + "run `{}` run.py --mode hot-loop --only python: {}", + python, err + ) + }); + assert_success( + &format!("{benchmark_name} runner python hot-loop checksum"), + &hot_run, + ); + + let stdout = String::from_utf8_lossy(&hot_run.stdout); + for needle in [ + r#""name": "python""#, + r#""status": "ok""#, + r#""timing_mode": "hot-loop""#, + r#""loop_count": 10000000"#, + r#""normalized_median_ms": "#, + ] { + assert!( + stdout.contains(needle), + "runner hot-loop execution missing `{}`\nstdout:\n{}", + needle, + stdout + ); + } +} + +fn benchmark_roots() -> Vec { + let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../benchmarks"); + vec![ + root.join("math-loop"), + root.join("branch-loop"), + root.join("parse-loop"), + root.join("array-index-loop"), + root.join("string-eq-loop"), + root.join("array-struct-field-loop"), + root.join("enum-struct-payload-loop"), + root.join("vec-i32-index-loop"), + root.join("vec-string-eq-loop"), + ] +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn python_command() -> String { + if let Some(python) = env::var_os("PYTHON") { + return python.to_string_lossy().into_owned(); + } + + for candidate in ["python3", "python"] { + if Command::new(candidate) + .arg("--version") + .output() + .map(|output| output.status.success()) + .unwrap_or(false) + { + return candidate.to_string(); + } + } + + panic!("benchmark runner list-mode test requires python3 or python") +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} + +fn assert_success_stdout(context: &str, output: Output, expected: &str) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert_eq!(stdout, expected, "{} stdout mismatch", context); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} diff --git a/compiler/tests/beta_1_0_0.rs b/compiler/tests/beta_1_0_0.rs new file mode 100644 index 0000000..67e4727 --- /dev/null +++ b/compiler/tests/beta_1_0_0.rs @@ -0,0 +1,413 @@ +use std::{ + ffi::OsStr, + fs, + path::{Path, PathBuf}, + process::{Command, Output}, + sync::atomic::{AtomicUsize, Ordering}, +}; + +static NEXT_ID: AtomicUsize = AtomicUsize::new(0); + +struct WorkspacePackageSpec<'a> { + member: &'a str, + manifest: &'a str, + modules: &'a [(&'a str, &'a str)], +} + +#[test] +fn beta_project_proves_general_purpose_cli_surface() { + let project = unique_path("beta-1-0-0-project"); + let std_path = slovo_std_path(); + + let scaffold = run_glagol(["new".as_ref(), project.as_os_str()]); + assert_success("beta scaffold", &scaffold); + + fs::write( + project.join("src/model.slo"), + r#"(module model (export Mode Report make_report total_score mode_name second_label report_name report_mode_text)) + +(import std.vec_i32 (pair sum)) + +(import std.num (i64_to_string)) + +(enum Mode + Dev + (Code i64)) + +(struct Report + (name string) + (scores (vec i32)) + (ratio f64) + (active bool) + (mode Mode) + (labels (array string 2))) + +(fn make_report () -> Report + (Report (name "beta") (scores (pair 3 4)) (ratio 1.5) (active true) (mode (Mode.Code 7i64)) (labels (array string "slovo" "glagol")))) + +(fn total_score ((report Report)) -> i32 + (sum (. report scores))) + +(fn mode_name ((mode Mode)) -> string + (match mode + ((Mode.Dev) + "dev") + ((Mode.Code payload) + (i64_to_string payload)))) + +(fn second_label ((report Report)) -> string + (index (. report labels) 1)) + +(fn report_name () -> string + (. (make_report) name)) + +(fn report_mode_text () -> string + (mode_name (. (make_report) mode))) +"#, + ) + .expect("write beta model"); + + fs::write( + project.join("src/helpers.slo"), + r#"(module helpers (export title_text parse_port_or port_parses big_counter_text parse_ratio parse_enabled)) + +(import std.string (concat parse_u32_result parse_u32_or parse_f64_or parse_bool_or)) + +(import std.num (u64_to_string)) + +(import std.result (is_ok_u32)) + +(fn title_text ((left string) (right string)) -> string + (concat left right)) + +(fn parse_port_or ((text string) (fallback u32)) -> u32 + (parse_u32_or text fallback)) + +(fn port_parses ((text string)) -> bool + (is_ok_u32 (parse_u32_result text))) + +(fn big_counter_text ((value u64)) -> string + (u64_to_string value)) + +(fn parse_ratio ((text string) (fallback f64)) -> f64 + (parse_f64_or text fallback)) + +(fn parse_enabled ((text string) (fallback bool)) -> bool + (parse_bool_or text fallback)) +"#, + ) + .expect("write beta helpers"); + + fs::write( + project.join("src/main.slo"), + r#"(module main) + +(import model (make_report total_score second_label report_name report_mode_text)) + +(import helpers (title_text parse_port_or port_parses big_counter_text parse_ratio parse_enabled)) + +(fn loop_sum ((limit i32)) -> i32 + (var current i32 0) + (var total i32 0) + (while (< current limit) + (set total (+ total current)) + (set current (+ current 1))) + total) + +(fn main () -> i32 + (std.io.print_string (title_text (report_name) "!")) + 0) + +(test "beta project string helpers" + (= (title_text "slo" "vo") "slovo")) + +(test "beta project unsigned helpers" + (if (= (parse_port_or "8080" 1u32) 8080u32) + (if (port_parses "8080") + (= (big_counter_text 42u64) "42") + false) + false)) + +(test "beta project float bool helpers" + (if (= (parse_ratio "3.5" 0.0) 3.5) + (parse_enabled "true" false) + false)) + +(test "beta project data types" + (if (= (total_score (make_report)) 7) + (if (= (report_mode_text) "7") + (= (second_label (make_report)) "glagol") + false) + false)) + +(test "beta project control flow" + (= (loop_sum 5) 10)) +"#, + ) + .expect("write beta main"); + + let check = run_glagol_with_env( + ["check".as_ref(), project.as_os_str()], + &[("SLOVO_STD_PATH", std_path.as_os_str())], + ); + assert_success("beta project check", &check); + + let fmt_check = run_glagol_with_env( + ["fmt".as_ref(), "--check".as_ref(), project.as_os_str()], + &[("SLOVO_STD_PATH", std_path.as_os_str())], + ); + assert_success("beta project fmt --check", &fmt_check); + + let test = run_glagol_with_env( + ["test".as_ref(), project.as_os_str()], + &[("SLOVO_STD_PATH", std_path.as_os_str())], + ); + assert_success("beta project test", &test); + assert_eq!( + String::from_utf8_lossy(&test.stdout), + "test \"beta project string helpers\" ... ok\n\ +test \"beta project unsigned helpers\" ... ok\n\ +test \"beta project float bool helpers\" ... ok\n\ +test \"beta project data types\" ... ok\n\ +test \"beta project control flow\" ... ok\n\ +5 test(s) passed\n" + ); + + let docs = unique_path("beta-1-0-0-project-docs"); + let doc = run_glagol_with_env( + [ + "doc".as_ref(), + project.as_os_str(), + "-o".as_ref(), + docs.as_os_str(), + ], + &[("SLOVO_STD_PATH", std_path.as_os_str())], + ); + assert_success("beta project doc", &doc); + let doc_index = fs::read_to_string(docs.join("index.md")).expect("read beta docs"); + assert!(doc_index.contains("# Project ")); + assert!(doc_index.contains("## Module model")); + assert!(doc_index.contains("## Module helpers")); + assert!(doc_index.contains("## Module main")); + assert!(doc_index.contains("beta project control flow")); + + let binary = unique_path("beta-1-0-0-project-bin"); + let build = run_glagol_with_env( + [ + "build".as_ref(), + project.as_os_str(), + "-o".as_ref(), + binary.as_os_str(), + ], + &[("SLOVO_STD_PATH", std_path.as_os_str())], + ); + if build.status.success() { + let run = Command::new(&binary).output().expect("run beta project"); + assert_success("beta project binary", &run); + assert_eq!(String::from_utf8_lossy(&run.stdout), "beta!\n"); + } else { + assert_stderr_contains("beta project build", &build, "ToolchainUnavailable"); + } +} + +#[test] +fn beta_workspace_proves_local_package_dependency_flow() { + let workspace = write_workspace( + "beta-1-0-0-workspace", + "[workspace]\nmembers = [\"packages/app\", \"packages/libutil\"]\n", + &[ + WorkspacePackageSpec { + member: "packages/libutil", + manifest: "[package]\nname = \"libutil\"\nversion = \"0.1.0\"\n", + modules: &[( + "text", + r#"(module text (export Status stamp parse_count render_total)) + +(import std.string (concat parse_u64_or)) + +(import std.num (u64_to_string)) + +(enum Status + Ready + Busy) + +(fn stamp ((status Status)) -> string + (match status + ((Status.Ready) + "ready") + ((Status.Busy) + "busy"))) + +(fn parse_count ((text string)) -> u64 + (parse_u64_or text 0u64)) + +(fn render_total ((label string) (value u64)) -> string + (concat label (u64_to_string value))) +"#, + )], + }, + WorkspacePackageSpec { + member: "packages/app", + manifest: "[package]\nname = \"app\"\nversion = \"0.1.0\"\n\n[dependencies]\nlibutil = { path = \"../libutil\" }\n", + modules: &[( + "main", + r#"(module main) + +(import libutil.text (Status stamp parse_count render_total)) + +(fn main () -> i32 + (if (= (parse_count "12") 12u64) + 0 + 1)) + +(test "beta workspace package import" + (if (= (stamp (Status.Ready)) "ready") + (= (render_total "n=" 12u64) "n=12") + false)) +"#, + )], + }, + ], + ); + let std_path = slovo_std_path(); + + let check = run_glagol_with_env( + ["check".as_ref(), workspace.as_os_str()], + &[("SLOVO_STD_PATH", std_path.as_os_str())], + ); + assert_success_stdout("beta workspace check", check, ""); + + let test = run_glagol_with_env( + ["test".as_ref(), workspace.as_os_str()], + &[("SLOVO_STD_PATH", std_path.as_os_str())], + ); + assert_success_stdout( + "beta workspace test", + test, + "test \"beta workspace package import\" ... ok\n1 test(s) passed\n", + ); + + let binary = unique_path("beta-1-0-0-workspace-bin"); + let build = run_glagol_with_env( + [ + "build".as_ref(), + workspace.as_os_str(), + "-o".as_ref(), + binary.as_os_str(), + ], + &[("SLOVO_STD_PATH", std_path.as_os_str())], + ); + if build.status.success() { + let run = Command::new(&binary) + .output() + .expect("run beta workspace binary"); + assert_success("beta workspace binary", &run); + assert!(run.stdout.is_empty(), "beta workspace binary wrote stdout"); + } else { + assert_stderr_contains("beta workspace build", &build, "ToolchainUnavailable"); + } +} + +fn slovo_std_path() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../lib/std") + .canonicalize() + .expect("canonicalize slovo std path") +} + +fn write_workspace( + name: &str, + workspace_manifest: &str, + packages: &[WorkspacePackageSpec<'_>], +) -> PathBuf { + let root = unique_path(name); + fs::create_dir_all(&root).expect("create workspace root"); + fs::write(root.join("slovo.toml"), workspace_manifest).expect("write workspace manifest"); + for package in packages { + let package_root = root.join(package.member); + let src = package_root.join("src"); + fs::create_dir_all(&src).expect("create package src"); + fs::write(package_root.join("slovo.toml"), package.manifest) + .expect("write package manifest"); + for (module, source) in package.modules { + fs::write(src.join(format!("{}.slo", module)), source).expect("write package module"); + } + } + root +} + +fn unique_path(name: &str) -> PathBuf { + let id = NEXT_ID.fetch_add(1, Ordering::Relaxed); + let nanos = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .expect("system clock before UNIX_EPOCH") + .as_nanos(); + std::env::temp_dir().join(format!( + "glagol-beta-1-0-0-{}-{}-{}-{}", + std::process::id(), + nanos, + id, + name + )) +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .output() + .expect("run glagol") +} + +fn run_glagol_with_env(args: I, envs: &[(&str, &OsStr)]) -> Output +where + I: IntoIterator, + S: AsRef, +{ + let mut command = Command::new(env!("CARGO_BIN_EXE_glagol")); + command.args(args); + for (key, value) in envs { + command.env(key, value); + } + command.output().expect("run glagol with env") +} + +fn assert_success(context: &str, output: &Output) { + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); +} + +fn assert_success_stdout(context: &str, output: Output, expected: &str) { + assert_success(context, &output); + assert_eq!( + String::from_utf8_lossy(&output.stdout), + expected, + "{} stdout mismatch", + context + ); + assert!( + output.stderr.is_empty(), + "{} wrote stderr:\n{}", + context, + String::from_utf8_lossy(&output.stderr) + ); +} + +fn assert_stderr_contains(context: &str, output: &Output, needle: &str) { + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + stderr.contains(needle), + "{} stderr did not contain `{}`:\n{}", + context, + needle, + stderr + ); +} diff --git a/compiler/tests/beta_v2_0_0_beta_1.rs b/compiler/tests/beta_v2_0_0_beta_1.rs new file mode 100644 index 0000000..cf6bd22 --- /dev/null +++ b/compiler/tests/beta_v2_0_0_beta_1.rs @@ -0,0 +1,107 @@ +use std::{ + fs, + path::PathBuf, + process::{Command, Output}, + sync::atomic::{AtomicUsize, Ordering}, +}; + +static NEXT_ID: AtomicUsize = AtomicUsize::new(0); + +#[test] +fn beta_scaffolded_project_runs_current_v1_7_gate_workflow() { + let project = unique_path("beta-project"); + + let scaffold = run_glagol(["new".as_ref(), project.as_os_str()]); + assert_success("beta scaffold", &scaffold); + + fs::write( + project.join("src/main.slo"), + "(module main)\n\n(fn main () -> i32\n (let values (array i32 3) (array i32 2 3 4))\n (std.io.print_i32 (index values 1))\n 0)\n\n(test \"fixed array collection subset\"\n (= (index (array i32 1 2 3) 2) 3))\n", + ) + .expect("write beta project main"); + + let fmt_check = run_glagol(["fmt".as_ref(), "--check".as_ref(), project.as_os_str()]); + assert_success("beta project fmt --check", &fmt_check); + + let test = run_glagol(["test".as_ref(), project.as_os_str()]); + assert_success("beta project test", &test); + assert_eq!( + String::from_utf8_lossy(&test.stdout), + "test \"fixed array collection subset\" ... ok\n1 test(s) passed\n" + ); + + let docs = unique_path("beta-project-docs"); + let doc = run_glagol([ + "doc".as_ref(), + project.as_os_str(), + "-o".as_ref(), + docs.as_os_str(), + ]); + assert_success("beta project doc", &doc); + let doc_index = fs::read_to_string(docs.join("index.md")).expect("read beta docs"); + assert!(doc_index.contains("# Project glagol-beta-")); + assert!(doc_index.contains("- `main() -> i32`")); + assert!(doc_index.contains("- `fixed array collection subset`")); + + let binary = unique_path("beta-project-bin"); + let build = run_glagol([ + "build".as_ref(), + project.as_os_str(), + "-o".as_ref(), + binary.as_os_str(), + ]); + if build.status.success() { + let run = Command::new(&binary).output().expect("run beta project"); + assert_success("beta project binary", &run); + assert_eq!(String::from_utf8_lossy(&run.stdout), "3\n"); + } else { + assert_stderr_contains("beta project build", &build, "ToolchainUnavailable"); + } +} + +fn unique_path(name: &str) -> PathBuf { + let id = NEXT_ID.fetch_add(1, Ordering::Relaxed); + let nanos = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .expect("system clock before UNIX_EPOCH") + .as_nanos(); + std::env::temp_dir().join(format!( + "glagol-beta-{}-{}-{}-{}", + std::process::id(), + nanos, + id, + name + )) +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .output() + .expect("run glagol") +} + +fn assert_success(context: &str, output: &Output) { + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); +} + +fn assert_stderr_contains(context: &str, output: &Output, needle: &str) { + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + stderr.contains(needle), + "{} stderr did not contain `{}`:\n{}", + context, + needle, + stderr + ); +} diff --git a/compiler/tests/binary_smoke.rs b/compiler/tests/binary_smoke.rs new file mode 100644 index 0000000..777f903 --- /dev/null +++ b/compiler/tests/binary_smoke.rs @@ -0,0 +1,129 @@ +use std::{ + env, + path::{Path, PathBuf}, + process::{Command, Output}, +}; + +#[test] +#[ignore = "runs cargo build and target/debug/glagol; promotion smoke for the built CLI artifact"] +fn cargo_build_produces_direct_binary_that_runs_supported_modes() { + let manifest = Path::new(env!("CARGO_MANIFEST_DIR")); + let build = Command::new("cargo") + .arg("build") + .current_dir(manifest) + .output() + .unwrap_or_else(|err| panic!("run cargo build: {}", err)); + assert_success("cargo build", build); + + let binary = direct_debug_binary(manifest); + assert!( + binary.is_file(), + "cargo build did not produce direct binary `{}`", + binary.display() + ); + + let add_fixture = manifest.join("../examples/add.slo"); + let llvm = Command::new(&binary) + .arg(&add_fixture) + .output() + .unwrap_or_else(|err| panic!("run `{}`: {}", binary.display(), err)); + assert_success_contains( + "direct binary LLVM emission", + llvm, + &[ + "define i32 @add", + "define i32 @main", + "call void @print_i32", + ], + ); + + let test_fixture = manifest.join("../tests/top-level-test.slo"); + let check_tests = Command::new(&binary) + .arg("--check-tests") + .arg(&test_fixture) + .output() + .unwrap_or_else(|err| panic!("run `{}` --check-tests: {}", binary.display(), err)); + assert_success_stdout( + "direct binary check-tests", + check_tests, + "test \"add works\" ... checked\n1 test(s) checked\n", + ); + + let run_tests = Command::new(&binary) + .arg("--run-tests") + .arg(&test_fixture) + .output() + .unwrap_or_else(|err| panic!("run `{}` --run-tests: {}", binary.display(), err)); + assert_success_stdout( + "direct binary run-tests", + run_tests, + "test \"add works\" ... ok\n1 test(s) passed\n", + ); +} + +fn direct_debug_binary(manifest: &Path) -> PathBuf { + let target = if let Some(target_dir) = env::var_os("CARGO_TARGET_DIR") { + let target_dir = PathBuf::from(target_dir); + if target_dir.is_absolute() { + target_dir + } else { + manifest.join(target_dir) + } + } else { + manifest.join("target") + }; + + target.join("debug").join(binary_name()) +} + +fn binary_name() -> String { + format!("glagol{}", env::consts::EXE_SUFFIX) +} + +fn assert_success(context: &str, output: Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); +} + +fn assert_success_contains(context: &str, output: Output, expected: &[&str]) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); + for needle in expected { + assert!( + stdout.contains(needle), + "{} stdout did not contain `{}`\nstdout:\n{}", + context, + needle, + stdout + ); + } +} + +fn assert_success_stdout(context: &str, output: Output, expected: &str) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert_eq!(stdout, expected, "{} stdout mismatch", context); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} diff --git a/compiler/tests/boolean_logic_alpha.rs b/compiler/tests/boolean_logic_alpha.rs new file mode 100644 index 0000000..086d9fc --- /dev/null +++ b/compiler/tests/boolean_logic_alpha.rs @@ -0,0 +1,154 @@ +use std::{ + env, + ffi::OsStr, + fs, + path::{Path, PathBuf}, + process::{Command, Output}, + sync::atomic::{AtomicUsize, Ordering}, +}; + +static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0); + +const EXPECTED_TEST_OUTPUT: &str = concat!( + "test \"and true\" ... ok\n", + "test \"and false\" ... ok\n", + "test \"or true\" ... ok\n", + "test \"not false\" ... ok\n", + "test \"and short circuit\" ... ok\n", + "test \"or short circuit\" ... ok\n", + "test \"boolean logic summary\" ... ok\n", + "7 test(s) passed\n", +); + +#[test] +fn boolean_logic_fixture_formats_lowers_and_runs() { + let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/boolean-logic.slo"); + + let fmt = run_glagol([ + OsStr::new("fmt"), + OsStr::new("--check"), + fixture.as_os_str(), + ]); + assert_success("format boolean logic fixture", &fmt); + + let check = run_glagol([OsStr::new("check"), fixture.as_os_str()]); + assert_success_stdout(check, "", "check boolean logic fixture"); + + let llvm = run_glagol([fixture.as_os_str()]); + assert_success("compile boolean logic fixture", &llvm); + let stdout = String::from_utf8_lossy(&llvm.stdout); + assert!( + stdout.contains("define i1 @and_true()") + && stdout.contains("define i1 @and_short_circuits()") + && stdout.contains("br i1"), + "boolean logic LLVM shape drifted\nstdout:\n{}", + stdout + ); + + let tests = run_glagol([OsStr::new("test"), fixture.as_os_str()]); + assert_success_stdout(tests, EXPECTED_TEST_OUTPUT, "test boolean logic fixture"); +} + +#[test] +fn boolean_logic_rejections_are_explicit() { + let bad_and = write_fixture( + "bad-and-arity", + "(module main)\n\n(fn main () -> i32\n (if (and true) 0 1))\n", + ); + let output = run_glagol([bad_and.as_os_str()]); + assert_failure_contains( + &output, + "bad and arity", + &[ + "InvalidLogical", + "logical operator expects exactly two operands", + ], + ); + + let bad_not = write_fixture( + "bad-not-arity", + "(module main)\n\n(fn main () -> i32\n (if (not true false) 0 1))\n", + ); + let output = run_glagol([bad_not.as_os_str()]); + assert_failure_contains( + &output, + "bad not arity", + &["InvalidLogical", "logical not expects exactly one operand"], + ); +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn write_fixture(name: &str, source: &str) -> PathBuf { + let mut path = env::temp_dir(); + path.push(format!( + "glagol-{}-{}-{}.slo", + name, + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + fs::write(&path, source).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err)); + path +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} + +fn assert_success_stdout(output: Output, expected: &str, context: &str) { + assert_success(context, &output); + assert_eq!( + String::from_utf8_lossy(&output.stdout), + expected, + "{} stdout drifted", + context + ); +} + +fn assert_failure_contains(output: &Output, context: &str, expected: &[&str]) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + !output.status.success(), + "{} unexpectedly succeeded\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert!( + stdout.is_empty(), + "{} wrote stdout on failure:\n{}", + context, + stdout + ); + for needle in expected { + assert!( + stderr.contains(needle), + "{} stderr missing `{}`\nstderr:\n{}", + context, + needle, + stderr + ); + } +} diff --git a/compiler/tests/c_ffi_scalar.rs b/compiler/tests/c_ffi_scalar.rs new file mode 100644 index 0000000..26e6a89 --- /dev/null +++ b/compiler/tests/c_ffi_scalar.rs @@ -0,0 +1,450 @@ +use std::{ + env, fs, + path::{Path, PathBuf}, + process::Command, +}; + +#[test] +fn c_import_emits_declare_and_direct_call_inside_unsafe() { + let output = run_glagol(["../examples/ffi/exp-6-c-add/main.slo"]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "compiler rejected C FFI fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("declare i32 @c_add(i32, i32)") + && stdout.contains("call i32 @c_add(i32 40, i32 2)") + && stdout.contains("define i32 @main()"), + "LLVM output lost C import declaration/call shape\nstdout:\n{}", + stdout + ); +} + +#[test] +fn c_import_call_requires_unsafe_before_backend_behavior() { + let output = run_glagol_source( + "c-ffi-unsafe-required", + "(module main)\n\n(import_c c_add ((lhs i32) (rhs i32)) -> i32)\n\n(fn main () -> i32\n (c_add 1 2))\n", + &[], + ); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + !output.status.success(), + "compiler accepted safe C import call" + ); + assert!( + stderr.contains("(code UnsafeRequired)") && !stderr.contains("UnsupportedBackendFeature"), + "C import call outside unsafe did not produce UnsafeRequired first\nstderr:\n{}", + stderr + ); +} + +#[test] +fn c_import_rejects_unsupported_signature_types() { + for (name, source) in [ + ( + "bool-param", + "(module main)\n\n(import_c c_bool ((flag bool)) -> i32)\n\n(fn main () -> i32\n 0)\n", + ), + ( + "string-return", + "(module main)\n\n(import_c c_string () -> string)\n\n(fn main () -> i32\n 0)\n", + ), + ( + "array-param", + "(module main)\n\n(import_c c_array ((xs (array i32 2))) -> i32)\n\n(fn main () -> i32\n 0)\n", + ), + ] { + let output = run_glagol_source(&format!("c-ffi-{}", name), source, &[]); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + !output.status.success() && stderr.contains("(code UnsupportedCImportType)"), + "unsupported C import type `{}` did not produce UnsupportedCImportType\nstderr:\n{}", + name, + stderr + ); + } +} + +#[test] +fn c_import_rejects_malformed_duplicate_and_reserved_names() { + let malformed = run_glagol_source( + "c-ffi-malformed", + "(module main)\n\n(import_c c_add ((lhs i32) (rhs i32)) i32)\n\n(fn main () -> i32\n 0)\n", + &[], + ); + assert_diagnostic(&malformed, "MalformedCImport"); + + let invalid_symbol = run_glagol_source( + "c-ffi-invalid-symbol", + "(module main)\n\n(import_c c-add () -> i32)\n\n(fn main () -> i32\n 0)\n", + &[], + ); + assert_diagnostic(&invalid_symbol, "MalformedCImport"); + + let duplicate_param = run_glagol_source( + "c-ffi-duplicate-param", + "(module main)\n\n(import_c c_add ((value i32) (value i32)) -> i32)\n\n(fn main () -> i32\n 0)\n", + &[], + ); + assert_diagnostic(&duplicate_param, "DuplicateName"); + + let duplicate = run_glagol_source( + "c-ffi-duplicate", + "(module main)\n\n(fn c_add () -> i32\n 1)\n\n(import_c c_add () -> i32)\n\n(fn main () -> i32\n (c_add))\n", + &[], + ); + assert_diagnostic(&duplicate, "DuplicateTopLevelName"); + + let reserved = run_glagol_source( + "c-ffi-reserved", + "(module main)\n\n(import_c ffi_call () -> i32)\n\n(fn main () -> i32\n 0)\n", + &[], + ); + assert_diagnostic(&reserved, "ReservedName"); +} + +#[test] +fn raw_unsafe_ffi_call_remains_unsupported_inside_unsafe() { + let output = run_glagol_source( + "c-ffi-raw-ffi-call", + "(module main)\n\n(fn main () -> i32\n (unsafe\n (ffi_call 1)))\n", + &[], + ); + assert_diagnostic(&output, "UnsupportedUnsafeOperation"); +} + +#[test] +fn formatter_and_lowering_show_c_imports() { + let fixture = "../examples/ffi/exp-6-c-add/main.slo"; + let formatted = run_glagol(["--format", fixture]); + let stdout = String::from_utf8_lossy(&formatted.stdout); + assert!( + formatted.status.success() + && stdout.contains("(import_c c_add ((lhs i32) (rhs i32)) -> i32)"), + "formatter did not preserve import_c\nstdout:\n{}\nstderr:\n{}", + stdout, + String::from_utf8_lossy(&formatted.stderr) + ); + + let surface = run_glagol(["--inspect-lowering=surface", fixture]); + let surface_stdout = String::from_utf8_lossy(&surface.stdout); + assert!( + surface.status.success() + && surface_stdout.contains(" import_c c_add(lhs: i32, rhs: i32) -> i32"), + "surface lowering did not show C import\nstdout:\n{}", + surface_stdout + ); + + let checked = run_glagol(["--inspect-lowering=checked", fixture]); + let checked_stdout = String::from_utf8_lossy(&checked.stdout); + assert!( + checked.status.success() + && checked_stdout.contains(" import_c c_add(lhs: i32, rhs: i32) -> i32"), + "checked lowering did not show C import\nstdout:\n{}", + checked_stdout + ); +} + +#[test] +fn test_runner_reports_c_imports_as_unsupported_execution() { + let output = run_glagol(["--run-tests", "../examples/ffi/exp-6-c-add/main.slo"]); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + !output.status.success() && stderr.contains("(code UnsupportedTestExpression)"), + "test runner did not report explicit C import execution diagnostic\nstderr:\n{}", + stderr + ); +} + +#[test] +fn test_runner_filter_skips_unselected_c_import_execution() { + let source = "(module main)\n\n(import_c c_add ((lhs i32) (rhs i32)) -> i32)\n\n(test \"pure selected\"\n true)\n\n(test \"ffi skipped\"\n (= (unsafe\n (c_add 1 2)) 3))\n"; + let path = temp_path("c-ffi-filter-skips-test-runner", "input.slo"); + fs::write(&path, source).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err)); + + let skipped = run_glagol_os([ + "--run-tests".as_ref(), + "--filter".as_ref(), + "pure".as_ref(), + path.as_os_str(), + ]); + let stdout = String::from_utf8_lossy(&skipped.stdout); + let stderr = String::from_utf8_lossy(&skipped.stderr); + assert!( + skipped.status.success() + && stdout.contains("test \"ffi skipped\" ... skipped") + && stdout.contains("selected 1") + && stdout.contains("skipped 1") + && !stderr.contains("UnsupportedTestExpression"), + "filtered run evaluated skipped C import test\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + + let selected = run_glagol_os([ + "--run-tests".as_ref(), + "--filter".as_ref(), + "ffi".as_ref(), + path.as_os_str(), + ]); + let selected_stderr = String::from_utf8_lossy(&selected.stderr); + assert!( + !selected.status.success() && selected_stderr.contains("(code UnsupportedTestExpression)"), + "selected C import test did not keep unsupported execution diagnostic\nstderr:\n{}", + selected_stderr + ); +} + +#[test] +fn unit_return_c_import_is_supported_and_release_gated() { + let source = "(module main)\n\n(import_c c_log ((value i32)) -> unit)\n\n(fn main () -> i32\n (unsafe\n (c_log 7))\n 0)\n"; + + let llvm = run_glagol_source("c-ffi-unit-return-llvm", source, &[]); + let stdout = String::from_utf8_lossy(&llvm.stdout); + assert!( + llvm.status.success() + && stdout.contains("declare void @c_log(i32)") + && stdout.contains("call void @c_log(i32 7)") + && stdout.contains("define i32 @main()"), + "unit C import did not lower to void declaration/call\nstdout:\n{}\nstderr:\n{}", + stdout, + String::from_utf8_lossy(&llvm.stderr) + ); + + let formatted = run_glagol_source("c-ffi-unit-return-fmt", source, &["--format"]); + let formatted_stdout = String::from_utf8_lossy(&formatted.stdout); + assert!( + formatted.status.success() + && formatted_stdout.contains("(import_c c_log ((value i32)) -> unit)") + && formatted_stdout.contains("(c_log 7)"), + "formatter did not preserve unit C import\nstdout:\n{}\nstderr:\n{}", + formatted_stdout, + String::from_utf8_lossy(&formatted.stderr) + ); + + let source_path = temp_path("c-ffi-unit-return-manifest", "input.slo"); + let manifest = temp_path("c-ffi-unit-return-manifest", "manifest.slo"); + fs::write(&source_path, source) + .unwrap_or_else(|err| panic!("write `{}`: {}", source_path.display(), err)); + let manifest_output = run_glagol_os([ + "check".as_ref(), + source_path.as_os_str(), + "--manifest".as_ref(), + manifest.as_os_str(), + ]); + assert_success("unit C FFI check manifest", &manifest_output); + let manifest_text = fs::read_to_string(&manifest).expect("read artifact manifest"); + assert!( + manifest_text.contains("(foreign_import") + && manifest_text.contains("(source_module \"main\")") + && manifest_text.contains("(name \"c_log\")") + && manifest_text.contains("(return \"unit\")"), + "unit C import manifest did not record source module and return metadata\n{}", + manifest_text + ); + + let test_source = "(module main)\n\n(import_c c_log ((value i32)) -> unit)\n\n(fn main () -> i32\n 0)\n\n(test \"unit C import test runner diagnostic\"\n (var i i32 0)\n (while (< i 1)\n (unsafe\n (c_log i))\n (set i (+ i 1)))\n (= i 1))\n"; + let test_path = temp_path("c-ffi-unit-return-test-runner", "input.slo"); + fs::write(&test_path, test_source) + .unwrap_or_else(|err| panic!("write `{}`: {}", test_path.display(), err)); + let test_output = run_glagol_os(["--run-tests".as_ref(), test_path.as_os_str()]); + let test_stderr = String::from_utf8_lossy(&test_output.stderr); + assert!( + !test_output.status.success() && test_stderr.contains("(code UnsupportedTestExpression)"), + "test runner did not reject reached unit C import execution\nstderr:\n{}", + test_stderr + ); +} + +#[test] +fn single_file_manifest_records_foreign_import_metadata() { + let manifest = temp_path("c-ffi-single-file-manifest", "manifest.slo"); + let output = run_glagol_os([ + "check".as_ref(), + "../examples/ffi/exp-6-c-add/main.slo".as_ref(), + "--manifest".as_ref(), + manifest.as_os_str(), + ]); + assert_success("single-file C FFI check manifest", &output); + + let manifest_text = fs::read_to_string(&manifest).expect("read artifact manifest"); + assert!( + manifest_text.contains("(foreign_import") + && manifest_text.contains("(name \"c_add\")") + && manifest_text.contains("(source_module \"main\")") + && manifest_text.contains("(symbol \"c_add\")") + && manifest_text.contains("(abi \"experimental-fixture-c\")"), + "single-file manifest did not record C import metadata\n{}", + manifest_text + ); +} + +#[test] +fn project_mode_emits_foreign_import_and_manifest_metadata() { + let fixture = "../examples/ffi/exp-6-c-add"; + let manifest = temp_path("c-ffi-project-manifest", "manifest.slo"); + let binary = temp_path("c-ffi-project-manifest", "bin"); + let output = run_glagol_os([ + "build".as_ref(), + fixture.as_ref(), + "-o".as_ref(), + binary.as_os_str(), + "--link-c".as_ref(), + "../examples/ffi/exp-6-c-add/c_add.c".as_ref(), + "--manifest".as_ref(), + manifest.as_os_str(), + ]); + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + if stderr.contains("ToolchainUnavailable") { + eprintln!("skipping project manifest assertion: clang is unavailable"); + return; + } + panic!( + "project build failed\nstdout:\n{}\nstderr:\n{}", + String::from_utf8_lossy(&output.stdout), + stderr + ); + } + let manifest_text = fs::read_to_string(&manifest).expect("read artifact manifest"); + assert!( + manifest_text.contains("(foreign_import") + && manifest_text.contains("(name \"c_add\")") + && manifest_text.contains("(source_module \"main\")") + && manifest_text.contains("(symbol \"c_add\")") + && manifest_text.contains("(input \"../examples/ffi/exp-6-c-add/c_add.c\")"), + "manifest did not record C import/link input\n{}", + manifest_text + ); +} + +#[test] +fn hosted_build_with_link_c_runs_when_clang_is_available() { + let Some(clang) = find_clang() else { + eprintln!("skipping C FFI hosted build: set GLAGOL_CLANG or install clang"); + return; + }; + + let binary = temp_path("c-ffi-hosted", "bin"); + let mut build_command = Command::new(env!("CARGO_BIN_EXE_glagol")); + build_command + .args([ + "build".as_ref(), + "../examples/ffi/exp-6-c-add".as_ref(), + "-o".as_ref(), + binary.as_os_str(), + "--link-c".as_ref(), + "../examples/ffi/exp-6-c-add/c_add.c".as_ref(), + ]) + .env("GLAGOL_CLANG", &clang); + configure_clang_runtime_env(&mut build_command, &clang); + let build = build_command + .output() + .expect("run glagol hosted C FFI build"); + assert_success("hosted C FFI build", &build); + + let run = Command::new(&binary) + .output() + .expect("run hosted C FFI binary"); + assert!( + run.status.code() == Some(42), + "hosted C FFI binary returned unexpected status\nstdout:\n{}\nstderr:\n{:?}", + String::from_utf8_lossy(&run.stdout), + String::from_utf8_lossy(&run.stderr) + ); +} + +fn run_glagol(args: [&str; N]) -> std::process::Output { + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .output() + .expect("run glagol") +} + +fn run_glagol_os(args: [&std::ffi::OsStr; N]) -> std::process::Output { + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .output() + .expect("run glagol") +} + +fn run_glagol_source(name: &str, source: &str, extra_args: &[&str]) -> std::process::Output { + let path = temp_path(name, "input.slo"); + fs::write(&path, source).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err)); + let mut command = Command::new(env!("CARGO_BIN_EXE_glagol")); + command.arg(&path); + command.args(extra_args); + command + .output() + .unwrap_or_else(|err| panic!("run glagol on `{}`: {}", path.display(), err)) +} + +fn assert_diagnostic(output: &std::process::Output, code: &str) { + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + !output.status.success() && stderr.contains(&format!("(code {})", code)), + "expected diagnostic `{}`\nstdout:\n{}\nstderr:\n{}", + code, + String::from_utf8_lossy(&output.stdout), + stderr + ); +} + +fn assert_success(context: &str, output: &std::process::Output) { + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); +} + +fn temp_path(name: &str, file: &str) -> PathBuf { + let dir = std::env::temp_dir().join(format!("glagol-{}-{}", name, std::process::id())); + fs::create_dir_all(&dir).unwrap_or_else(|err| panic!("create `{}`: {}", dir.display(), err)); + dir.join(file) +} + +fn find_clang() -> Option { + if let Ok(path) = std::env::var("GLAGOL_CLANG") { + let path = PathBuf::from(path); + if path.is_file() { + return Some(path); + } + } + let hermetic_clang = PathBuf::from("/tmp/glagol-clang-root/usr/bin/clang"); + if hermetic_clang.is_file() { + return Some(hermetic_clang); + } + find_on_path("clang") +} + +fn find_on_path(name: &str) -> Option { + let path = env::var_os("PATH")?; + env::split_paths(&path) + .map(|dir| dir.join(name)) + .find(|candidate| candidate.is_file()) +} + +fn configure_clang_runtime_env(command: &mut Command, clang: &Path) { + if !clang.starts_with("/tmp/glagol-clang-root") { + return; + } + + let root = Path::new("/tmp/glagol-clang-root"); + let lib64 = root.join("usr/lib64"); + let lib = root.join("usr/lib"); + let existing = env::var_os("LD_LIBRARY_PATH").unwrap_or_default(); + let mut paths = vec![lib64, lib]; + paths.extend(env::split_paths(&existing)); + let joined = env::join_paths(paths).expect("join LD_LIBRARY_PATH"); + command.env("LD_LIBRARY_PATH", joined); +} diff --git a/compiler/tests/checked_i64_to_i32_conversion_alpha.rs b/compiler/tests/checked_i64_to_i32_conversion_alpha.rs new file mode 100644 index 0000000..46ca701 --- /dev/null +++ b/compiler/tests/checked_i64_to_i32_conversion_alpha.rs @@ -0,0 +1,183 @@ +use std::{ + env, + ffi::OsStr, + fs, + path::{Path, PathBuf}, + process::{Command, Output}, + sync::atomic::{AtomicUsize, Ordering}, +}; + +static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0); + +#[test] +fn checked_i64_to_i32_fixture_lowers_and_runs_tests() { + let fixture = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/checked-i64-to-i32-conversion.slo"); + + let llvm = run_glagol([fixture.as_os_str()]); + assert_success("compile checked i64-to-i32 fixture", &llvm); + let stdout = String::from_utf8_lossy(&llvm.stdout); + assert!( + stdout.contains("define { i1, i32 } @narrow(i64 %value)") + && stdout.contains("icmp sge i64 %value, -2147483648") + && stdout.contains("icmp sle i64 %value, 2147483647") + && stdout.contains("trunc i64 %value to i32") + && stdout.contains("select i1 %") + && stdout.contains("i32 1") + && stdout.contains("insertvalue { i1, i32 }") + && !stdout.contains("@std.num.") + && !stdout.contains("__glagol_i64_to_i32"), + "checked i64-to-i32 LLVM shape drifted\nstdout:\n{}", + stdout + ); + + let tests = run_glagol([OsStr::new("test"), fixture.as_os_str()]); + assert_success("run checked i64-to-i32 fixture tests", &tests); + assert_eq!( + String::from_utf8_lossy(&tests.stdout), + concat!( + "test \"i64 low bound narrows to i32\" ... ok\n", + "test \"i64 high bound narrows to i32\" ... ok\n", + "test \"negative i64 narrows to i32\" ... ok\n", + "test \"below i32 range returns err\" ... ok\n", + "test \"above i32 range returns err\" ... ok\n", + "5 test(s) passed\n", + ), + "checked i64-to-i32 test runner stdout drifted" + ); +} + +#[test] +fn checked_i64_to_i32_formatter_and_lowering_are_visible() { + let fixture = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/checked-i64-to-i32-conversion.slo"); + + let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]); + assert_success("format checked i64-to-i32 fixture", &formatted); + let formatted_stdout = String::from_utf8_lossy(&formatted.stdout); + assert!( + formatted_stdout.contains("(std.num.i64_to_i32_result value)") + && formatted_stdout.contains("(narrow -2147483648i64)") + && formatted_stdout.contains("(std.result.unwrap_err value)") + && formatted_stdout.contains("(above_high_err)"), + "checked i64-to-i32 formatter output omitted expected calls\nstdout:\n{}", + formatted_stdout + ); + + let surface = run_glagol([ + OsStr::new("--inspect-lowering=surface"), + fixture.as_os_str(), + ]); + assert_success("inspect checked i64-to-i32 surface lowering", &surface); + assert_eq!( + String::from_utf8_lossy(&surface.stdout), + fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../tests/checked-i64-to-i32-conversion.surface.lower") + ) + .expect("read checked i64-to-i32 surface snapshot"), + "checked i64-to-i32 surface lowering snapshot drifted" + ); + + let checked = run_glagol([ + OsStr::new("--inspect-lowering=checked"), + fixture.as_os_str(), + ]); + assert_success("inspect checked i64-to-i32 checked lowering", &checked); + assert_eq!( + String::from_utf8_lossy(&checked.stdout), + fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../tests/checked-i64-to-i32-conversion.checked.lower") + ) + .expect("read checked i64-to-i32 checked snapshot"), + "checked i64-to-i32 checked lowering snapshot drifted" + ); +} + +#[test] +fn checked_i64_to_i32_rejections_are_explicit() { + for (name, source, expected) in [ + ( + "arity", + "(module main)\n\n(fn main () -> (result i32 i32)\n (std.num.i64_to_i32_result))\n", + "wrong number of arguments", + ), + ( + "type", + "(module main)\n\n(fn main () -> (result i32 i32)\n (std.num.i64_to_i32_result 1))\n", + "cannot call `std.num.i64_to_i32_result` with argument of wrong type", + ), + ( + "i32-to-i64-result", + "(module main)\n\n(fn main () -> i32\n (std.num.i32_to_i64_result 1)\n 0)\n", + "standard library call `std.num.i32_to_i64_result` is not supported", + ), + ( + "cast-checked", + "(module main)\n\n(fn main () -> i32\n (std.num.cast_checked 1)\n 0)\n", + "standard library call `std.num.cast_checked` is not supported", + ), + ] { + let fixture = write_fixture(name, source); + let output = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + !output.status.success(), + "compiler unexpectedly accepted checked i64-to-i32 rejection fixture `{}`\nstdout:\n{}\nstderr:\n{}", + name, + stdout, + stderr + ); + assert!( + stdout.is_empty(), + "rejected compile wrote stdout for `{}`:\n{}", + name, + stdout + ); + assert!( + stderr.contains(expected), + "checked i64-to-i32 diagnostic drifted for `{}`\nstderr:\n{}", + name, + stderr + ); + } +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn write_fixture(name: &str, source: &str) -> PathBuf { + let mut path = env::temp_dir(); + path.push(format!( + "glagol-checked-i64-to-i32-alpha-{}-{}-{}.slo", + name, + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + fs::write(&path, source).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err)); + path +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); +} diff --git a/compiler/tests/cli_v1_1.rs b/compiler/tests/cli_v1_1.rs new file mode 100644 index 0000000..5005640 --- /dev/null +++ b/compiler/tests/cli_v1_1.rs @@ -0,0 +1,784 @@ +use std::{ + fs, + path::{Path, PathBuf}, + process::{Command, Output}, + sync::atomic::{AtomicUsize, Ordering}, +}; + +static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0); + +#[test] +fn check_subcommand_accepts_supported_source_without_primary_output() { + let fixture = write_fixture( + "check", + r#" +(module main) + +(fn main () -> i32 + 0) +"#, + ); + + let output = run_glagol(["check".as_ref(), fixture.as_os_str()]); + + assert_success_stdout("check", output, ""); +} + +#[test] +fn check_subcommand_rejects_backend_boundary_without_primary_output() { + let fixture = write_fixture( + "check-backend-gap", + r#" +(module main) + +(fn id ((value (ptr i32))) -> i32 + 0) +"#, + ); + + let output = run_glagol(["check".as_ref(), fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert_exit_code("check backend boundary", &output, 1); + assert!( + stdout.is_empty(), + "failed check wrote primary stdout:\n{}", + stdout + ); + assert!( + stderr.contains("UnsupportedBackendFeature"), + "backend boundary diagnostic mismatch:\n{}", + stderr + ); +} + +#[test] +fn fmt_subcommand_matches_legacy_format_flag() { + let fixture = write_fixture( + "fmt-alias", + r#" +(module main) + +(fn main () -> i32 0) +"#, + ); + + let legacy = run_glagol(["--format".as_ref(), fixture.as_os_str()]); + let alias = run_glagol(["fmt".as_ref(), fixture.as_os_str()]); + + assert_success("legacy --format", &legacy); + assert_success("fmt", &alias); + assert_eq!(alias.stdout, legacy.stdout, "fmt alias output mismatch"); +} + +#[test] +fn test_subcommand_matches_legacy_run_tests_flag() { + let fixture = write_fixture( + "test-alias", + r#" +(module main) + +(test "true" + true) +"#, + ); + + let legacy = run_glagol(["--run-tests".as_ref(), fixture.as_os_str()]); + let alias = run_glagol(["test".as_ref(), fixture.as_os_str()]); + + assert_success("legacy --run-tests", &legacy); + assert_success("test", &alias); + assert_eq!(alias.stdout, legacy.stdout, "test alias output mismatch"); +} + +#[test] +fn json_diagnostics_emit_one_object_per_line_without_human_preamble() { + let fixture = write_fixture( + "json-type-mismatch", + r#" +(module main) + +(fn id ((value i32)) -> i32 + value) + +(fn main () -> i32 + (id true)) +"#, + ); + + let output = run_glagol(["--json-diagnostics".as_ref(), fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert_exit_code("json diagnostics", &output, 1); + assert!( + stdout.is_empty(), + "json diagnostic failure wrote stdout:\n{}", + stdout + ); + assert!( + stderr + .lines() + .all(|line| line.starts_with('{') && line.ends_with('}')), + "stderr contained non-JSON diagnostic text:\n{}", + stderr + ); + assert!( + stderr.contains(r#""schema":"slovo.diagnostic""#) + && stderr.contains(r#""version":1"#) + && stderr.contains(r#""code":"TypeMismatch""#) + && stderr.contains(r#""file":"#) + && stderr.contains(r#""span":{"byte_start":"#), + "json diagnostic did not contain expected fields:\n{}", + stderr + ); +} + +#[test] +fn json_diagnostics_cover_parse_error() { + let fixture = write_fixture("json-parse", "(module main"); + + let output = run_glagol(["--json-diagnostics".as_ref(), fixture.as_os_str()]); + + assert_exit_code("json parse error", &output, 1); + assert_json_diagnostic("json parse error", &output, "UnclosedList"); +} + +#[test] +fn json_diagnostics_cover_lowering_error() { + let fixture = write_fixture( + "json-lowering", + r#" +(module main) + +(bogus top level) +"#, + ); + + let output = run_glagol(["--json-diagnostics".as_ref(), fixture.as_os_str()]); + + assert_exit_code("json lowering error", &output, 1); + assert_json_diagnostic("json lowering error", &output, "UnknownTopLevelForm"); +} + +#[test] +fn json_diagnostics_cover_formatter_error() { + let fixture = write_fixture( + "json-formatter", + r#" +(module main) ; trailing comments are outside the formatter subset + +(fn main () -> i32 + 0) +"#, + ); + + let output = run_glagol([ + "--json-diagnostics".as_ref(), + "fmt".as_ref(), + fixture.as_os_str(), + ]); + + assert_exit_code("json formatter error", &output, 1); + assert_json_diagnostic( + "json formatter error", + &output, + "UnsupportedFormatterComment", + ); +} + +#[test] +fn json_diagnostics_cover_failed_test() { + let fixture = write_fixture( + "json-failed-test", + r#" +(module main) + +(test "false" + false) +"#, + ); + + let output = run_glagol([ + "--json-diagnostics".as_ref(), + "test".as_ref(), + fixture.as_os_str(), + ]); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert_exit_code("json failed test", &output, 1); + assert_json_diagnostic("json failed test", &output, "TestFailed"); + assert!( + stderr.contains(r#""expected":"true""#) && stderr.contains(r#""found":"false""#), + "failed-test JSON diagnostic did not include expected/found:\n{}", + stderr + ); +} + +#[test] +fn json_diagnostics_cover_backend_boundary_through_check() { + let fixture = write_fixture( + "json-check-backend-gap", + r#" +(module main) + +(fn id ((value (ptr i32))) -> i32 + 0) +"#, + ); + + let output = run_glagol([ + "--json-diagnostics".as_ref(), + "check".as_ref(), + fixture.as_os_str(), + ]); + + assert_exit_code("json check backend boundary", &output, 1); + assert_json_diagnostic( + "json check backend boundary", + &output, + "UnsupportedBackendFeature", + ); +} + +#[test] +fn json_diagnostics_cover_usage_error_with_manifest() { + let manifest_path = temp_path("json-usage", "manifest.slo"); + + let output = run_glagol([ + "--json-diagnostics".as_ref(), + "--manifest".as_ref(), + manifest_path.as_os_str(), + ]); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert_exit_code("json usage error", &output, 2); + assert_json_diagnostic("json usage error", &output, "UsageError"); + assert!( + stderr.contains(r#""file":null"#) && stderr.contains(r#""span":null"#), + "usage JSON diagnostic should not claim a source span:\n{}", + stderr + ); + + let manifest = read_manifest(&manifest_path); + assert!( + manifest.contains(" (mode usage-error)\n") + && manifest.contains(" (success false)\n") + && manifest.contains(" (diagnostics-encoding json)\n") + && manifest.contains("UsageError"), + "usage JSON manifest mismatch:\n{}", + manifest + ); +} + +#[test] +fn json_diagnostics_cover_hosted_build_missing_clang() { + let fixture = write_fixture( + "json-missing-clang", + r#" +(module main) + +(fn main () -> i32 + 0) +"#, + ); + let output_path = temp_path("json-missing-clang", "bin"); + let manifest_path = temp_path("json-missing-clang", "manifest.slo"); + let missing_clang = temp_path("json-missing-clang", "not-a-clang"); + + let output = Command::new(compiler_path()) + .arg("--json-diagnostics") + .arg("build") + .arg(&fixture) + .arg("-o") + .arg(&output_path) + .arg("--manifest") + .arg(&manifest_path) + .env("GLAGOL_CLANG", &missing_clang) + .output() + .unwrap_or_else(|err| panic!("run glagol build: {}", err)); + + assert_exit_code("json missing clang", &output, 3); + assert_json_diagnostic("json missing clang", &output, "ToolchainUnavailable"); + let manifest = read_manifest(&manifest_path); + assert!( + manifest.contains(" (mode build)\n") + && manifest.contains(" (success false)\n") + && manifest.contains(" (diagnostics-encoding json)\n") + && manifest.contains("ToolchainUnavailable"), + "missing clang JSON manifest mismatch:\n{}", + manifest + ); +} + +#[test] +fn manifest_records_successful_check_fmt_and_test_v1_1_commands() { + let source = r#" +(module main) + +(fn main () -> i32 + 0) +"#; + let fixture = write_fixture("manifest-success-commands", source); + + let check_manifest = temp_path("manifest-check-success", "manifest.slo"); + let check = run_glagol([ + "check".as_ref(), + "--manifest".as_ref(), + check_manifest.as_os_str(), + fixture.as_os_str(), + ]); + assert_success_stdout("manifest check success", check, ""); + let manifest = read_manifest(&check_manifest); + assert!( + manifest.contains(" (mode check)\n") + && manifest.contains(" (success true)\n") + && manifest.contains(" (kind no-output)\n") + && !manifest.contains(" (stdout "), + "check success manifest mismatch:\n{}", + manifest + ); + + let fmt_manifest = temp_path("manifest-fmt-success", "manifest.slo"); + let fmt = run_glagol([ + "fmt".as_ref(), + "--manifest".as_ref(), + fmt_manifest.as_os_str(), + fixture.as_os_str(), + ]); + assert_success("manifest fmt success", &fmt); + let manifest = read_manifest(&fmt_manifest); + assert!( + manifest.contains(" (mode format)\n") + && manifest.contains(" (success true)\n") + && manifest.contains(" (kind formatted-source)\n") + && manifest.contains("(fn main () -> i32"), + "fmt success manifest mismatch:\n{}", + manifest + ); + + let test_fixture = write_fixture( + "manifest-test-success", + r#" +(module main) + +(test "true" + true) +"#, + ); + let test_manifest = temp_path("manifest-test-success", "manifest.slo"); + let test = run_glagol([ + "test".as_ref(), + "--manifest".as_ref(), + test_manifest.as_os_str(), + test_fixture.as_os_str(), + ]); + assert_success("manifest test success", &test); + let manifest = read_manifest(&test_manifest); + assert_manifest_test_report(&manifest, 1, 1, 0, 0); + assert!( + manifest.contains(" (mode test)\n") && manifest.contains(" (success true)\n"), + "test success manifest mismatch:\n{}", + manifest + ); +} + +#[test] +fn manifest_records_failed_test_counts_after_execution_begins() { + let fixture = write_fixture( + "manifest-failed-test", + r#" +(module main) + +(test "passes" + true) + +(test "fails" + false) +"#, + ); + let manifest_path = temp_path("manifest-failed-test", "manifest.slo"); + + let output = run_glagol([ + "test".as_ref(), + "--manifest".as_ref(), + manifest_path.as_os_str(), + fixture.as_os_str(), + ]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert_exit_code("manifest failed test", &output, 1); + assert!( + stdout.is_empty(), + "failed test invocation wrote primary stdout:\n{}", + stdout + ); + assert!( + stderr.contains("TestFailed"), + "failed test diagnostic mismatch:\n{}", + stderr + ); + + let manifest = read_manifest(&manifest_path); + assert_manifest_test_report(&manifest, 2, 1, 1, 0); + assert!( + manifest.contains(" (mode test)\n") + && manifest.contains(" (success false)\n") + && manifest.contains("TestFailed"), + "failed test manifest mismatch:\n{}", + manifest + ); +} + +#[test] +fn manifest_records_source_diagnostic_failure() { + let fixture = write_fixture( + "manifest-source-failure", + r#" +(module main) + +(fn id ((value i32)) -> i32 + value) + +(fn main () -> i32 + (id true)) +"#, + ); + let manifest_path = temp_path("manifest-source-failure", "manifest.slo"); + + let output = run_glagol([ + "check".as_ref(), + "--manifest".as_ref(), + manifest_path.as_os_str(), + fixture.as_os_str(), + ]); + + assert_exit_code("manifest source failure", &output, 1); + let manifest = read_manifest(&manifest_path); + assert!( + manifest.contains(" (mode check)\n") + && manifest.contains(" (success false)\n") + && manifest.contains(" (kind diagnostics)\n") + && manifest.contains("TypeMismatch"), + "source diagnostic manifest mismatch:\n{}", + manifest + ); +} + +#[test] +fn usage_failure_writes_manifest_when_manifest_path_was_parsed() { + let fixture = write_fixture( + "usage-manifest", + r#" +(module main) + +(fn main () -> i32 + 0) +"#, + ); + let manifest_path = temp_path("usage-manifest", "manifest.slo"); + + let output = run_glagol([ + "build".as_ref(), + "--manifest".as_ref(), + manifest_path.as_os_str(), + fixture.as_os_str(), + ]); + + assert_exit_code("build missing output", &output, 2); + let manifest = read_manifest(&manifest_path); + assert!( + manifest.contains(" (success false)\n") + && manifest.contains(" (mode usage-error)\n") + && manifest.contains("`build` requires `-o `"), + "usage failure manifest mismatch:\n{}", + manifest + ); +} + +#[test] +fn build_reports_missing_clang_as_toolchain_failure_with_manifest() { + let fixture = write_fixture( + "missing-clang", + r#" +(module main) + +(fn main () -> i32 + 0) +"#, + ); + let output_path = temp_path("missing-clang", "bin"); + let manifest_path = temp_path("missing-clang", "manifest.slo"); + let missing_clang = temp_path("missing-clang", "not-a-clang"); + + let output = Command::new(compiler_path()) + .arg("build") + .arg(&fixture) + .arg("-o") + .arg(&output_path) + .arg("--manifest") + .arg(&manifest_path) + .env("GLAGOL_CLANG", &missing_clang) + .output() + .unwrap_or_else(|err| panic!("run glagol build: {}", err)); + + assert_exit_code("missing clang", &output, 3); + assert!( + !output_path.exists(), + "build left output behind after missing clang" + ); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + stderr.contains("cannot find Clang executable"), + "missing clang diagnostic mismatch:\n{}", + stderr + ); + let manifest = read_manifest(&manifest_path); + assert!( + manifest.contains(" (mode build)\n") + && manifest.contains(" (success false)\n") + && manifest.contains("ToolchainUnavailable"), + "missing clang manifest mismatch:\n{}", + manifest + ); +} + +#[test] +#[ignore = "requires hosted clang and system linker"] +fn hosted_build_smoke_builds_and_runs_promoted_v1_1_examples() { + let Some(clang) = find_clang() else { + eprintln!("skipping hosted build smoke: set GLAGOL_CLANG or install clang"); + return; + }; + let manifest = Path::new(env!("CARGO_MANIFEST_DIR")); + let cases = [ + ("add", "add.slo", Some(0), "42\n"), + ("while", "while.slo", Some(4), ""), + ("struct-value-flow", "struct-value-flow.slo", Some(42), ""), + ("array-value-flow", "array-value-flow.slo", Some(11), ""), + ( + "option-result-payload", + "option-result-payload.slo", + Some(0), + "", + ), + ( + "option-result-match", + "option-result-match.slo", + Some(0), + "", + ), + ( + "string-print", + "string-print.slo", + Some(0), + "hello\nline\nquote\"slash\\tab\t\n", + ), + ]; + + for (name, fixture_name, expected_status, expected_stdout) in cases { + let fixture = manifest.join("../examples").join(fixture_name); + let output_path = temp_path(&format!("hosted-build-{}", name), "bin"); + let manifest_path = temp_path(&format!("hosted-build-{}", name), "manifest.slo"); + + let mut build_command = Command::new(compiler_path()); + build_command + .arg("build") + .arg(&fixture) + .arg("-o") + .arg(&output_path) + .arg("--manifest") + .arg(&manifest_path) + .env("GLAGOL_CLANG", &clang); + configure_clang_runtime_env(&mut build_command, &clang); + let build = build_command + .output() + .unwrap_or_else(|err| panic!("run glagol build for `{}`: {}", name, err)); + assert_success(&format!("hosted build {}", name), &build); + let build_manifest = read_manifest(&manifest_path); + assert!( + build_manifest.contains(" (mode build)\n") + && build_manifest.contains(" (success true)\n") + && build_manifest.contains(" (kind native-executable)\n") + && build_manifest.contains(" (hosted-build\n"), + "hosted build manifest mismatch for `{}`:\n{}", + name, + build_manifest + ); + + let run = Command::new(&output_path) + .output() + .unwrap_or_else(|err| panic!("run `{}`: {}", output_path.display(), err)); + assert_eq!( + run.status.code(), + expected_status, + "{} exit status mismatch\nstdout:\n{}\nstderr:\n{}", + name, + String::from_utf8_lossy(&run.stdout), + String::from_utf8_lossy(&run.stderr) + ); + assert_eq!( + String::from_utf8_lossy(&run.stdout), + expected_stdout, + "{} stdout mismatch", + name + ); + } +} + +fn run_glagol(args: [&std::ffi::OsStr; N]) -> Output { + Command::new(compiler_path()) + .args(args) + .output() + .unwrap_or_else(|err| panic!("run glagol: {}", err)) +} + +fn compiler_path() -> &'static str { + env!("CARGO_BIN_EXE_glagol") +} + +fn write_fixture(name: &str, source: &str) -> PathBuf { + let id = NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed); + let path = temp_path(&format!("{}-{}", name, id), "slo"); + fs::write(&path, source).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err)); + path +} + +fn temp_path(name: &str, extension: &str) -> PathBuf { + let mut path = std::env::temp_dir(); + path.push(format!( + "glagol-v1-1-{}-{}.{}", + std::process::id(), + name, + extension + )); + path +} + +fn read_manifest(path: &Path) -> String { + fs::read_to_string(path).unwrap_or_else(|err| panic!("read `{}`: {}", path.display(), err)) +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} + +fn assert_success_stdout(context: &str, output: Output, expected: &str) { + assert_success(context, &output); + let stdout = String::from_utf8(output.stdout).expect("stdout is UTF-8"); + assert_eq!(stdout, expected, "{} stdout mismatch", context); +} + +fn assert_exit_code(context: &str, output: &Output, expected: i32) { + assert_eq!( + output.status.code(), + Some(expected), + "{} exit code mismatch\nstdout:\n{}\nstderr:\n{}", + context, + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); +} + +fn assert_json_diagnostic(context: &str, output: &Output, expected_code: &str) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + stdout.is_empty(), + "{} wrote stdout on diagnostic failure:\n{}", + context, + stdout + ); + assert!( + !stderr.trim().is_empty(), + "{} did not write a JSON diagnostic", + context + ); + assert!( + stderr + .lines() + .all(|line| line.starts_with('{') && line.ends_with('}')), + "{} stderr contained non-JSON diagnostic text:\n{}", + context, + stderr + ); + assert!( + stderr.contains(r#""schema":"slovo.diagnostic""#) + && stderr.contains(r#""version":1"#) + && stderr.contains(&format!(r#""code":"{}""#, expected_code)) + && stderr.contains(r#""severity":"error""#) + && stderr.contains(r#""file":"#) + && stderr.contains(r#""span":"#), + "{} JSON diagnostic did not contain expected fields:\n{}", + context, + stderr + ); +} + +fn assert_manifest_test_report( + manifest: &str, + total: usize, + passed: usize, + failed: usize, + skipped: usize, +) { + assert!( + manifest.contains(" (test-report\n") + && manifest.contains(&format!(" (total {})\n", total)) + && manifest.contains(&format!(" (passed {})\n", passed)) + && manifest.contains(&format!(" (failed {})\n", failed)) + && manifest.contains(&format!(" (skipped {})\n", skipped)), + "manifest test report mismatch:\n{}", + manifest + ); +} + +fn find_clang() -> Option { + if let Some(path) = std::env::var_os("GLAGOL_CLANG").filter(|value| !value.is_empty()) { + return Some(PathBuf::from(path)); + } + + let hermetic_clang = PathBuf::from("/tmp/glagol-clang-root/usr/bin/clang"); + if hermetic_clang.is_file() { + return Some(hermetic_clang); + } + + find_on_path("clang") +} + +fn find_on_path(program: &str) -> Option { + let path = std::env::var_os("PATH")?; + std::env::split_paths(&path) + .map(|dir| dir.join(program)) + .find(|candidate| candidate.is_file()) +} + +fn configure_clang_runtime_env(command: &mut Command, clang: &Path) { + if !clang.starts_with("/tmp/glagol-clang-root") { + return; + } + + let root = Path::new("/tmp/glagol-clang-root"); + let lib64 = root.join("usr/lib64"); + let lib = root.join("usr/lib"); + let mut paths = vec![lib64, lib]; + + if let Some(existing) = std::env::var_os("LD_LIBRARY_PATH") { + paths.extend(std::env::split_paths(&existing)); + } + + let joined = std::env::join_paths(paths).expect("join LD_LIBRARY_PATH"); + command.env("LD_LIBRARY_PATH", joined); +} diff --git a/compiler/tests/collections_alpha.rs b/compiler/tests/collections_alpha.rs new file mode 100644 index 0000000..9e2fdf4 --- /dev/null +++ b/compiler/tests/collections_alpha.rs @@ -0,0 +1,1941 @@ +use std::{ + env, + ffi::OsStr, + fs, + path::{Path, PathBuf}, + process::{Command, Output}, + sync::atomic::{AtomicUsize, Ordering}, +}; + +static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0); + +#[test] +fn vec_i32_fixture_emits_runtime_owned_vector_calls_and_tests_pass() { + let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/vec-i32.slo"); + let compile = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + + assert!( + compile.status.success(), + "compiler rejected vec-i32 fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("declare ptr @__glagol_vec_i32_empty()") + && stdout.contains("declare ptr @__glagol_vec_i32_append(ptr, i32)") + && stdout.contains("declare i32 @__glagol_vec_i32_len(ptr)") + && stdout.contains("declare i32 @__glagol_vec_i32_index(ptr, i32)") + && stdout.contains("declare i1 @__glagol_vec_i32_eq(ptr, ptr)") + && stdout.contains("define ptr @empty_values()") + && stdout.contains("define ptr @pair(i32 %base)") + && stdout.contains("define ptr @echo(ptr %values)") + && stdout.contains("define i32 @at(ptr %values, i32 %i)") + && stdout.contains("call ptr @__glagol_vec_i32_empty()") + && stdout.contains("call ptr @__glagol_vec_i32_append(ptr %") + && stdout.contains("call i32 @__glagol_vec_i32_len(ptr %") + && stdout.contains("call i32 @__glagol_vec_i32_index(ptr %") + && !stdout.contains("@std."), + "LLVM output did not contain expected vec runtime shape\nstdout:\n{}", + stdout + ); + assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr); + + let run = run_glagol([OsStr::new("--run-tests"), fixture.as_os_str()]); + assert_success_stdout( + run, + concat!( + "test \"vec i32 empty length\" ... ok\n", + "test \"vec i32 append length\" ... ok\n", + "test \"vec i32 index\" ... ok\n", + "test \"vec i32 append is immutable\" ... ok\n", + "test \"vec i32 equality\" ... ok\n", + "5 test(s) passed\n", + ), + "vec-i32 test runner output", + ); +} + +#[test] +fn vec_i32_equality_lowers_to_runtime_helper() { + let fixture = write_fixture( + "vec-i32-equality", + r#" +(module main) + +(fn same () -> i32 + (if (= (std.vec.i32.append (std.vec.i32.empty) 1) + (std.vec.i32.append (std.vec.i32.empty) 1)) + 1 + 0)) + +(fn main () -> i32 + (same)) +"#, + ); + let compile = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + + assert!( + compile.status.success(), + "compiler rejected vec equality fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("call i1 @__glagol_vec_i32_eq(ptr %"), + "LLVM output did not lower vec equality through helper\nstdout:\n{}", + stdout + ); +} + +#[test] +fn vec_i32_formatter_and_lowering_inspector_are_visible() { + let fixture = write_fixture( + "vec-i32-tooling", + r#" +(module main) + +(fn make () -> (vec i32) + (let values (vec i32) (std.vec.i32.empty)) + (std.vec.i32.append values 1)) + +(fn main () -> i32 + (std.vec.i32.len (make))) +"#, + ); + + let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]); + assert_success_stdout( + formatted, + concat!( + "(module main)\n", + "\n", + "(fn make () -> (vec i32)\n", + " (let values (vec i32) (std.vec.i32.empty))\n", + " (std.vec.i32.append values 1))\n", + "\n", + "(fn main () -> i32\n", + " (std.vec.i32.len (make)))\n", + ), + "vec-i32 formatter output", + ); + + let surface = run_glagol([ + OsStr::new("--inspect-lowering=surface"), + fixture.as_os_str(), + ]); + assert_success("inspect vec-i32 surface lowering", &surface); + assert!( + String::from_utf8_lossy(&surface.stdout).contains("fn make() -> (vec i32)"), + "surface lowering did not show vec return type\nstdout:\n{}", + String::from_utf8_lossy(&surface.stdout) + ); + + let checked = run_glagol([ + OsStr::new("--inspect-lowering=checked"), + fixture.as_os_str(), + ]); + assert_success("inspect vec-i32 checked lowering", &checked); + assert!( + String::from_utf8_lossy(&checked.stdout).contains("call std.vec.i32.append : (vec i32)"), + "checked lowering did not show typed vec append\nstdout:\n{}", + String::from_utf8_lossy(&checked.stdout) + ); +} + +#[test] +fn vec_i32_runtime_smoke_when_clang_is_available() { + let Some(clang) = find_clang() else { + eprintln!("skipping vec-i32 runtime smoke: set GLAGOL_CLANG or install clang"); + return; + }; + + let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/vec-i32.slo"); + let compile = run_glagol([fixture.as_os_str()]); + assert_success("compile vec-i32 runtime smoke", &compile); + + let run = compile_and_run_with_runtime(&clang, "vec-i32", &compile.stdout); + assert_eq!( + run.status.code(), + Some(41), + "vec-i32 runtime exit code drifted\nstdout:\n{}\nstderr:\n{}", + String::from_utf8_lossy(&run.stdout), + String::from_utf8_lossy(&run.stderr) + ); + assert_eq!( + String::from_utf8_lossy(&run.stdout), + "2\n", + "vec-i32 runtime stdout drifted" + ); + assert!( + run.stderr.is_empty(), + "vec-i32 runtime wrote stderr:\n{}", + String::from_utf8_lossy(&run.stderr) + ); +} + +#[test] +fn vec_i32_runtime_traps_have_contract_messages_when_clang_is_available() { + let Some(clang) = find_clang() else { + eprintln!("skipping vec-i32 trap smoke: set GLAGOL_CLANG or install clang"); + return; + }; + + let cases = [ + ( + "vec-index-bounds", + r#" +(module main) + +(fn main () -> i32 + (std.vec.i32.index (std.vec.i32.empty) 0)) +"#, + "slovo runtime error: vector index out of bounds\n", + ), + ( + "vec-index-negative", + r#" +(module main) + +(fn main () -> i32 + (std.vec.i32.index (std.vec.i32.append (std.vec.i32.empty) 1) -1)) +"#, + "slovo runtime error: vector index out of bounds\n", + ), + ( + "vec-allocation", + r#"#include +#include + +typedef struct __glagol_vec_i32 __glagol_vec_i32; + +__glagol_vec_i32 *__glagol_vec_i32_empty(void); + +void *__wrap_malloc(size_t size) { + (void)size; + return NULL; +} + +int main(void) { + (void)__glagol_vec_i32_empty(); + return 0; +} +"#, + "slovo runtime error: vector allocation failed\n", + ), + ]; + + let manifest = Path::new(env!("CARGO_MANIFEST_DIR")); + let runtime = manifest.join("../runtime/runtime.c"); + + for (name, source, expected_stderr) in cases { + let temp_dir = env::temp_dir().join(format!( + "glagol-vec-runtime-{}-{}", + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + fs::create_dir_all(&temp_dir) + .unwrap_or_else(|err| panic!("create `{}`: {}", temp_dir.display(), err)); + + let exe = temp_dir.join(name); + let run = if name.starts_with("vec-index-") { + let fixture = write_fixture(name, source); + let compile = run_glagol([fixture.as_os_str()]); + assert_success("compile vec index trap", &compile); + compile_and_run_with_runtime(&clang, name, &compile.stdout) + } else { + let probe = temp_dir.join(format!("{}.c", name)); + fs::write(&probe, source) + .unwrap_or_else(|err| panic!("write `{}`: {}", probe.display(), err)); + let mut clang_command = Command::new(&clang); + clang_command + .arg(&runtime) + .arg(&probe) + .arg("-Wl,--wrap=malloc") + .arg("-o") + .arg(&exe) + .current_dir(manifest); + configure_clang_runtime_env(&mut clang_command, &clang); + let clang_output = clang_command + .output() + .unwrap_or_else(|err| panic!("run `{}`: {}", clang.display(), err)); + assert_success("clang vec allocation probe", &clang_output); + Command::new(&exe) + .output() + .unwrap_or_else(|err| panic!("run `{}`: {}", exe.display(), err)) + }; + + assert_eq!( + run.status.code(), + Some(1), + "trap `{}` exit code drifted\nstdout:\n{}\nstderr:\n{}", + name, + String::from_utf8_lossy(&run.stdout), + String::from_utf8_lossy(&run.stderr) + ); + assert_eq!( + String::from_utf8_lossy(&run.stderr), + expected_stderr, + "trap `{}` stderr drifted", + name + ); + assert!( + run.stdout.is_empty(), + "trap `{}` wrote stdout:\n{}", + name, + String::from_utf8_lossy(&run.stdout) + ); + } +} + +#[test] +fn vec_i32_test_runner_reports_index_trap() { + let cases = [ + ( + "vec-index-test-trap", + r#" +(module main) + +(test "vec index trap" + (= (std.vec.i32.index (std.vec.i32.empty) 0) 0)) +"#, + ), + ( + "vec-negative-index-test-trap", + r#" +(module main) + +(test "vec negative index trap" + (= (std.vec.i32.index (std.vec.i32.append (std.vec.i32.empty) 1) -1) 0)) +"#, + ), + ]; + + for (name, source) in cases { + let fixture = write_fixture(name, source); + let output = run_glagol([OsStr::new("test"), fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert_eq!( + output.status.code(), + Some(1), + "test runner vec trap `{}` exit code drifted\nstdout:\n{}\nstderr:\n{}", + name, + stdout, + stderr + ); + assert!( + stdout.is_empty(), + "test runner vec trap `{}` wrote stdout:\n{}", + name, + stdout + ); + assert!( + stderr.contains("TestRuntimeTrap") + && stderr.contains("slovo runtime error: vector index out of bounds"), + "test runner vec trap `{}` diagnostic drifted\nstderr:\n{}", + name, + stderr + ); + } +} + +#[test] +fn vec_i32_project_mode_rewrites_imported_vector_symbols() { + let project = write_vec_project(); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success("check vec-i32 project", &check); + + let run = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + run, + "test \"project vector import\" ... ok\n1 test(s) passed\n", + "vec-i32 project test output", + ); +} + +#[test] +fn vec_i64_fixture_emits_runtime_owned_vector_calls_and_tests_pass() { + let fixture = write_fixture("vec-i64", vec_i64_fixture_source()); + let compile = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + + assert!( + compile.status.success(), + "compiler rejected vec-i64 fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("declare ptr @__glagol_vec_i64_empty()") + && stdout.contains("declare ptr @__glagol_vec_i64_append(ptr, i64)") + && stdout.contains("declare i32 @__glagol_vec_i64_len(ptr)") + && stdout.contains("declare i64 @__glagol_vec_i64_index(ptr, i32)") + && stdout.contains("declare i1 @__glagol_vec_i64_eq(ptr, ptr)") + && stdout.contains("define ptr @empty_values()") + && stdout.contains("define ptr @pair(i64 %base)") + && stdout.contains("define ptr @echo(ptr %values)") + && stdout.contains("define i64 @at(ptr %values, i32 %i)") + && stdout.contains("call ptr @__glagol_vec_i64_empty()") + && stdout.contains("call ptr @__glagol_vec_i64_append(ptr %") + && stdout.contains("call i32 @__glagol_vec_i64_len(ptr %") + && stdout.contains("call i64 @__glagol_vec_i64_index(ptr %") + && stdout.contains("call i1 @__glagol_vec_i64_eq(ptr %") + && !stdout.contains("@std."), + "LLVM output did not contain expected vec i64 runtime shape\nstdout:\n{}", + stdout + ); + assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr); + + let run = run_glagol([OsStr::new("--run-tests"), fixture.as_os_str()]); + assert_success_stdout( + run, + concat!( + "test \"vec i64 empty length\" ... ok\n", + "test \"vec i64 append length\" ... ok\n", + "test \"vec i64 index\" ... ok\n", + "test \"vec i64 append is immutable\" ... ok\n", + "test \"vec i64 equality\" ... ok\n", + "5 test(s) passed\n", + ), + "vec-i64 test runner output", + ); +} + +#[test] +fn vec_i64_formatter_and_lowering_inspector_are_visible() { + let fixture = write_fixture( + "vec-i64-tooling", + r#" +(module main) + +(fn make () -> (vec i64) + (let values (vec i64) (std.vec.i64.empty)) + (std.vec.i64.append values 1i64)) + +(fn main () -> i32 + (std.vec.i64.len (make))) +"#, + ); + + let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]); + assert_success_stdout( + formatted, + concat!( + "(module main)\n", + "\n", + "(fn make () -> (vec i64)\n", + " (let values (vec i64) (std.vec.i64.empty))\n", + " (std.vec.i64.append values 1i64))\n", + "\n", + "(fn main () -> i32\n", + " (std.vec.i64.len (make)))\n", + ), + "vec-i64 formatter output", + ); + + let surface = run_glagol([ + OsStr::new("--inspect-lowering=surface"), + fixture.as_os_str(), + ]); + assert_success("inspect vec-i64 surface lowering", &surface); + assert!( + String::from_utf8_lossy(&surface.stdout).contains("fn make() -> (vec i64)"), + "surface lowering did not show vec i64 return type\nstdout:\n{}", + String::from_utf8_lossy(&surface.stdout) + ); + + let checked = run_glagol([ + OsStr::new("--inspect-lowering=checked"), + fixture.as_os_str(), + ]); + assert_success("inspect vec-i64 checked lowering", &checked); + assert!( + String::from_utf8_lossy(&checked.stdout).contains("call std.vec.i64.append : (vec i64)"), + "checked lowering did not show typed vec i64 append\nstdout:\n{}", + String::from_utf8_lossy(&checked.stdout) + ); +} + +#[test] +fn vec_i64_runtime_smoke_when_clang_is_available() { + let Some(clang) = find_clang() else { + eprintln!("skipping vec-i64 runtime smoke: set GLAGOL_CLANG or install clang"); + return; + }; + + let fixture = write_fixture("vec-i64-runtime", vec_i64_fixture_source()); + let compile = run_glagol([fixture.as_os_str()]); + assert_success("compile vec-i64 runtime smoke", &compile); + + let run = compile_and_run_with_runtime(&clang, "vec-i64", &compile.stdout); + assert_eq!( + run.status.code(), + Some(0), + "vec-i64 runtime exit code drifted\nstdout:\n{}\nstderr:\n{}", + String::from_utf8_lossy(&run.stdout), + String::from_utf8_lossy(&run.stderr) + ); + assert_eq!( + String::from_utf8_lossy(&run.stdout), + "21\n", + "vec-i64 runtime stdout drifted" + ); + assert!( + run.stderr.is_empty(), + "vec-i64 runtime wrote stderr:\n{}", + String::from_utf8_lossy(&run.stderr) + ); +} + +#[test] +fn vec_i64_runtime_traps_have_contract_messages_when_clang_is_available() { + let Some(clang) = find_clang() else { + eprintln!("skipping vec-i64 trap smoke: set GLAGOL_CLANG or install clang"); + return; + }; + + let cases = [ + ( + "vec-i64-index-bounds", + r#" +(module main) + +(fn main () -> i32 + (std.io.print_i64 (std.vec.i64.index (std.vec.i64.empty) 0)) + 0) +"#, + "slovo runtime error: vector index out of bounds\n", + ), + ( + "vec-i64-index-negative", + r#" +(module main) + +(fn main () -> i32 + (std.io.print_i64 + (std.vec.i64.index (std.vec.i64.append (std.vec.i64.empty) 1i64) -1)) + 0) +"#, + "slovo runtime error: vector index out of bounds\n", + ), + ( + "vec-i64-allocation", + r#"#include +#include + +typedef struct __glagol_vec_i64 __glagol_vec_i64; + +__glagol_vec_i64 *__glagol_vec_i64_empty(void); + +void *__wrap_malloc(size_t size) { + (void)size; + return NULL; +} + +int main(void) { + (void)__glagol_vec_i64_empty(); + return 0; +} +"#, + "slovo runtime error: vector allocation failed\n", + ), + ]; + + let manifest = Path::new(env!("CARGO_MANIFEST_DIR")); + let runtime = manifest.join("../runtime/runtime.c"); + + for (name, source, expected_stderr) in cases { + let temp_dir = env::temp_dir().join(format!( + "glagol-vec-i64-runtime-{}-{}", + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + fs::create_dir_all(&temp_dir) + .unwrap_or_else(|err| panic!("create `{}`: {}", temp_dir.display(), err)); + + let exe = temp_dir.join(name); + let run = if name.starts_with("vec-i64-index-") { + let fixture = write_fixture(name, source); + let compile = run_glagol([fixture.as_os_str()]); + assert_success("compile vec i64 index trap", &compile); + compile_and_run_with_runtime(&clang, name, &compile.stdout) + } else { + let probe = temp_dir.join(format!("{}.c", name)); + fs::write(&probe, source) + .unwrap_or_else(|err| panic!("write `{}`: {}", probe.display(), err)); + let mut clang_command = Command::new(&clang); + clang_command + .arg(&runtime) + .arg(&probe) + .arg("-Wl,--wrap=malloc") + .arg("-o") + .arg(&exe) + .current_dir(manifest); + configure_clang_runtime_env(&mut clang_command, &clang); + let clang_output = clang_command + .output() + .unwrap_or_else(|err| panic!("run `{}`: {}", clang.display(), err)); + assert_success("clang vec i64 allocation probe", &clang_output); + Command::new(&exe) + .output() + .unwrap_or_else(|err| panic!("run `{}`: {}", exe.display(), err)) + }; + + assert_eq!( + run.status.code(), + Some(1), + "trap `{}` exit code drifted\nstdout:\n{}\nstderr:\n{}", + name, + String::from_utf8_lossy(&run.stdout), + String::from_utf8_lossy(&run.stderr) + ); + assert_eq!( + String::from_utf8_lossy(&run.stderr), + expected_stderr, + "trap `{}` stderr drifted", + name + ); + assert!( + run.stdout.is_empty(), + "trap `{}` wrote stdout:\n{}", + name, + String::from_utf8_lossy(&run.stdout) + ); + } +} + +#[test] +fn vec_i64_test_runner_reports_index_trap() { + let cases = [ + ( + "vec-i64-index-test-trap", + r#" +(module main) + +(test "vec i64 index trap" + (= (std.vec.i64.index (std.vec.i64.empty) 0) 0i64)) +"#, + ), + ( + "vec-i64-negative-index-test-trap", + r#" +(module main) + +(test "vec i64 negative index trap" + (= (std.vec.i64.index (std.vec.i64.append (std.vec.i64.empty) 1i64) -1) 0i64)) +"#, + ), + ]; + + for (name, source) in cases { + let fixture = write_fixture(name, source); + let output = run_glagol([OsStr::new("test"), fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert_eq!( + output.status.code(), + Some(1), + "test runner vec i64 trap `{}` exit code drifted\nstdout:\n{}\nstderr:\n{}", + name, + stdout, + stderr + ); + assert!( + stdout.is_empty(), + "test runner vec i64 trap `{}` wrote stdout:\n{}", + name, + stdout + ); + assert!( + stderr.contains("TestRuntimeTrap") + && stderr.contains("slovo runtime error: vector index out of bounds"), + "test runner vec i64 trap `{}` diagnostic drifted\nstderr:\n{}", + name, + stderr + ); + } +} + +#[test] +fn vec_f64_fixture_emits_runtime_owned_vector_calls_and_tests_pass() { + let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/vec-f64.slo"); + let compile = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + + assert!( + compile.status.success(), + "compiler rejected vec-f64 fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("declare ptr @__glagol_vec_f64_empty()") + && stdout.contains("declare ptr @__glagol_vec_f64_append(ptr, double)") + && stdout.contains("declare i32 @__glagol_vec_f64_len(ptr)") + && stdout.contains("declare double @__glagol_vec_f64_index(ptr, i32)") + && stdout.contains("declare i1 @__glagol_vec_f64_eq(ptr, ptr)") + && stdout.contains("define ptr @empty_values()") + && stdout.contains("define ptr @pair(double %base)") + && stdout.contains("define ptr @echo(ptr %values)") + && stdout.contains("define double @at(ptr %values, i32 %i)") + && stdout.contains("call ptr @__glagol_vec_f64_empty()") + && stdout.contains("call ptr @__glagol_vec_f64_append(ptr %") + && stdout.contains("call i32 @__glagol_vec_f64_len(ptr %") + && stdout.contains("call double @__glagol_vec_f64_index(ptr %") + && !stdout.contains("@std."), + "LLVM output did not contain expected vec f64 runtime shape\nstdout:\n{}", + stdout + ); + assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr); + + let run = run_glagol([OsStr::new("--run-tests"), fixture.as_os_str()]); + assert_success_stdout( + run, + concat!( + "test \"vec f64 empty length\" ... ok\n", + "test \"vec f64 append length\" ... ok\n", + "test \"vec f64 index\" ... ok\n", + "test \"vec f64 append is immutable\" ... ok\n", + "test \"vec f64 equality\" ... ok\n", + "5 test(s) passed\n", + ), + "vec-f64 test runner output", + ); +} + +#[test] +fn vec_f64_formatter_and_lowering_inspector_are_visible() { + let fixture = write_fixture( + "vec-f64-tooling", + r#" +(module main) + +(fn make () -> (vec f64) + (let values (vec f64) (std.vec.f64.empty)) + (std.vec.f64.append values 1.0)) + +(fn main () -> i32 + (std.vec.f64.len (make))) +"#, + ); + + let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]); + assert_success_stdout( + formatted, + concat!( + "(module main)\n", + "\n", + "(fn make () -> (vec f64)\n", + " (let values (vec f64) (std.vec.f64.empty))\n", + " (std.vec.f64.append values 1.0))\n", + "\n", + "(fn main () -> i32\n", + " (std.vec.f64.len (make)))\n", + ), + "vec-f64 formatter output", + ); + + let surface = run_glagol([ + OsStr::new("--inspect-lowering=surface"), + fixture.as_os_str(), + ]); + assert_success("inspect vec-f64 surface lowering", &surface); + assert!( + String::from_utf8_lossy(&surface.stdout).contains("fn make() -> (vec f64)"), + "surface lowering did not show vec f64 return type\nstdout:\n{}", + String::from_utf8_lossy(&surface.stdout) + ); + + let checked = run_glagol([ + OsStr::new("--inspect-lowering=checked"), + fixture.as_os_str(), + ]); + assert_success("inspect vec-f64 checked lowering", &checked); + assert!( + String::from_utf8_lossy(&checked.stdout).contains("call std.vec.f64.append : (vec f64)"), + "checked lowering did not show typed vec f64 append\nstdout:\n{}", + String::from_utf8_lossy(&checked.stdout) + ); +} + +#[test] +fn vec_f64_runtime_smoke_when_clang_is_available() { + let Some(clang) = find_clang() else { + eprintln!("skipping vec-f64 runtime smoke: set GLAGOL_CLANG or install clang"); + return; + }; + + let fixture = write_fixture("vec-f64-runtime", vec_f64_fixture_source()); + let compile = run_glagol([fixture.as_os_str()]); + assert_success("compile vec-f64 runtime smoke", &compile); + + let run = compile_and_run_with_runtime(&clang, "vec-f64", &compile.stdout); + assert_eq!( + run.status.code(), + Some(0), + "vec-f64 runtime exit code drifted\nstdout:\n{}\nstderr:\n{}", + String::from_utf8_lossy(&run.stdout), + String::from_utf8_lossy(&run.stderr) + ); + assert_eq!( + String::from_utf8_lossy(&run.stdout), + "21\n", + "vec-f64 runtime stdout drifted" + ); + assert!( + run.stderr.is_empty(), + "vec-f64 runtime wrote stderr:\n{}", + String::from_utf8_lossy(&run.stderr) + ); +} + +#[test] +fn vec_f64_runtime_traps_have_contract_messages_when_clang_is_available() { + let Some(clang) = find_clang() else { + eprintln!("skipping vec-f64 trap smoke: set GLAGOL_CLANG or install clang"); + return; + }; + + let cases = [ + ( + "vec-f64-index-bounds", + r#" +(module main) + +(fn main () -> i32 + (std.io.print_f64 (std.vec.f64.index (std.vec.f64.empty) 0)) + 0) +"#, + "slovo runtime error: vector index out of bounds\n", + ), + ( + "vec-f64-index-negative", + r#" +(module main) + +(fn main () -> i32 + (std.io.print_f64 + (std.vec.f64.index (std.vec.f64.append (std.vec.f64.empty) 1.0) -1)) + 0) +"#, + "slovo runtime error: vector index out of bounds\n", + ), + ( + "vec-f64-allocation", + r#"#include +#include + +typedef struct __glagol_vec_f64 __glagol_vec_f64; + +__glagol_vec_f64 *__glagol_vec_f64_empty(void); + +void *__wrap_malloc(size_t size) { + (void)size; + return NULL; +} + +int main(void) { + (void)__glagol_vec_f64_empty(); + return 0; +} +"#, + "slovo runtime error: vector allocation failed\n", + ), + ]; + + let manifest = Path::new(env!("CARGO_MANIFEST_DIR")); + let runtime = manifest.join("../runtime/runtime.c"); + + for (name, source, expected_stderr) in cases { + let temp_dir = env::temp_dir().join(format!( + "glagol-vec-f64-runtime-{}-{}", + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + fs::create_dir_all(&temp_dir) + .unwrap_or_else(|err| panic!("create `{}`: {}", temp_dir.display(), err)); + + let exe = temp_dir.join(name); + let run = if name.starts_with("vec-f64-index-") { + let fixture = write_fixture(name, source); + let compile = run_glagol([fixture.as_os_str()]); + assert_success("compile vec f64 index trap", &compile); + compile_and_run_with_runtime(&clang, name, &compile.stdout) + } else { + let probe = temp_dir.join(format!("{}.c", name)); + fs::write(&probe, source) + .unwrap_or_else(|err| panic!("write `{}`: {}", probe.display(), err)); + let mut clang_command = Command::new(&clang); + clang_command + .arg(&runtime) + .arg(&probe) + .arg("-Wl,--wrap=malloc") + .arg("-o") + .arg(&exe) + .current_dir(manifest); + configure_clang_runtime_env(&mut clang_command, &clang); + let clang_output = clang_command + .output() + .unwrap_or_else(|err| panic!("run `{}`: {}", clang.display(), err)); + assert_success("clang vec f64 allocation probe", &clang_output); + Command::new(&exe) + .output() + .unwrap_or_else(|err| panic!("run `{}`: {}", exe.display(), err)) + }; + + assert_eq!( + run.status.code(), + Some(1), + "trap `{}` exit code drifted\nstdout:\n{}\nstderr:\n{}", + name, + String::from_utf8_lossy(&run.stdout), + String::from_utf8_lossy(&run.stderr) + ); + assert_eq!( + String::from_utf8_lossy(&run.stderr), + expected_stderr, + "trap `{}` stderr drifted", + name + ); + assert!( + run.stdout.is_empty(), + "trap `{}` wrote stdout:\n{}", + name, + String::from_utf8_lossy(&run.stdout) + ); + } +} + +#[test] +fn vec_f64_test_runner_reports_index_trap() { + let cases = [ + ( + "vec-f64-index-test-trap", + r#" +(module main) + +(test "vec f64 index trap" + (= (std.vec.f64.index (std.vec.f64.empty) 0) 0.0)) +"#, + ), + ( + "vec-f64-negative-index-test-trap", + r#" +(module main) + +(test "vec f64 negative index trap" + (= (std.vec.f64.index (std.vec.f64.append (std.vec.f64.empty) 1.0) -1) 0.0)) +"#, + ), + ]; + + for (name, source) in cases { + let fixture = write_fixture(name, source); + let output = run_glagol([OsStr::new("test"), fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert_eq!( + output.status.code(), + Some(1), + "test runner vec f64 trap `{}` exit code drifted\nstdout:\n{}\nstderr:\n{}", + name, + stdout, + stderr + ); + assert!( + stdout.is_empty(), + "test runner vec f64 trap `{}` wrote stdout:\n{}", + name, + stdout + ); + assert!( + stderr.contains("TestRuntimeTrap") + && stderr.contains("slovo runtime error: vector index out of bounds"), + "test runner vec f64 trap `{}` diagnostic drifted\nstderr:\n{}", + name, + stderr + ); + } +} + +#[test] +fn vec_bool_fixture_emits_runtime_owned_vector_calls_and_tests_pass() { + let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/vec-bool.slo"); + let compile = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + + assert!( + compile.status.success(), + "compiler rejected vec-bool fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("declare ptr @__glagol_vec_bool_empty()") + && stdout.contains("declare ptr @__glagol_vec_bool_append(ptr, i1)") + && stdout.contains("declare i32 @__glagol_vec_bool_len(ptr)") + && stdout.contains("declare i1 @__glagol_vec_bool_index(ptr, i32)") + && stdout.contains("declare i1 @__glagol_vec_bool_eq(ptr, ptr)") + && stdout.contains("define ptr @empty_values()") + && stdout.contains("define ptr @pair()") + && stdout.contains("define ptr @echo(ptr %values)") + && stdout.contains("define i1 @at(ptr %values, i32 %i)") + && stdout.contains("call ptr @__glagol_vec_bool_empty()") + && stdout.contains("call ptr @__glagol_vec_bool_append(ptr %") + && stdout.contains("call i32 @__glagol_vec_bool_len(ptr %") + && stdout.contains("call i1 @__glagol_vec_bool_index(ptr %") + && !stdout.contains("@std."), + "LLVM output did not contain expected vec bool runtime shape\nstdout:\n{}", + stdout + ); + assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr); + + let run = run_glagol([OsStr::new("--run-tests"), fixture.as_os_str()]); + assert_success_stdout( + run, + concat!( + "test \"vec bool empty length\" ... ok\n", + "test \"vec bool append length\" ... ok\n", + "test \"vec bool index\" ... ok\n", + "test \"vec bool append is immutable\" ... ok\n", + "test \"vec bool equality\" ... ok\n", + "5 test(s) passed\n", + ), + "vec-bool test runner output", + ); +} + +#[test] +fn vec_bool_formatter_and_lowering_inspector_are_visible() { + let fixture = write_fixture( + "vec-bool-tooling", + r#" +(module main) + +(fn make () -> (vec bool) + (let values (vec bool) (std.vec.bool.empty)) + (std.vec.bool.append values true)) + +(fn main () -> i32 + (if (= (std.vec.bool.index (make) 0) true) + 1 + 0)) +"#, + ); + + let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]); + assert_success_stdout( + formatted, + concat!( + "(module main)\n", + "\n", + "(fn make () -> (vec bool)\n", + " (let values (vec bool) (std.vec.bool.empty))\n", + " (std.vec.bool.append values true))\n", + "\n", + "(fn main () -> i32\n", + " (if (= (std.vec.bool.index (make) 0) true)\n", + " 1\n", + " 0))\n", + ), + "vec-bool formatter output", + ); + + let surface = run_glagol([ + OsStr::new("--inspect-lowering=surface"), + fixture.as_os_str(), + ]); + assert_success("inspect vec-bool surface lowering", &surface); + assert!( + String::from_utf8_lossy(&surface.stdout).contains("fn make() -> (vec bool)"), + "surface lowering did not show vec bool return type\nstdout:\n{}", + String::from_utf8_lossy(&surface.stdout) + ); + + let checked = run_glagol([ + OsStr::new("--inspect-lowering=checked"), + fixture.as_os_str(), + ]); + assert_success("inspect vec-bool checked lowering", &checked); + assert!( + String::from_utf8_lossy(&checked.stdout).contains("call std.vec.bool.append : (vec bool)"), + "checked lowering did not show typed vec bool append\nstdout:\n{}", + String::from_utf8_lossy(&checked.stdout) + ); +} + +#[test] +fn vec_bool_runtime_smoke_when_clang_is_available() { + let Some(clang) = find_clang() else { + eprintln!("skipping vec-bool runtime smoke: set GLAGOL_CLANG or install clang"); + return; + }; + + let fixture = write_fixture("vec-bool-runtime", vec_bool_fixture_source()); + let compile = run_glagol([fixture.as_os_str()]); + assert_success("compile vec-bool runtime smoke", &compile); + + let run = compile_and_run_with_runtime(&clang, "vec-bool", &compile.stdout); + assert_eq!( + run.status.code(), + Some(0), + "vec-bool runtime exit code drifted\nstdout:\n{}\nstderr:\n{}", + String::from_utf8_lossy(&run.stdout), + String::from_utf8_lossy(&run.stderr) + ); + assert_eq!( + String::from_utf8_lossy(&run.stdout), + "false\n", + "vec-bool runtime stdout drifted" + ); + assert!( + run.stderr.is_empty(), + "vec-bool runtime wrote stderr:\n{}", + String::from_utf8_lossy(&run.stderr) + ); +} + +#[test] +fn vec_bool_runtime_traps_have_contract_messages_when_clang_is_available() { + let Some(clang) = find_clang() else { + eprintln!("skipping vec-bool trap smoke: set GLAGOL_CLANG or install clang"); + return; + }; + + let cases = [ + ( + "vec-bool-index-bounds", + r#" +(module main) + +(fn main () -> i32 + (std.io.print_bool (std.vec.bool.index (std.vec.bool.empty) 0)) + 0) +"#, + "slovo runtime error: vector index out of bounds\n", + ), + ( + "vec-bool-index-negative", + r#" +(module main) + +(fn main () -> i32 + (std.io.print_bool + (std.vec.bool.index (std.vec.bool.append (std.vec.bool.empty) true) -1)) + 0) +"#, + "slovo runtime error: vector index out of bounds\n", + ), + ( + "vec-bool-allocation", + r#"#include +#include +#include + +typedef struct __glagol_vec_bool __glagol_vec_bool; + +__glagol_vec_bool *__glagol_vec_bool_empty(void); + +void *__wrap_malloc(size_t size) { + (void)size; + return NULL; +} + +int main(void) { + (void)__glagol_vec_bool_empty(); + return 0; +} +"#, + "slovo runtime error: vector allocation failed\n", + ), + ]; + + let manifest = Path::new(env!("CARGO_MANIFEST_DIR")); + let runtime = manifest.join("../runtime/runtime.c"); + + for (name, source, expected_stderr) in cases { + let temp_dir = env::temp_dir().join(format!( + "glagol-vec-bool-runtime-{}-{}", + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + fs::create_dir_all(&temp_dir) + .unwrap_or_else(|err| panic!("create `{}`: {}", temp_dir.display(), err)); + + let exe = temp_dir.join(name); + let run = if name.starts_with("vec-bool-index-") { + let fixture = write_fixture(name, source); + let compile = run_glagol([fixture.as_os_str()]); + assert_success("compile vec bool index trap", &compile); + compile_and_run_with_runtime(&clang, name, &compile.stdout) + } else { + let probe = temp_dir.join(format!("{}.c", name)); + fs::write(&probe, source) + .unwrap_or_else(|err| panic!("write `{}`: {}", probe.display(), err)); + let mut clang_command = Command::new(&clang); + clang_command + .arg(&runtime) + .arg(&probe) + .arg("-Wl,--wrap=malloc") + .arg("-o") + .arg(&exe) + .current_dir(manifest); + configure_clang_runtime_env(&mut clang_command, &clang); + let clang_output = clang_command + .output() + .unwrap_or_else(|err| panic!("run `{}`: {}", clang.display(), err)); + assert_success("clang vec bool allocation probe", &clang_output); + Command::new(&exe) + .output() + .unwrap_or_else(|err| panic!("run `{}`: {}", exe.display(), err)) + }; + + assert_eq!( + run.status.code(), + Some(1), + "trap `{}` exit code drifted\nstdout:\n{}\nstderr:\n{}", + name, + String::from_utf8_lossy(&run.stdout), + String::from_utf8_lossy(&run.stderr) + ); + assert_eq!( + String::from_utf8_lossy(&run.stderr), + expected_stderr, + "trap `{}` stderr drifted", + name + ); + assert!( + run.stdout.is_empty(), + "trap `{}` wrote stdout:\n{}", + name, + String::from_utf8_lossy(&run.stdout) + ); + } +} + +#[test] +fn vec_bool_test_runner_reports_index_trap() { + let cases = [ + ( + "vec-bool-index-test-trap", + r#" +(module main) + +(test "vec bool index trap" + (= (std.vec.bool.index (std.vec.bool.empty) 0) false)) +"#, + ), + ( + "vec-bool-negative-index-test-trap", + r#" +(module main) + +(test "vec bool negative index trap" + (= (std.vec.bool.index (std.vec.bool.append (std.vec.bool.empty) true) -1) false)) +"#, + ), + ]; + + for (name, source) in cases { + let fixture = write_fixture(name, source); + let output = run_glagol([OsStr::new("test"), fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert_eq!( + output.status.code(), + Some(1), + "test runner vec bool trap `{}` exit code drifted\nstdout:\n{}\nstderr:\n{}", + name, + stdout, + stderr + ); + assert!( + stdout.is_empty(), + "test runner vec bool trap `{}` wrote stdout:\n{}", + name, + stdout + ); + assert!( + stderr.contains("TestRuntimeTrap") + && stderr.contains("slovo runtime error: vector index out of bounds"), + "test runner vec bool trap `{}` diagnostic drifted\nstderr:\n{}", + name, + stderr + ); + } +} + +#[test] +fn vec_string_fixture_emits_runtime_owned_vector_calls_and_tests_pass() { + let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/vec-string.slo"); + let compile = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + + assert!( + compile.status.success(), + "compiler rejected vec-string fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("declare ptr @__glagol_vec_string_empty()") + && stdout.contains("declare ptr @__glagol_vec_string_append(ptr, ptr)") + && stdout.contains("declare i32 @__glagol_vec_string_len(ptr)") + && stdout.contains("declare ptr @__glagol_vec_string_index(ptr, i32)") + && stdout.contains("declare i1 @__glagol_vec_string_eq(ptr, ptr)") + && stdout.contains("define ptr @empty_values()") + && stdout.contains("define ptr @pair(ptr %first, ptr %second)") + && stdout.contains("define ptr @echo(ptr %values)") + && stdout.contains("define ptr @at(ptr %values, i32 %i)") + && stdout.contains("call ptr @__glagol_vec_string_empty()") + && stdout.contains("call ptr @__glagol_vec_string_append(ptr %") + && stdout.contains("call i32 @__glagol_vec_string_len(ptr %") + && stdout.contains("call ptr @__glagol_vec_string_index(ptr %") + && !stdout.contains("@std."), + "LLVM output did not contain expected vec string runtime shape\nstdout:\n{}", + stdout + ); + assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr); + + let run = run_glagol([OsStr::new("--run-tests"), fixture.as_os_str()]); + assert_success_stdout( + run, + concat!( + "test \"vec string empty length\" ... ok\n", + "test \"vec string append length\" ... ok\n", + "test \"vec string index\" ... ok\n", + "test \"vec string append is immutable\" ... ok\n", + "test \"vec string equality\" ... ok\n", + "5 test(s) passed\n", + ), + "vec-string test runner output", + ); +} + +#[test] +fn vec_string_equality_lowers_to_runtime_helper() { + let fixture = write_fixture( + "vec-string-equality", + r#" +(module main) + +(fn same () -> i32 + (if (= (std.vec.string.append (std.vec.string.empty) "same") + (std.vec.string.append (std.vec.string.empty) "same")) + 1 + 0)) + +(fn main () -> i32 + (same)) +"#, + ); + let compile = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + + assert!( + compile.status.success(), + "compiler rejected vec string equality fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("call i1 @__glagol_vec_string_eq(ptr %"), + "LLVM output did not lower vec string equality through helper\nstdout:\n{}", + stdout + ); +} + +#[test] +fn vec_string_formatter_and_lowering_inspector_are_visible() { + let fixture = write_fixture( + "vec-string-tooling", + r#" +(module main) + +(fn make () -> (vec string) + (let values (vec string) (std.vec.string.empty)) + (std.vec.string.append values "alpha")) + +(fn main () -> i32 + (std.vec.string.len (make))) +"#, + ); + + let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]); + assert_success_stdout( + formatted, + concat!( + "(module main)\n", + "\n", + "(fn make () -> (vec string)\n", + " (let values (vec string) (std.vec.string.empty))\n", + " (std.vec.string.append values \"alpha\"))\n", + "\n", + "(fn main () -> i32\n", + " (std.vec.string.len (make)))\n", + ), + "vec-string formatter output", + ); + + let surface = run_glagol([ + OsStr::new("--inspect-lowering=surface"), + fixture.as_os_str(), + ]); + assert_success("inspect vec-string surface lowering", &surface); + assert!( + String::from_utf8_lossy(&surface.stdout).contains("fn make() -> (vec string)"), + "surface lowering did not show vec string return type\nstdout:\n{}", + String::from_utf8_lossy(&surface.stdout) + ); + + let checked = run_glagol([ + OsStr::new("--inspect-lowering=checked"), + fixture.as_os_str(), + ]); + assert_success("inspect vec-string checked lowering", &checked); + assert!( + String::from_utf8_lossy(&checked.stdout) + .contains("call std.vec.string.append : (vec string)"), + "checked lowering did not show typed vec string append\nstdout:\n{}", + String::from_utf8_lossy(&checked.stdout) + ); +} + +#[test] +fn vec_string_runtime_smoke_when_clang_is_available() { + let Some(clang) = find_clang() else { + eprintln!("skipping vec-string runtime smoke: set GLAGOL_CLANG or install clang"); + return; + }; + + let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/vec-string.slo"); + let compile = run_glagol([fixture.as_os_str()]); + assert_success("compile vec-string runtime smoke", &compile); + + let run = compile_and_run_with_runtime(&clang, "vec-string", &compile.stdout); + assert_eq!( + run.status.code(), + Some(0), + "vec-string runtime exit code drifted\nstdout:\n{}\nstderr:\n{}", + String::from_utf8_lossy(&run.stdout), + String::from_utf8_lossy(&run.stderr) + ); + assert_eq!( + String::from_utf8_lossy(&run.stdout), + "tree\n", + "vec-string runtime stdout drifted" + ); + assert!( + run.stderr.is_empty(), + "vec-string runtime wrote stderr:\n{}", + String::from_utf8_lossy(&run.stderr) + ); +} + +#[test] +fn vec_string_runtime_traps_have_contract_messages_when_clang_is_available() { + let Some(clang) = find_clang() else { + eprintln!("skipping vec-string trap smoke: set GLAGOL_CLANG or install clang"); + return; + }; + + let cases = [ + ( + "vec-string-index-bounds", + r#" +(module main) + +(fn main () -> i32 + (std.io.print_string (std.vec.string.index (std.vec.string.empty) 0)) + 0) +"#, + "slovo runtime error: vector index out of bounds\n", + ), + ( + "vec-string-index-negative", + r#" +(module main) + +(fn main () -> i32 + (std.io.print_string + (std.vec.string.index + (std.vec.string.append (std.vec.string.empty) "only") + -1)) + 0) +"#, + "slovo runtime error: vector index out of bounds\n", + ), + ( + "vec-string-allocation", + r#"#include +#include + +typedef struct __glagol_vec_string __glagol_vec_string; + +__glagol_vec_string *__glagol_vec_string_empty(void); + +void *__wrap_malloc(size_t size) { + (void)size; + return NULL; +} + +int main(void) { + (void)__glagol_vec_string_empty(); + return 0; +} +"#, + "slovo runtime error: vector allocation failed\n", + ), + ]; + + let manifest = Path::new(env!("CARGO_MANIFEST_DIR")); + let runtime = manifest.join("../runtime/runtime.c"); + + for (name, source, expected_stderr) in cases { + let temp_dir = env::temp_dir().join(format!( + "glagol-vec-string-runtime-{}-{}", + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + fs::create_dir_all(&temp_dir) + .unwrap_or_else(|err| panic!("create `{}`: {}", temp_dir.display(), err)); + + let exe = temp_dir.join(name); + let run = if name.starts_with("vec-string-index-") { + let fixture = write_fixture(name, source); + let compile = run_glagol([fixture.as_os_str()]); + assert_success("compile vec string index trap", &compile); + compile_and_run_with_runtime(&clang, name, &compile.stdout) + } else { + let probe = temp_dir.join(format!("{}.c", name)); + fs::write(&probe, source) + .unwrap_or_else(|err| panic!("write `{}`: {}", probe.display(), err)); + let mut clang_command = Command::new(&clang); + clang_command + .arg(&runtime) + .arg(&probe) + .arg("-Wl,--wrap=malloc") + .arg("-o") + .arg(&exe) + .current_dir(manifest); + configure_clang_runtime_env(&mut clang_command, &clang); + let clang_output = clang_command + .output() + .unwrap_or_else(|err| panic!("run `{}`: {}", clang.display(), err)); + assert_success("clang vec string allocation probe", &clang_output); + Command::new(&exe) + .output() + .unwrap_or_else(|err| panic!("run `{}`: {}", exe.display(), err)) + }; + + assert_eq!( + run.status.code(), + Some(1), + "trap `{}` exit code drifted\nstdout:\n{}\nstderr:\n{}", + name, + String::from_utf8_lossy(&run.stdout), + String::from_utf8_lossy(&run.stderr) + ); + assert_eq!( + String::from_utf8_lossy(&run.stderr), + expected_stderr, + "trap `{}` stderr drifted", + name + ); + assert!( + run.stdout.is_empty(), + "trap `{}` wrote stdout:\n{}", + name, + String::from_utf8_lossy(&run.stdout) + ); + } +} + +#[test] +fn vec_string_test_runner_reports_index_trap() { + let cases = [ + ( + "vec-string-index-test-trap", + r#" +(module main) + +(test "vec string index trap" + (= (std.vec.string.index (std.vec.string.empty) 0) "missing")) +"#, + ), + ( + "vec-string-negative-index-test-trap", + r#" +(module main) + +(test "vec string negative index trap" + (= (std.vec.string.index (std.vec.string.append (std.vec.string.empty) "only") -1) "missing")) +"#, + ), + ]; + + for (name, source) in cases { + let fixture = write_fixture(name, source); + let output = run_glagol([OsStr::new("test"), fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert_eq!( + output.status.code(), + Some(1), + "test runner vec string trap `{}` exit code drifted\nstdout:\n{}\nstderr:\n{}", + name, + stdout, + stderr + ); + assert!( + stdout.is_empty(), + "test runner vec string trap `{}` wrote stdout:\n{}", + name, + stdout + ); + assert!( + stderr.contains("TestRuntimeTrap") + && stderr.contains("slovo runtime error: vector index out of bounds"), + "test runner vec string trap `{}` diagnostic drifted\nstderr:\n{}", + name, + stderr + ); + } +} + +fn vec_i64_fixture_source() -> &'static str { + r#" +(module main) + +(fn empty_values () -> (vec i64) + (std.vec.i64.empty)) + +(fn pair ((base i64)) -> (vec i64) + (let values (vec i64) (std.vec.i64.empty)) + (let first (vec i64) (std.vec.i64.append values base)) + (std.vec.i64.append first (+ base 1i64))) + +(fn echo ((values (vec i64))) -> (vec i64) + values) + +(fn length ((values (vec i64))) -> i32 + (std.vec.i64.len values)) + +(fn at ((values (vec i64)) (i i32)) -> i64 + (std.vec.i64.index values i)) + +(fn call_return_value () -> i64 + (std.vec.i64.index (echo (pair 20i64)) 1)) + +(fn original_len_after_append () -> i32 + (let values (vec i64) (std.vec.i64.empty)) + (let appended (vec i64) (std.vec.i64.append values 1i64)) + (std.vec.i64.len values)) + +(fn same_pair () -> bool + (= (pair 5i64) + (std.vec.i64.append (std.vec.i64.append (std.vec.i64.empty) 5i64) 6i64))) + +(test "vec i64 empty length" + (= (std.vec.i64.len (empty_values)) 0)) + +(test "vec i64 append length" + (= (length (pair 40i64)) 2)) + +(test "vec i64 index" + (= (at (pair 40i64) 1) 41i64)) + +(test "vec i64 append is immutable" + (= (original_len_after_append) 0)) + +(test "vec i64 equality" + (same_pair)) + +(fn main () -> i32 + (std.io.print_i64 (call_return_value)) + 0) +"# +} + +fn vec_f64_fixture_source() -> &'static str { + r#" +(module main) + +(fn empty_values () -> (vec f64) + (std.vec.f64.empty)) + +(fn pair ((base f64)) -> (vec f64) + (let values (vec f64) (std.vec.f64.empty)) + (let first (vec f64) (std.vec.f64.append values base)) + (std.vec.f64.append first (+ base 1.0))) + +(fn echo ((values (vec f64))) -> (vec f64) + values) + +(fn length ((values (vec f64))) -> i32 + (std.vec.f64.len values)) + +(fn at ((values (vec f64)) (i i32)) -> f64 + (std.vec.f64.index values i)) + +(fn call_return_value () -> f64 + (std.vec.f64.index (echo (pair 20.0)) 1)) + +(fn original_len_after_append () -> i32 + (let values (vec f64) (std.vec.f64.empty)) + (let appended (vec f64) (std.vec.f64.append values 1.0)) + (std.vec.f64.len values)) + +(fn same_pair () -> bool + (= (pair 5.0) + (std.vec.f64.append (std.vec.f64.append (std.vec.f64.empty) 5.0) 6.0))) + +(test "vec f64 empty length" + (= (std.vec.f64.len (empty_values)) 0)) + +(test "vec f64 append length" + (= (length (pair 40.0)) 2)) + +(test "vec f64 index" + (= (at (pair 40.0) 1) 41.0)) + +(test "vec f64 append is immutable" + (= (original_len_after_append) 0)) + +(test "vec f64 equality" + (same_pair)) + +(fn main () -> i32 + (std.io.print_f64 (call_return_value)) + 0) +"# +} + +fn vec_bool_fixture_source() -> &'static str { + r#" +(module main) + +(fn empty_values () -> (vec bool) + (std.vec.bool.empty)) + +(fn pair () -> (vec bool) + (let values (vec bool) (std.vec.bool.empty)) + (let first (vec bool) (std.vec.bool.append values true)) + (std.vec.bool.append first false)) + +(fn echo ((values (vec bool))) -> (vec bool) + values) + +(fn length ((values (vec bool))) -> i32 + (std.vec.bool.len values)) + +(fn at ((values (vec bool)) (i i32)) -> bool + (std.vec.bool.index values i)) + +(fn call_return_value () -> bool + (std.vec.bool.index (echo (pair)) 1)) + +(fn original_len_after_append () -> i32 + (let values (vec bool) (std.vec.bool.empty)) + (let appended (vec bool) (std.vec.bool.append values true)) + (std.vec.bool.len values)) + +(fn same_pair () -> bool + (= (pair) + (std.vec.bool.append (std.vec.bool.append (std.vec.bool.empty) true) false))) + +(test "vec bool empty length" + (= (std.vec.bool.len (empty_values)) 0)) + +(test "vec bool append length" + (= (length (pair)) 2)) + +(test "vec bool index" + (= (at (pair) 1) false)) + +(test "vec bool append is immutable" + (= (original_len_after_append) 0)) + +(test "vec bool equality" + (same_pair)) + +(fn main () -> i32 + (std.io.print_bool (call_return_value)) + 0) +"# +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn write_fixture(name: &str, source: &str) -> PathBuf { + let mut path = env::temp_dir(); + path.push(format!( + "glagol-collections-alpha-{}-{}-{}.slo", + name, + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + fs::write(&path, source).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err)); + path +} + +fn write_vec_project() -> PathBuf { + let root = env::temp_dir().join(format!( + "glagol-collections-alpha-project-{}-{}", + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + let src = root.join("src"); + fs::create_dir_all(&src).unwrap_or_else(|err| panic!("create `{}`: {}", src.display(), err)); + fs::write( + root.join("slovo.toml"), + "[project]\nname = \"vecproj\"\nsource_root = \"src\"\nentry = \"main\"\n", + ) + .unwrap_or_else(|err| panic!("write project manifest: {}", err)); + fs::write( + src.join("math.slo"), + r#"(module math (export singleton length)) + +(fn singleton ((value i32)) -> (vec i32) + (std.vec.i32.append (std.vec.i32.empty) value)) + +(fn length ((values (vec i32))) -> i32 + (std.vec.i32.len values)) +"#, + ) + .unwrap_or_else(|err| panic!("write math module: {}", err)); + fs::write( + src.join("main.slo"), + r#"(module main) + +(import math (singleton length)) + +(fn main () -> i32 + (length (singleton 7))) + +(test "project vector import" + (= (length (singleton 7)) 1)) +"#, + ) + .unwrap_or_else(|err| panic!("write main module: {}", err)); + root +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); +} + +fn assert_success_stdout(output: Output, expected: &str, context: &str) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert_eq!(stdout, expected, "{} stdout drifted", context); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} + +fn compile_and_run_with_runtime(clang: &Path, name: &str, ir: &[u8]) -> Output { + let manifest = Path::new(env!("CARGO_MANIFEST_DIR")); + let temp_dir = env::temp_dir().join(format!( + "glagol-vec-runtime-{}-{}", + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + fs::create_dir_all(&temp_dir) + .unwrap_or_else(|err| panic!("create `{}`: {}", temp_dir.display(), err)); + + let ir_path = temp_dir.join(format!("{}.ll", name)); + let exe_path = temp_dir.join(name); + fs::write(&ir_path, ir).unwrap_or_else(|err| panic!("write `{}`: {}", ir_path.display(), err)); + + let runtime = manifest.join("../runtime/runtime.c"); + let mut clang_command = Command::new(clang); + clang_command + .arg(&runtime) + .arg(&ir_path) + .arg("-o") + .arg(&exe_path) + .current_dir(manifest); + configure_clang_runtime_env(&mut clang_command, clang); + let clang_output = clang_command + .output() + .unwrap_or_else(|err| panic!("run `{}`: {}", clang.display(), err)); + assert_success("clang vec runtime smoke", &clang_output); + + Command::new(&exe_path) + .output() + .unwrap_or_else(|err| panic!("run `{}`: {}", exe_path.display(), err)) +} + +fn find_clang() -> Option { + if let Some(path) = env::var_os("GLAGOL_CLANG").filter(|value| !value.is_empty()) { + return Some(PathBuf::from(path)); + } + + let hermetic_clang = PathBuf::from("/tmp/glagol-clang-root/usr/bin/clang"); + if hermetic_clang.is_file() { + return Some(hermetic_clang); + } + + find_on_path("clang") +} + +fn find_on_path(program: &str) -> Option { + let path = env::var_os("PATH")?; + env::split_paths(&path) + .map(|dir| dir.join(program)) + .find(|candidate| candidate.is_file()) +} + +fn configure_clang_runtime_env(command: &mut Command, clang: &Path) { + if !clang.starts_with("/tmp/glagol-clang-root") { + return; + } + + let root = Path::new("/tmp/glagol-clang-root"); + let lib64 = root.join("usr/lib64"); + let lib = root.join("usr/lib"); + let existing = env::var_os("LD_LIBRARY_PATH").unwrap_or_default(); + let mut paths = vec![lib64, lib]; + paths.extend(env::split_paths(&existing)); + let joined = env::join_paths(paths).expect("join LD_LIBRARY_PATH"); + command.env("LD_LIBRARY_PATH", joined); +} diff --git a/compiler/tests/composite_struct_fields_alpha.rs b/compiler/tests/composite_struct_fields_alpha.rs new file mode 100644 index 0000000..7985376 --- /dev/null +++ b/compiler/tests/composite_struct_fields_alpha.rs @@ -0,0 +1,124 @@ +use std::{ + ffi::OsStr, + path::{Path, PathBuf}, + process::{Command, Output}, +}; + +#[test] +fn composite_struct_fields_alpha_fixture_emits_composite_fields_and_tests_pass() { + let fixture = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/composite-struct-fields.slo"); + let compile = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + + assert!( + compile.status.success(), + "compiler rejected composite struct field fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains( + "define { ptr, ptr, ptr, ptr, ptr, { i1, i32 }, { i1, i64 }, { i1, double }, { i1, i1 }, { i1, ptr }, { i1, i32 }, { i1, i64, i32 }, { i1, double, i32 }, { i1, i1, i32 }, { i1, ptr, i32 }, { i32, i32 }, i32 } @make_record()" + ) && stdout.contains("define i32 @pair_total(") + && stdout.contains("extractvalue { i32, i32 }") + && stdout.contains("extractvalue { i1, i64 }") + && stdout.contains("extractvalue { i1, ptr, i32 }"), + "LLVM output did not contain expected composite struct field shape\nstdout:\n{}", + stdout + ); + assert!(stderr.is_empty(), "stderr was not empty:\n{}", stderr); + + let run = run_glagol([OsStr::new("--run-tests"), fixture.as_os_str()]); + assert_success_stdout( + run, + concat!( + "test \"struct vec i32 field access\" ... ok\n", + "test \"struct vec i64 field access\" ... ok\n", + "test \"struct vec f64 field access\" ... ok\n", + "test \"struct vec bool field access\" ... ok\n", + "test \"struct vec string field access\" ... ok\n", + "test \"struct option i32 field access\" ... ok\n", + "test \"struct option i64 field access\" ... ok\n", + "test \"struct option f64 field access\" ... ok\n", + "test \"struct option bool field access\" ... ok\n", + "test \"struct option string field access\" ... ok\n", + "test \"struct result i32 field access\" ... ok\n", + "test \"struct result i64 field access\" ... ok\n", + "test \"struct result f64 field access\" ... ok\n", + "test \"struct result bool field access\" ... ok\n", + "test \"struct result string field access\" ... ok\n", + "test \"nested struct field access\" ... ok\n", + "test \"nested struct local param return call flow\" ... ok\n", + "test \"direct enum struct field equality\" ... ok\n", + "18 test(s) passed\n", + ), + ); +} + +#[test] +fn composite_struct_fields_alpha_formatter_and_lowering_are_visible() { + let fixture = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/composite-struct-fields.slo"); + + let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]); + assert_success_stdout( + formatted, + &std::fs::read_to_string(&fixture).expect("read composite struct field fixture"), + ); + + let surface = run_glagol([ + OsStr::new("--inspect-lowering=surface"), + fixture.as_os_str(), + ]); + assert_success_stdout( + surface, + &std::fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../tests/composite-struct-fields.surface.lower"), + ) + .expect("read surface snapshot"), + ); + + let checked = run_glagol([ + OsStr::new("--inspect-lowering=checked"), + fixture.as_os_str(), + ]); + assert_success_stdout( + checked, + &std::fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../tests/composite-struct-fields.checked.lower"), + ) + .expect("read checked snapshot"), + ); +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(glagol_bin()) + .args(args) + .output() + .expect("run glagol") +} + +fn glagol_bin() -> PathBuf { + PathBuf::from(env!("CARGO_BIN_EXE_glagol")) +} + +fn assert_success_stdout(output: Output, expected: &str) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "command failed\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert_eq!(stdout, expected); + assert!(stderr.is_empty(), "stderr was not empty:\n{}", stderr); +} diff --git a/compiler/tests/diagnostics_contract.rs b/compiler/tests/diagnostics_contract.rs new file mode 100644 index 0000000..a14e7d2 --- /dev/null +++ b/compiler/tests/diagnostics_contract.rs @@ -0,0 +1,3899 @@ +use std::{ + fs, + path::PathBuf, + process::{Command, Output}, + sync::atomic::{AtomicUsize, Ordering}, +}; + +static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0); + +macro_rules! unsafe_required_source { + ($head:literal) => { + concat!( + "\n(module main)\n\n(fn main () -> i32\n (", + $head, + " i32))\n" + ) + }; +} + +macro_rules! unsupported_unsafe_source { + ($head:literal) => { + concat!( + "\n(module main)\n\n(fn main () -> i32\n (unsafe\n (", + $head, + " i32)))\n" + ) + }; +} + +const CASES: &[DiagnosticCase] = &[ + DiagnosticCase { + name: "unknown-function", + source: r#" +(module main) + +(fn main () -> i32 + (missing 1)) +"#, + snapshot: "../tests/unknown-function.diag", + }, + DiagnosticCase { + name: "arity-mismatch", + source: r#" +(module main) + +(fn add ((a i32) (b i32)) -> i32 + (+ a b)) + +(fn main () -> i32 + (add 1)) +"#, + snapshot: "../tests/arity-mismatch.diag", + }, + DiagnosticCase { + name: "type-mismatch", + source: r#" +(module main) + +(fn id ((value i32)) -> i32 + value) + +(fn main () -> i32 + (id true)) +"#, + snapshot: "../tests/type-mismatch.diag", + }, + DiagnosticCase { + name: "unclosed-list", + source: r#" +(module main) + +(fn main () -> i32 + (+ 1 2) +"#, + snapshot: "../tests/unclosed-list.diag", + }, + DiagnosticCase { + name: "unknown-top-level-form", + source: r#" +(module main) + +(bogus top level) +"#, + snapshot: "../tests/unknown-top-level-form.diag", + }, + DiagnosticCase { + name: "enum-empty", + source: r#" +(module main) + +(enum Empty) + +(fn main () -> i32 + 0) +"#, + snapshot: "../tests/enum-empty.diag", + }, + DiagnosticCase { + name: "enum-duplicate", + source: r#" +(module main) + +(enum Color Red) +(enum Color Blue) + +(fn main () -> i32 + 0) +"#, + snapshot: "../tests/enum-duplicate.diag", + }, + DiagnosticCase { + name: "enum-duplicate-variant", + source: r#" +(module main) + +(enum Color Red Red) + +(fn main () -> i32 + 0) +"#, + snapshot: "../tests/enum-duplicate-variant.diag", + }, + DiagnosticCase { + name: "enum-unknown-constructor", + source: r#" +(module main) + +(fn main () -> i32 + (Missing.Red)) +"#, + snapshot: "../tests/enum-unknown-constructor.diag", + }, + DiagnosticCase { + name: "enum-unknown-variant", + source: r#" +(module main) + +(enum Color Red) + +(fn main () -> Color + (Color.Blue)) +"#, + snapshot: "../tests/enum-unknown-variant.diag", + }, + DiagnosticCase { + name: "enum-constructor-arity", + source: r#" +(module main) + +(enum Color Red) + +(fn main () -> Color + (Color.Red 1)) +"#, + snapshot: "../tests/enum-constructor-arity.diag", + }, + DiagnosticCase { + name: "enum-match-non-exhaustive", + source: r#" +(module main) + +(enum Color Red Blue) + +(fn main () -> i32 + (match (Color.Red) + ((Color.Red) 1))) +"#, + snapshot: "../tests/enum-match-non-exhaustive.diag", + }, + DiagnosticCase { + name: "enum-match-duplicate-arm", + source: r#" +(module main) + +(enum Color Red Blue) + +(fn main () -> i32 + (match (Color.Red) + ((Color.Red) 1) + ((Color.Red) 2) + ((Color.Blue) 3))) +"#, + snapshot: "../tests/enum-match-duplicate-arm.diag", + }, + DiagnosticCase { + name: "enum-match-invalid-arm", + source: r#" +(module main) + +(enum Color Red) +(enum Shape Circle) + +(fn main () -> i32 + (match (Color.Red) + ((Shape.Circle) 1))) +"#, + snapshot: "../tests/enum-match-invalid-arm.diag", + }, + DiagnosticCase { + name: "enum-container-values", + source: r#" +(module main) + +(enum Color Red) + +(fn main () -> i32 + (let colors (array Color 1) (array Color (Color.Red))) + (let maybe (option Color) (some Color (Color.Red))) + 0) +"#, + snapshot: "../tests/enum-container-values.diag", + }, + DiagnosticCase { + name: "enum-struct-fields-container", + source: r#" +(module main) + +(enum Color Red) + +(struct Bag + (colors (array Color 1)) + (maybe (option Color))) + +(fn main () -> i32 + 0) +"#, + snapshot: "../tests/enum-struct-fields-container.diag", + }, + DiagnosticCase { + name: "enum-printing", + source: r#" +(module main) + +(enum Color Red) + +(fn main () -> i32 + (print_i32 (Color.Red)) + 0) +"#, + snapshot: "../tests/enum-printing.diag", + }, + DiagnosticCase { + name: "enum-subject-mismatch", + source: r#" +(module main) + +(enum Color Red) +(enum Shape Circle) + +(fn main () -> bool + (= (Color.Red) (Shape.Circle))) +"#, + snapshot: "../tests/enum-subject-mismatch.diag", + }, + DiagnosticCase { + name: "enum-ordering", + source: r#" +(module main) + +(enum Color Red Blue) + +(fn main () -> bool + (< (Color.Red) (Color.Blue))) +"#, + snapshot: "../tests/enum-ordering.diag", + }, + DiagnosticCase { + name: "enum-match-payload-binding", + source: r#" +(module main) + +(enum Color Red Blue) + +(fn main () -> i32 + (match (Color.Red) + ((Color.Red payload) 1) + ((Color.Blue) 2))) +"#, + snapshot: "../tests/enum-match-payload-binding.diag", + }, + DiagnosticCase { + name: "enum-payload-unsupported-type", + source: r#" +(module main) + +(enum Reading + (Value (vec i32))) + +(fn main () -> i32 + 0) +"#, + snapshot: "../tests/enum-payload-unsupported-type.diag", + }, + DiagnosticCase { + name: "enum-payload-mixed-types", + source: r#" +(module main) + +(enum Reading + (Value i32) + (Message string)) + +(fn main () -> i32 + 0) +"#, + snapshot: "../tests/enum-payload-mixed-types.diag", + }, + DiagnosticCase { + name: "enum-payload-struct-equality", + source: r#" +(module main) + +(struct Pair + (left i32) + (right i32)) + +(enum Reading + Missing + (Value Pair)) + +(fn main () -> bool + (= (Reading.Value (Pair (left 1) (right 2))) + (Reading.Value (Pair (left 1) (right 2))))) +"#, + snapshot: "../tests/enum-payload-struct-equality.diag", + }, + DiagnosticCase { + name: "enum-payload-recursive-struct", + source: r#" +(module main) + +(struct Node + (next Wrapper)) + +(enum Wrapper + Missing + (Value Node)) + +(fn main () -> i32 + 0) +"#, + snapshot: "../tests/enum-payload-recursive-struct.diag", + }, + DiagnosticCase { + name: "enum-payload-constructor-arity", + source: r#" +(module main) + +(enum Reading + (Value string)) + +(fn main () -> Reading + (Reading.Value)) +"#, + snapshot: "../tests/enum-payload-constructor-arity.diag", + }, + DiagnosticCase { + name: "enum-payload-constructor-type", + source: r#" +(module main) + +(enum Reading + (Value string)) + +(fn main () -> Reading + (Reading.Value 1)) +"#, + snapshot: "../tests/enum-payload-constructor-type.diag", + }, + DiagnosticCase { + name: "enum-payload-match-missing-binding", + source: r#" +(module main) + +(enum Reading + Missing + (Value i32)) + +(fn main () -> i32 + (match (Reading.Value 1) + ((Reading.Missing) 0) + ((Reading.Value) 1))) +"#, + snapshot: "../tests/enum-payload-match-missing-binding.diag", + }, + DiagnosticCase { + name: "enum-unqualified-variant-constructor", + source: r#" +(module main) + +(enum Color Red) + +(fn main () -> Color + (Red)) +"#, + snapshot: "../tests/enum-unqualified-variant-constructor.diag", + }, + DiagnosticCase { + name: "unsupported-string-escape", + source: r#" +(module main) + +(fn main () -> i32 + (print_string "\0") + 0) +"#, + snapshot: "../tests/unsupported-string-escape.diag", + }, + DiagnosticCase { + name: "unsupported-string-literal", + source: concat!( + "\n(module main)\n\n(fn main () -> i32\n (print_string \"zdravo ", + "\u{017E}", + "\")\n 0)\n" + ), + snapshot: "../tests/unsupported-string-literal.diag", + }, + DiagnosticCase { + name: "unsupported-string-concatenation", + source: r#" +(module main) + +(fn main () -> string + (+ "a" "b")) +"#, + snapshot: "../tests/unsupported-string-concatenation.diag", + }, + DiagnosticCase { + name: "unsupported-string-array", + source: r#" +(module main) + +(fn main () -> i32 + (index (array (array string 1) (array string "a")) 0)) +"#, + snapshot: "../tests/unsupported-string-array.diag", + }, + DiagnosticCase { + name: "unsupported-print-unit", + source: r#" +(module main) + +(fn main () -> i32 + (print_unit 0) + 0) +"#, + snapshot: "../tests/unsupported-print-unit.diag", + }, + DiagnosticCase { + name: "unsupported-standard-library-call", + source: r#" +(module main) + +(fn main () -> i32 + (std.io.print_unit 0) + 0) +"#, + snapshot: "../tests/unsupported-standard-library-call.diag", + }, + DiagnosticCase { + name: "std-num-i32-to-i64-arity", + source: r#" +(module main) + +(fn main () -> i64 + (std.num.i32_to_i64)) +"#, + snapshot: "../tests/std-num-i32-to-i64-arity.diag", + }, + DiagnosticCase { + name: "std-num-i32-to-f64-type", + source: r#" +(module main) + +(fn main () -> f64 + (std.num.i32_to_f64 1i64)) +"#, + snapshot: "../tests/std-num-i32-to-f64-type.diag", + }, + DiagnosticCase { + name: "std-num-i64-to-i32-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.num.i64_to_i32 1i64) + 0) +"#, + snapshot: "../tests/std-num-i64-to-i32-unsupported.diag", + }, + DiagnosticCase { + name: "std-num-i64-to-i32-result-arity", + source: r#" +(module main) + +(fn main () -> (result i32 i32) + (std.num.i64_to_i32_result)) +"#, + snapshot: "../tests/std-num-i64-to-i32-result-arity.diag", + }, + DiagnosticCase { + name: "std-num-i64-to-i32-result-type", + source: r#" +(module main) + +(fn main () -> (result i32 i32) + (std.num.i64_to_i32_result 1)) +"#, + snapshot: "../tests/std-num-i64-to-i32-result-type.diag", + }, + DiagnosticCase { + name: "std-num-i32-to-string-arity", + source: r#" +(module main) + +(fn main () -> string + (std.num.i32_to_string)) +"#, + snapshot: "../tests/std-num-i32-to-string-arity.diag", + }, + DiagnosticCase { + name: "std-num-i64-to-string-type", + source: r#" +(module main) + +(fn main () -> string + (std.num.i64_to_string 1)) +"#, + snapshot: "../tests/std-num-i64-to-string-type.diag", + }, + DiagnosticCase { + name: "std-num-f64-to-string-arity", + source: r#" +(module main) + +(fn main () -> string + (std.num.f64_to_string)) +"#, + snapshot: "../tests/std-num-f64-to-string-arity.diag", + }, + DiagnosticCase { + name: "std-num-f64-to-string-type", + source: r#" +(module main) + +(fn main () -> string + (std.num.f64_to_string 1)) +"#, + snapshot: "../tests/std-num-f64-to-string-type.diag", + }, + DiagnosticCase { + name: "std-num-f64-to-string-context", + source: r#" +(module main) + +(fn main () -> i32 + (std.num.f64_to_string 1.0) +) +"#, + snapshot: "../tests/std-num-f64-to-string-context.diag", + }, + DiagnosticCase { + name: "std-num-f64-to-string-bool-context", + source: r#" +(module main) + +(fn main () -> i32 + (if (std.num.f64_to_string 1.0) 1 0)) +"#, + snapshot: "../tests/std-num-f64-to-string-bool-context.diag", + }, + DiagnosticCase { + name: "std-num-f64-to-string-name-shadow", + source: r#" +(module main) + +(fn std.num.f64_to_string ((value f64)) -> string + "0.0") + +(fn main () -> i32 + 0) +"#, + snapshot: "../tests/std-num-f64-to-string-name-shadow.diag", + }, + DiagnosticCase { + name: "std-num-f64-to-string-helper-shadow", + source: r#" +(module main) + +(fn __glagol_num_f64_to_string ((value f64)) -> string + "0.0") + +(fn main () -> i32 + 0) +"#, + snapshot: "../tests/std-num-f64-to-string-helper-shadow.diag", + }, + DiagnosticCase { + name: "std-num-to-string-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.num.to_string 1) + 0) +"#, + snapshot: "../tests/std-num-to-string-unsupported.diag", + }, + DiagnosticCase { + name: "std-string-from-i64-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.string.from_i64 1i64) + 0) +"#, + snapshot: "../tests/std-string-from-i64-unsupported.diag", + }, + DiagnosticCase { + name: "std-num-i32-to-i64-result-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.num.i32_to_i64_result 1) + 0) +"#, + snapshot: "../tests/std-num-i32-to-i64-result-unsupported.diag", + }, + DiagnosticCase { + name: "std-num-f64-to-i32-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.num.f64_to_i32 1.0) + 0) +"#, + snapshot: "../tests/std-num-f64-to-i32-unsupported.diag", + }, + DiagnosticCase { + name: "std-num-f64-to-i64-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.num.f64_to_i64 1.0) + 0) +"#, + snapshot: "../tests/std-num-f64-to-i64-unsupported.diag", + }, + DiagnosticCase { + name: "std-num-f64-to-i32-result-arity", + source: r#" +(module main) + +(fn main () -> (result i32 i32) + (std.num.f64_to_i32_result)) +"#, + snapshot: "../tests/std-num-f64-to-i32-result-arity.diag", + }, + DiagnosticCase { + name: "std-num-f64-to-i32-result-type", + source: r#" +(module main) + +(fn main () -> (result i32 i32) + (std.num.f64_to_i32_result 1)) +"#, + snapshot: "../tests/std-num-f64-to-i32-result-type.diag", + }, + DiagnosticCase { + name: "std-num-f64-to-i32-result-context", + source: r#" +(module main) + +(fn main () -> i32 + (std.num.f64_to_i32_result 1.0)) +"#, + snapshot: "../tests/std-num-f64-to-i32-result-context.diag", + }, + DiagnosticCase { + name: "std-num-f64-to-i32-result-bool-context", + source: r#" +(module main) + +(fn main () -> i32 + (if (std.num.f64_to_i32_result 1.0) 1 0)) +"#, + snapshot: "../tests/std-num-f64-to-i32-result-bool-context.diag", + }, + DiagnosticCase { + name: "std-num-f64-to-i32-result-name-shadow", + source: r#" +(module main) + +(fn std.num.f64_to_i32_result ((value f64)) -> (result i32 i32) + (ok 0)) + +(fn main () -> i32 + 0) +"#, + snapshot: "../tests/std-num-f64-to-i32-result-name-shadow.diag", + }, + DiagnosticCase { + name: "std-num-f64-to-i64-result-arity", + source: r#" +(module main) + +(fn main () -> (result i64 i32) + (std.num.f64_to_i64_result)) +"#, + snapshot: "../tests/std-num-f64-to-i64-result-arity.diag", + }, + DiagnosticCase { + name: "std-num-f64-to-i64-result-type", + source: r#" +(module main) + +(fn main () -> (result i64 i32) + (std.num.f64_to_i64_result 1)) +"#, + snapshot: "../tests/std-num-f64-to-i64-result-type.diag", + }, + DiagnosticCase { + name: "std-num-f64-to-i64-result-context", + source: r#" +(module main) + +(fn main () -> i32 + (std.num.f64_to_i64_result 1.0)) +"#, + snapshot: "../tests/std-num-f64-to-i64-result-context.diag", + }, + DiagnosticCase { + name: "std-num-f64-to-i64-result-bool-context", + source: r#" +(module main) + +(fn main () -> i32 + (if (std.num.f64_to_i64_result 1.0) 1 0)) +"#, + snapshot: "../tests/std-num-f64-to-i64-result-bool-context.diag", + }, + DiagnosticCase { + name: "std-num-f64-to-i64-result-name-shadow", + source: r#" +(module main) + +(fn std.num.f64_to_i64_result ((value f64)) -> (result i64 i32) + (ok 0i64)) + +(fn main () -> i32 + 0) +"#, + snapshot: "../tests/std-num-f64-to-i64-result-name-shadow.diag", + }, + DiagnosticCase { + name: "std-num-cast-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.num.cast 1) + 0) +"#, + snapshot: "../tests/std-num-cast-unsupported.diag", + }, + DiagnosticCase { + name: "std-num-cast-checked-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.num.cast_checked 1) + 0) +"#, + snapshot: "../tests/std-num-cast-checked-unsupported.diag", + }, + DiagnosticCase { + name: "print-bool-arity", + source: r#" +(module main) + +(fn main () -> i32 + (print_bool true false) + 0) +"#, + snapshot: "../tests/print-bool-arity.diag", + }, + DiagnosticCase { + name: "std-print-bool-arity", + source: r#" +(module main) + +(fn main () -> i32 + (std.io.print_bool true false) + 0) +"#, + snapshot: "../tests/std-print-bool-arity.diag", + }, + DiagnosticCase { + name: "print-bool-type", + source: r#" +(module main) + +(fn main () -> i32 + (print_bool 1) + 0) +"#, + snapshot: "../tests/print-bool-type.diag", + }, + DiagnosticCase { + name: "string-len-arity", + source: r#" +(module main) + +(fn main () -> i32 + (string_len "a" "b")) +"#, + snapshot: "../tests/string-len-arity.diag", + }, + DiagnosticCase { + name: "string-len-type", + source: r#" +(module main) + +(fn main () -> i32 + (string_len 1)) +"#, + snapshot: "../tests/string-len-type.diag", + }, + DiagnosticCase { + name: "std-string-len-type", + source: r#" +(module main) + +(fn main () -> i32 + (std.string.len 1)) +"#, + snapshot: "../tests/std-string-len-type.diag", + }, + DiagnosticCase { + name: "std-string-concat-arity", + source: r#" +(module main) + +(fn main () -> string + (std.string.concat "a")) +"#, + snapshot: "../tests/std-string-concat-arity.diag", + }, + DiagnosticCase { + name: "std-string-concat-type", + source: r#" +(module main) + +(fn main () -> string + (std.string.concat 1 "a")) +"#, + snapshot: "../tests/std-string-concat-type.diag", + }, + DiagnosticCase { + name: "std-string-concat-result-context", + source: r#" +(module main) + +(fn main () -> i32 + (std.string.concat "a" "b")) +"#, + snapshot: "../tests/std-string-concat-result-context.diag", + }, + DiagnosticCase { + name: "std-string-concat-helper-shadow", + source: r#" +(module main) + +(fn __glagol_string_concat ((left string) (right string)) -> string + "intercepted") + +(fn main () -> string + (std.string.concat "a" "b")) +"#, + snapshot: "../tests/std-string-concat-helper-shadow.diag", + }, + DiagnosticCase { + name: "std-string-concat-unsupported-string-container", + source: r#" +(module main) + +(fn main () -> string + (std.string.concat (array string "a") "b")) +"#, + snapshot: "../tests/std-string-concat-unsupported-string-container.diag", + }, + DiagnosticCase { + name: "std-string-parse-i32-result-arity", + source: r#" +(module main) + +(fn main () -> (result i32 i32) + (std.string.parse_i32_result)) +"#, + snapshot: "../tests/std-string-parse-i32-result-arity.diag", + }, + DiagnosticCase { + name: "std-string-parse-i32-result-type", + source: r#" +(module main) + +(fn main () -> (result i32 i32) + (std.string.parse_i32_result 1)) +"#, + snapshot: "../tests/std-string-parse-i32-result-type.diag", + }, + DiagnosticCase { + name: "std-string-parse-i32-result-context", + source: r#" +(module main) + +(fn main () -> i32 + (std.string.parse_i32_result "42")) +"#, + snapshot: "../tests/std-string-parse-i32-result-context.diag", + }, + DiagnosticCase { + name: "std-string-parse-i32-result-bool-context", + source: r#" +(module main) + +(fn main () -> i32 + (if (std.string.parse_i32_result "42") 1 0)) +"#, + snapshot: "../tests/std-string-parse-i32-result-bool-context.diag", + }, + DiagnosticCase { + name: "std-string-parse-i32-result-name-shadow", + source: r#" +(module main) + +(fn std.string.parse_i32_result ((text string)) -> (result i32 i32) + (err i32 i32 1)) + +(fn main () -> i32 + 0) +"#, + snapshot: "../tests/std-string-parse-i32-result-name-shadow.diag", + }, + DiagnosticCase { + name: "std-string-parse-i32-result-helper-shadow", + source: r#" +(module main) + +(fn __glagol_string_parse_i32_result ((text string)) -> (result i32 i32) + (err i32 i32 1)) + +(fn main () -> i32 + 0) +"#, + snapshot: "../tests/std-string-parse-i32-result-helper-shadow.diag", + }, + DiagnosticCase { + name: "std-string-parse-i64-result-arity", + source: r#" +(module main) + +(fn main () -> (result i64 i32) + (std.string.parse_i64_result)) +"#, + snapshot: "../tests/std-string-parse-i64-result-arity.diag", + }, + DiagnosticCase { + name: "std-string-parse-i64-result-type", + source: r#" +(module main) + +(fn main () -> (result i64 i32) + (std.string.parse_i64_result 1)) +"#, + snapshot: "../tests/std-string-parse-i64-result-type.diag", + }, + DiagnosticCase { + name: "std-string-parse-i64-result-context", + source: r#" +(module main) + +(fn main () -> i32 + (std.string.parse_i64_result "42")) +"#, + snapshot: "../tests/std-string-parse-i64-result-context.diag", + }, + DiagnosticCase { + name: "std-string-parse-i64-result-bool-context", + source: r#" +(module main) + +(fn main () -> i32 + (if (std.string.parse_i64_result "42") 1 0)) +"#, + snapshot: "../tests/std-string-parse-i64-result-bool-context.diag", + }, + DiagnosticCase { + name: "std-string-parse-i64-result-name-shadow", + source: r#" +(module main) + +(fn std.string.parse_i64_result ((text string)) -> (result i64 i32) + (err i64 i32 1)) + +(fn main () -> i32 + 0) +"#, + snapshot: "../tests/std-string-parse-i64-result-name-shadow.diag", + }, + DiagnosticCase { + name: "std-string-parse-i64-result-helper-shadow", + source: r#" +(module main) + +(fn __glagol_string_parse_i64_result ((text string)) -> (result i64 i32) + (err i64 i32 1)) + +(fn main () -> i32 + 0) +"#, + snapshot: "../tests/std-string-parse-i64-result-helper-shadow.diag", + }, + DiagnosticCase { + name: "std-string-parse-f64-result-arity", + source: r#" +(module main) + +(fn main () -> (result f64 i32) + (std.string.parse_f64_result)) +"#, + snapshot: "../tests/std-string-parse-f64-result-arity.diag", + }, + DiagnosticCase { + name: "std-string-parse-f64-result-type", + source: r#" +(module main) + +(fn main () -> (result f64 i32) + (std.string.parse_f64_result 1)) +"#, + snapshot: "../tests/std-string-parse-f64-result-type.diag", + }, + DiagnosticCase { + name: "std-string-parse-f64-result-context", + source: r#" +(module main) + +(fn main () -> i32 + (std.string.parse_f64_result "1.0")) +"#, + snapshot: "../tests/std-string-parse-f64-result-context.diag", + }, + DiagnosticCase { + name: "std-string-parse-f64-result-bool-context", + source: r#" +(module main) + +(fn main () -> i32 + (if (std.string.parse_f64_result "1.0") 1 0)) +"#, + snapshot: "../tests/std-string-parse-f64-result-bool-context.diag", + }, + DiagnosticCase { + name: "std-string-parse-f64-result-name-shadow", + source: r#" +(module main) + +(fn std.string.parse_f64_result ((text string)) -> (result f64 i32) + (std.string.parse_f64_result text)) + +(fn main () -> i32 + 0) +"#, + snapshot: "../tests/std-string-parse-f64-result-name-shadow.diag", + }, + DiagnosticCase { + name: "std-string-parse-f64-result-helper-shadow", + source: r#" +(module main) + +(fn __glagol_string_parse_f64_result ((text string)) -> (result f64 i32) + (std.string.parse_f64_result text)) + +(fn main () -> i32 + 0) +"#, + snapshot: "../tests/std-string-parse-f64-result-helper-shadow.diag", + }, + DiagnosticCase { + name: "std-string-parse-bool-result-arity", + source: r#" +(module main) + +(fn main () -> (result bool i32) + (std.string.parse_bool_result)) +"#, + snapshot: "../tests/std-string-parse-bool-result-arity.diag", + }, + DiagnosticCase { + name: "std-string-parse-bool-result-type", + source: r#" +(module main) + +(fn main () -> (result bool i32) + (std.string.parse_bool_result 1)) +"#, + snapshot: "../tests/std-string-parse-bool-result-type.diag", + }, + DiagnosticCase { + name: "std-string-parse-bool-result-context", + source: r#" +(module main) + +(fn main () -> i32 + (std.string.parse_bool_result "true")) +"#, + snapshot: "../tests/std-string-parse-bool-result-context.diag", + }, + DiagnosticCase { + name: "std-string-parse-bool-result-bool-context", + source: r#" +(module main) + +(fn main () -> i32 + (if (std.string.parse_bool_result "true") 1 0)) +"#, + snapshot: "../tests/std-string-parse-bool-result-bool-context.diag", + }, + DiagnosticCase { + name: "std-string-parse-bool-result-name-shadow", + source: r#" +(module main) + +(fn std.string.parse_bool_result ((text string)) -> (result bool i32) + (std.string.parse_bool_result text)) + +(fn main () -> i32 + 0) +"#, + snapshot: "../tests/std-string-parse-bool-result-name-shadow.diag", + }, + DiagnosticCase { + name: "std-string-parse-bool-result-helper-shadow", + source: r#" +(module main) + +(fn __glagol_string_parse_bool_result ((text string)) -> (result bool i32) + (std.string.parse_bool_result text)) + +(fn main () -> i32 + 0) +"#, + snapshot: "../tests/std-string-parse-bool-result-helper-shadow.diag", + }, + DiagnosticCase { + name: "std-string-parse-i32-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.string.parse_i32 "42") + 0) +"#, + snapshot: "../tests/std-string-parse-i32-unsupported.diag", + }, + DiagnosticCase { + name: "std-string-parse-f64-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.string.parse_f64 "1.0") + 0) +"#, + snapshot: "../tests/std-string-parse-f64-unsupported.diag", + }, + DiagnosticCase { + name: "std-string-parse-bool-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.string.parse_bool "true") + 0) +"#, + snapshot: "../tests/std-string-parse-bool-unsupported.diag", + }, + DiagnosticCase { + name: "std-string-parse-string-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.string.parse_string_result "text") + 0) +"#, + snapshot: "../tests/std-string-parse-string-unsupported.diag", + }, + DiagnosticCase { + name: "std-string-parse-bytes-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.string.parse_bytes_result "42") + 0) +"#, + snapshot: "../tests/std-string-parse-bytes-unsupported.diag", + }, + DiagnosticCase { + name: "std-string-parse-i32-trim-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.string.parse_i32_trim_result " 42 ") + 0) +"#, + snapshot: "../tests/std-string-parse-i32-trim-unsupported.diag", + }, + DiagnosticCase { + name: "std-string-parse-i32-whitespace-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.string.parse_i32_whitespace_result " 42 ") + 0) +"#, + snapshot: "../tests/std-string-parse-i32-whitespace-unsupported.diag", + }, + DiagnosticCase { + name: "std-string-parse-i32-locale-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.string.parse_i32_locale_result "42" "hr-HR") + 0) +"#, + snapshot: "../tests/std-string-parse-i32-locale-unsupported.diag", + }, + DiagnosticCase { + name: "std-string-parse-i32-base-prefix-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.string.parse_i32_base_prefix_result "0x2a") + 0) +"#, + snapshot: "../tests/std-string-parse-i32-base-prefix-unsupported.diag", + }, + DiagnosticCase { + name: "std-string-parse-i32-binary-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.string.parse_i32_binary_result "101010") + 0) +"#, + snapshot: "../tests/std-string-parse-i32-binary-unsupported.diag", + }, + DiagnosticCase { + name: "std-string-parse-i32-octal-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.string.parse_i32_octal_result "52") + 0) +"#, + snapshot: "../tests/std-string-parse-i32-octal-unsupported.diag", + }, + DiagnosticCase { + name: "std-string-parse-i32-hex-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.string.parse_i32_hex_result "2a") + 0) +"#, + snapshot: "../tests/std-string-parse-i32-hex-unsupported.diag", + }, + DiagnosticCase { + name: "std-string-parse-i32-radix-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.string.parse_i32_radix_result "2a" 16) + 0) +"#, + snapshot: "../tests/std-string-parse-i32-radix-unsupported.diag", + }, + DiagnosticCase { + name: "std-string-parse-i32-underscore-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.string.parse_i32_underscore_result "1_000") + 0) +"#, + snapshot: "../tests/std-string-parse-i32-underscore-unsupported.diag", + }, + DiagnosticCase { + name: "std-string-parse-i32-plus-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.string.parse_i32_plus_result "+42") + 0) +"#, + snapshot: "../tests/std-string-parse-i32-plus-unsupported.diag", + }, + DiagnosticCase { + name: "std-string-parse-generic-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.string.parse_result "42") + 0) +"#, + snapshot: "../tests/std-string-parse-generic-unsupported.diag", + }, + DiagnosticCase { + name: "std-string-parse-i32-message-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.string.parse_i32_message_result "nope") + 0) +"#, + snapshot: "../tests/std-string-parse-i32-message-unsupported.diag", + }, + DiagnosticCase { + name: "std-string-parse-i32-code-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.string.parse_i32_code_result "nope") + 0) +"#, + snapshot: "../tests/std-string-parse-i32-code-unsupported.diag", + }, + DiagnosticCase { + name: "std-string-parse-i32-error-adt-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.string.parse_i32_error_result "nope") + 0) +"#, + snapshot: "../tests/std-string-parse-i32-error-adt-unsupported.diag", + }, + DiagnosticCase { + name: "std-string-parse-i32-unicode-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.string.parse_i32_unicode_result "42") + 0) +"#, + snapshot: "../tests/std-string-parse-i32-unicode-unsupported.diag", + }, + DiagnosticCase { + name: "std-string-index-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.string.index "42" 0)) +"#, + snapshot: "../tests/std-string-index-unsupported.diag", + }, + DiagnosticCase { + name: "std-string-slice-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.string.slice "42" 0 1) + 0) +"#, + snapshot: "../tests/std-string-slice-unsupported.diag", + }, + DiagnosticCase { + name: "std-string-tokenize-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.string.tokenize "1 2") + 0) +"#, + snapshot: "../tests/std-string-tokenize-unsupported.diag", + }, + DiagnosticCase { + name: "std-string-scan-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.string.scan "42") + 0) +"#, + snapshot: "../tests/std-string-scan-unsupported.diag", + }, + DiagnosticCase { + name: "std-io-eprint-arity", + source: r#" +(module main) + +(fn main () -> i32 + (std.io.eprint "a" "b") + 0) +"#, + snapshot: "../tests/std-io-eprint-arity.diag", + }, + DiagnosticCase { + name: "std-process-arg-type", + source: r#" +(module main) + +(fn main () -> string + (std.process.arg true)) +"#, + snapshot: "../tests/std-process-arg-type.diag", + }, + DiagnosticCase { + name: "std-env-get-type", + source: r#" +(module main) + +(fn main () -> string + (std.env.get 1)) +"#, + snapshot: "../tests/std-env-get-type.diag", + }, + DiagnosticCase { + name: "std-fs-write-text-arity", + source: r#" +(module main) + +(fn main () -> i32 + (std.fs.write_text "path")) +"#, + snapshot: "../tests/std-fs-write-text-arity.diag", + }, + DiagnosticCase { + name: "std-io-eprint-result-context", + source: r#" +(module main) + +(fn main () -> i32 + (std.io.eprint "not an i32")) +"#, + snapshot: "../tests/std-io-eprint-result-context.diag", + }, + DiagnosticCase { + name: "std-fs-read-text-helper-shadow", + source: r#" +(module main) + +(fn __glagol_fs_read_text ((path string)) -> string + "intercepted") + +(fn main () -> string + (std.fs.read_text "path")) +"#, + snapshot: "../tests/std-fs-read-text-helper-shadow.diag", + }, + DiagnosticCase { + name: "std-fs-read-binary-unsupported-alias", + source: r#" +(module main) + +(fn main () -> string + (std.fs.read_binary "path")) +"#, + snapshot: "../tests/std-fs-read-binary-unsupported-alias.diag", + }, + DiagnosticCase { + name: "std-net-connect-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.net.connect "host") + 0) +"#, + snapshot: "../tests/std-net-connect-unsupported.diag", + }, + DiagnosticCase { + name: "std-async-spawn-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.async.spawn 0) + 0) +"#, + snapshot: "../tests/std-async-spawn-unsupported.diag", + }, + DiagnosticCase { + name: "std-fs-list-dir-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.fs.list_dir ".") + 0) +"#, + snapshot: "../tests/std-fs-list-dir-unsupported.diag", + }, + DiagnosticCase { + name: "std-terminal-clear-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.terminal.clear) + 0) +"#, + snapshot: "../tests/std-terminal-clear-unsupported.diag", + }, + DiagnosticCase { + name: "std-platform-os-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.platform.os) + 0) +"#, + snapshot: "../tests/std-platform-os-unsupported.diag", + }, + DiagnosticCase { + name: "std-io-read-stdin-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.io.read_stdin) + 0) +"#, + snapshot: "../tests/std-io-read-stdin-unsupported.diag", + }, + DiagnosticCase { + name: "std-io-read-line-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.io.read_line) + 0) +"#, + snapshot: "../tests/std-io-read-line-unsupported.diag", + }, + DiagnosticCase { + name: "std-io-stdin-lines-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.io.stdin_lines) + 0) +"#, + snapshot: "../tests/std-io-stdin-lines-unsupported.diag", + }, + DiagnosticCase { + name: "std-io-prompt-unsupported", + source: r#" +(module main) + +(fn main () -> string + (std.io.prompt "name")) +"#, + snapshot: "../tests/std-io-prompt-unsupported.diag", + }, + DiagnosticCase { + name: "std-terminal-raw-mode-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.terminal.raw_mode true) + 0) +"#, + snapshot: "../tests/std-terminal-raw-mode-unsupported.diag", + }, + DiagnosticCase { + name: "std-terminal-echo-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.terminal.echo false) + 0) +"#, + snapshot: "../tests/std-terminal-echo-unsupported.diag", + }, + DiagnosticCase { + name: "std-terminal-is-tty-unsupported", + source: r#" +(module main) + +(fn main () -> bool + (std.terminal.is_tty)) +"#, + snapshot: "../tests/std-terminal-is-tty-unsupported.diag", + }, + DiagnosticCase { + name: "std-io-read-stdin-binary-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.io.read_stdin_binary) + 0) +"#, + snapshot: "../tests/std-io-read-stdin-binary-unsupported.diag", + }, + DiagnosticCase { + name: "std-io-read-stdin-bytes-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.io.read_stdin_bytes) + 0) +"#, + snapshot: "../tests/std-io-read-stdin-bytes-unsupported.diag", + }, + DiagnosticCase { + name: "std-io-stdin-stream-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.io.stdin_stream) + 0) +"#, + snapshot: "../tests/std-io-stdin-stream-unsupported.diag", + }, + DiagnosticCase { + name: "std-io-read-stdin-async-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.io.read_stdin_async) + 0) +"#, + snapshot: "../tests/std-io-read-stdin-async-unsupported.diag", + }, + DiagnosticCase { + name: "std-io-stdin-encoding-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.io.stdin_encoding) + 0) +"#, + snapshot: "../tests/std-io-stdin-encoding-unsupported.diag", + }, + DiagnosticCase { + name: "std-random-i32-arity", + source: r#" +(module main) + +(fn main () -> i32 + (std.random.i32 1)) +"#, + snapshot: "../tests/std-random-i32-arity.diag", + }, + DiagnosticCase { + name: "std-random-i32-bool-context", + source: r#" +(module main) + +(fn main () -> i32 + (if (std.random.i32) 1 0)) +"#, + snapshot: "../tests/std-random-i32-bool-context.diag", + }, + DiagnosticCase { + name: "std-random-i32-name-shadow", + source: r#" +(module main) + +(fn std.random.i32 () -> i32 + 0) + +(fn main () -> i32 + (std.random.i32)) +"#, + snapshot: "../tests/std-random-i32-name-shadow.diag", + }, + DiagnosticCase { + name: "std-random-i32-helper-shadow", + source: r#" +(module main) + +(fn __glagol_random_i32 () -> i32 + 0) + +(fn main () -> i32 + (std.random.i32)) +"#, + snapshot: "../tests/std-random-i32-helper-shadow.diag", + }, + DiagnosticCase { + name: "std-random-seed-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.random.seed 1) + 0) +"#, + snapshot: "../tests/std-random-seed-unsupported.diag", + }, + DiagnosticCase { + name: "std-random-range-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.random.range 0 10)) +"#, + snapshot: "../tests/std-random-range-unsupported.diag", + }, + DiagnosticCase { + name: "std-random-bytes-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.random.bytes 16) + 0) +"#, + snapshot: "../tests/std-random-bytes-unsupported.diag", + }, + DiagnosticCase { + name: "std-random-float-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.random.float) + 0) +"#, + snapshot: "../tests/std-random-float-unsupported.diag", + }, + DiagnosticCase { + name: "std-random-shuffle-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.random.shuffle (std.vec.i32.empty)) + 0) +"#, + snapshot: "../tests/std-random-shuffle-unsupported.diag", + }, + DiagnosticCase { + name: "std-random-string-unsupported", + source: r#" +(module main) + +(fn main () -> string + (std.random.string 8)) +"#, + snapshot: "../tests/std-random-string-unsupported.diag", + }, + DiagnosticCase { + name: "std-random-uuid-unsupported", + source: r#" +(module main) + +(fn main () -> string + (std.random.uuid)) +"#, + snapshot: "../tests/std-random-uuid-unsupported.diag", + }, + DiagnosticCase { + name: "std-random-crypto-i32-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.random.crypto_i32)) +"#, + snapshot: "../tests/std-random-crypto-i32-unsupported.diag", + }, + DiagnosticCase { + name: "std-time-now-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.time.now) + 0) +"#, + snapshot: "../tests/std-time-now-unsupported.diag", + }, + DiagnosticCase { + name: "std-time-monotonic-arity", + source: r#" +(module main) + +(fn main () -> i32 + (std.time.monotonic_ms 1)) +"#, + snapshot: "../tests/std-time-monotonic-arity.diag", + }, + DiagnosticCase { + name: "std-time-monotonic-result-context", + source: r#" +(module main) + +(fn main () -> i32 + (let elapsed string (std.time.monotonic_ms)) + 0) +"#, + snapshot: "../tests/std-time-monotonic-result-context.diag", + }, + DiagnosticCase { + name: "std-time-sleep-arity", + source: r#" +(module main) + +(fn main () -> i32 + (std.time.sleep_ms) + 0) +"#, + snapshot: "../tests/std-time-sleep-arity.diag", + }, + DiagnosticCase { + name: "std-time-sleep-type", + source: r#" +(module main) + +(fn main () -> i32 + (std.time.sleep_ms true) + 0) +"#, + snapshot: "../tests/std-time-sleep-type.diag", + }, + DiagnosticCase { + name: "std-time-monotonic-helper-shadow", + source: r#" +(module main) + +(fn __glagol_time_monotonic_ms () -> i32 + 0) + +(fn main () -> i32 + (std.time.monotonic_ms)) +"#, + snapshot: "../tests/std-time-monotonic-helper-shadow.diag", + }, + DiagnosticCase { + name: "std-time-monotonic-name-shadow", + source: r#" +(module main) + +(fn std.time.monotonic_ms () -> i32 + 0) + +(fn main () -> i32 + (std.time.monotonic_ms)) +"#, + snapshot: "../tests/std-time-monotonic-name-shadow.diag", + }, + DiagnosticCase { + name: "std-time-sleep-helper-shadow", + source: r#" +(module main) + +(fn __glagol_time_sleep_ms ((ms i32)) -> i32 + ms) + +(fn main () -> i32 + (std.time.sleep_ms 0) + 0) +"#, + snapshot: "../tests/std-time-sleep-helper-shadow.diag", + }, + DiagnosticCase { + name: "std-time-sleep-name-shadow", + source: r#" +(module main) + +(fn std.time.sleep_ms ((ms i32)) -> i32 + ms) + +(fn main () -> i32 + (std.time.sleep_ms 0) + 0) +"#, + snapshot: "../tests/std-time-sleep-name-shadow.diag", + }, + DiagnosticCase { + name: "std-process-arg-result-arity", + source: r#" +(module main) + +(fn main () -> i32 + (std.process.arg_result) + 0) +"#, + snapshot: "../tests/std-process-arg-result-arity.diag", + }, + DiagnosticCase { + name: "std-env-get-result-type", + source: r#" +(module main) + +(fn main () -> i32 + (std.env.get_result 1) + 0) +"#, + snapshot: "../tests/std-env-get-result-type.diag", + }, + DiagnosticCase { + name: "std-fs-write-text-result-arity", + source: r#" +(module main) + +(fn main () -> i32 + (std.fs.write_text_result "path") + 0) +"#, + snapshot: "../tests/std-fs-write-text-result-arity.diag", + }, + DiagnosticCase { + name: "std-fs-write-text-result-type", + source: r#" +(module main) + +(fn main () -> i32 + (std.fs.write_text_result "path" 1) + 0) +"#, + snapshot: "../tests/std-fs-write-text-result-type.diag", + }, + DiagnosticCase { + name: "std-process-arg-result-name-shadow", + source: r#" +(module main) + +(fn std.process.arg_result ((index i32)) -> (result string i32) + (err string i32 1)) + +(fn main () -> i32 + 0) +"#, + snapshot: "../tests/std-process-arg-result-name-shadow.diag", + }, + DiagnosticCase { + name: "std-process-arg-result-helper-shadow", + source: r#" +(module main) + +(fn __glagol_process_arg_result ((index i32)) -> (result string i32) + (err string i32 1)) + +(fn main () -> i32 + 0) +"#, + snapshot: "../tests/std-process-arg-result-helper-shadow.diag", + }, + DiagnosticCase { + name: "std-io-read-stdin-result-arity", + source: r#" +(module main) + +(fn main () -> i32 + (std.io.read_stdin_result 1)) +"#, + snapshot: "../tests/std-io-read-stdin-result-arity.diag", + }, + DiagnosticCase { + name: "std-io-read-stdin-result-bool-context", + source: r#" +(module main) + +(fn main () -> i32 + (if (std.io.read_stdin_result) 1 0)) +"#, + snapshot: "../tests/std-io-read-stdin-result-bool-context.diag", + }, + DiagnosticCase { + name: "std-io-read-stdin-result-type", + source: r#" +(module main) + +(fn main () -> i32 + (let value i32 (std.io.read_stdin_result)) + value) +"#, + snapshot: "../tests/std-io-read-stdin-result-type.diag", + }, + DiagnosticCase { + name: "std-io-read-stdin-result-name-shadow", + source: r#" +(module main) + +(fn std.io.read_stdin_result () -> (result string i32) + (err string i32 1)) + +(fn main () -> i32 + 0) +"#, + snapshot: "../tests/std-io-read-stdin-result-name-shadow.diag", + }, + DiagnosticCase { + name: "std-io-read-stdin-result-helper-shadow", + source: r#" +(module main) + +(fn __glagol_io_read_stdin_result () -> (result string i32) + (err string i32 1)) + +(fn main () -> i32 + 0) +"#, + snapshot: "../tests/std-io-read-stdin-result-helper-shadow.diag", + }, + DiagnosticCase { + name: "std-result-map-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.result.map (ok string i32 "a")) + 0) +"#, + snapshot: "../tests/std-result-map-unsupported.diag", + }, + DiagnosticCase { + name: "std-package-load-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.package.load "dep") + 0) +"#, + snapshot: "../tests/std-package-load-unsupported.diag", + }, + DiagnosticCase { + name: "std-abi-layout-unsupported", + source: r#" +(module main) + +(fn main () -> i32 + (std.abi.layout) + 0) +"#, + snapshot: "../tests/std-abi-layout-unsupported.diag", + }, + DiagnosticCase { + name: "std-vec-i32-arity", + source: r#" +(module main) + +(fn main () -> (vec i32) + (std.vec.i32.append (std.vec.i32.empty))) +"#, + snapshot: "../tests/std-vec-i32-arity.diag", + }, + DiagnosticCase { + name: "std-vec-i32-empty-arity", + source: r#" +(module main) + +(fn main () -> (vec i32) + (std.vec.i32.empty 1)) +"#, + snapshot: "../tests/std-vec-i32-empty-arity.diag", + }, + DiagnosticCase { + name: "std-vec-i32-len-arity", + source: r#" +(module main) + +(fn main () -> i32 + (std.vec.i32.len)) +"#, + snapshot: "../tests/std-vec-i32-len-arity.diag", + }, + DiagnosticCase { + name: "std-vec-i32-index-arity", + source: r#" +(module main) + +(fn main () -> i32 + (std.vec.i32.index (std.vec.i32.empty))) +"#, + snapshot: "../tests/std-vec-i32-index-arity.diag", + }, + DiagnosticCase { + name: "std-vec-i32-type", + source: r#" +(module main) + +(fn main () -> (vec i32) + (std.vec.i32.append (std.vec.i32.empty) true)) +"#, + snapshot: "../tests/std-vec-i32-type.diag", + }, + DiagnosticCase { + name: "std-vec-i32-append-vector-type", + source: r#" +(module main) + +(fn main () -> (vec i32) + (std.vec.i32.append 1 2)) +"#, + snapshot: "../tests/std-vec-i32-append-vector-type.diag", + }, + DiagnosticCase { + name: "std-vec-i32-len-type", + source: r#" +(module main) + +(fn main () -> i32 + (std.vec.i32.len 1)) +"#, + snapshot: "../tests/std-vec-i32-len-type.diag", + }, + DiagnosticCase { + name: "std-vec-i32-index-vector-type", + source: r#" +(module main) + +(fn main () -> i32 + (std.vec.i32.index 1 0)) +"#, + snapshot: "../tests/std-vec-i32-index-vector-type.diag", + }, + DiagnosticCase { + name: "std-vec-i32-index-index-type", + source: r#" +(module main) + +(fn main () -> i32 + (std.vec.i32.index (std.vec.i32.empty) true)) +"#, + snapshot: "../tests/std-vec-i32-index-index-type.diag", + }, + DiagnosticCase { + name: "std-vec-i32-result-context", + source: r#" +(module main) + +(fn main () -> i32 + (std.vec.i32.empty)) +"#, + snapshot: "../tests/std-vec-i32-result-context.diag", + }, + DiagnosticCase { + name: "std-vec-i32-one-sided-equality", + source: r#" +(module main) + +(fn main () -> bool + (= (std.vec.i32.empty) 1)) +"#, + snapshot: "../tests/std-vec-i32-one-sided-equality.diag", + }, + DiagnosticCase { + name: "std-vec-i32-helper-shadow", + source: r#" +(module main) + +(fn __glagol_vec_i32_eq ((left (vec i32)) (right (vec i32))) -> bool + (= left right)) + +(fn main () -> (vec i32) + (std.vec.i32.empty)) +"#, + snapshot: "../tests/std-vec-i32-helper-shadow.diag", + }, + DiagnosticCase { + name: "std-vec-i32-push-alias", + source: r#" +(module main) + +(fn main () -> (vec i32) + (std.vec.i32.push (std.vec.i32.empty) 1)) +"#, + snapshot: "../tests/std-vec-i32-push-alias.diag", + }, + DiagnosticCase { + name: "std-vec-i64-arity", + source: r#" +(module main) + +(fn main () -> (vec i64) + (std.vec.i64.append (std.vec.i64.empty))) +"#, + snapshot: "../tests/std-vec-i64-arity.diag", + }, + DiagnosticCase { + name: "std-vec-i64-empty-arity", + source: r#" +(module main) + +(fn main () -> (vec i64) + (std.vec.i64.empty 1)) +"#, + snapshot: "../tests/std-vec-i64-empty-arity.diag", + }, + DiagnosticCase { + name: "std-vec-i64-len-arity", + source: r#" +(module main) + +(fn main () -> i32 + (std.vec.i64.len)) +"#, + snapshot: "../tests/std-vec-i64-len-arity.diag", + }, + DiagnosticCase { + name: "std-vec-i64-index-arity", + source: r#" +(module main) + +(fn main () -> i64 + (std.vec.i64.index (std.vec.i64.empty))) +"#, + snapshot: "../tests/std-vec-i64-index-arity.diag", + }, + DiagnosticCase { + name: "std-vec-i64-type", + source: r#" +(module main) + +(fn main () -> (vec i64) + (std.vec.i64.append (std.vec.i64.empty) true)) +"#, + snapshot: "../tests/std-vec-i64-type.diag", + }, + DiagnosticCase { + name: "std-vec-i64-append-vector-type", + source: r#" +(module main) + +(fn main () -> (vec i64) + (std.vec.i64.append 1 2i64)) +"#, + snapshot: "../tests/std-vec-i64-append-vector-type.diag", + }, + DiagnosticCase { + name: "std-vec-i64-len-type", + source: r#" +(module main) + +(fn main () -> i32 + (std.vec.i64.len 1)) +"#, + snapshot: "../tests/std-vec-i64-len-type.diag", + }, + DiagnosticCase { + name: "std-vec-i64-index-vector-type", + source: r#" +(module main) + +(fn main () -> i64 + (std.vec.i64.index 1 0)) +"#, + snapshot: "../tests/std-vec-i64-index-vector-type.diag", + }, + DiagnosticCase { + name: "std-vec-i64-index-index-type", + source: r#" +(module main) + +(fn main () -> i64 + (std.vec.i64.index (std.vec.i64.empty) true)) +"#, + snapshot: "../tests/std-vec-i64-index-index-type.diag", + }, + DiagnosticCase { + name: "std-vec-i64-result-context", + source: r#" +(module main) + +(fn main () -> i32 + (std.vec.i64.empty)) +"#, + snapshot: "../tests/std-vec-i64-result-context.diag", + }, + DiagnosticCase { + name: "std-vec-i64-one-sided-equality", + source: r#" +(module main) + +(fn main () -> bool + (= (std.vec.i64.empty) 1)) +"#, + snapshot: "../tests/std-vec-i64-one-sided-equality.diag", + }, + DiagnosticCase { + name: "std-vec-i64-helper-shadow", + source: r#" +(module main) + +(fn __glagol_vec_i64_eq ((left (vec i64)) (right (vec i64))) -> bool + (= left right)) + +(fn main () -> (vec i64) + (std.vec.i64.empty)) +"#, + snapshot: "../tests/std-vec-i64-helper-shadow.diag", + }, + DiagnosticCase { + name: "std-vec-i64-push-alias", + source: r#" +(module main) + +(fn main () -> (vec i64) + (std.vec.i64.push (std.vec.i64.empty) 1i64)) +"#, + snapshot: "../tests/std-vec-i64-push-alias.diag", + }, + DiagnosticCase { + name: "std-vec-string-arity", + source: r#" +(module main) + +(fn main () -> (vec string) + (std.vec.string.append (std.vec.string.empty))) +"#, + snapshot: "../tests/std-vec-string-arity.diag", + }, + DiagnosticCase { + name: "std-vec-string-empty-arity", + source: r#" +(module main) + +(fn main () -> (vec string) + (std.vec.string.empty "extra")) +"#, + snapshot: "../tests/std-vec-string-empty-arity.diag", + }, + DiagnosticCase { + name: "std-vec-string-len-arity", + source: r#" +(module main) + +(fn main () -> i32 + (std.vec.string.len)) +"#, + snapshot: "../tests/std-vec-string-len-arity.diag", + }, + DiagnosticCase { + name: "std-vec-string-index-arity", + source: r#" +(module main) + +(fn main () -> string + (std.vec.string.index (std.vec.string.empty))) +"#, + snapshot: "../tests/std-vec-string-index-arity.diag", + }, + DiagnosticCase { + name: "std-vec-string-type", + source: r#" +(module main) + +(fn main () -> (vec string) + (std.vec.string.append (std.vec.string.empty) 1)) +"#, + snapshot: "../tests/std-vec-string-type.diag", + }, + DiagnosticCase { + name: "std-vec-string-append-vector-type", + source: r#" +(module main) + +(fn main () -> (vec string) + (std.vec.string.append 1 "x")) +"#, + snapshot: "../tests/std-vec-string-append-vector-type.diag", + }, + DiagnosticCase { + name: "std-vec-string-len-type", + source: r#" +(module main) + +(fn main () -> i32 + (std.vec.string.len 1)) +"#, + snapshot: "../tests/std-vec-string-len-type.diag", + }, + DiagnosticCase { + name: "std-vec-string-index-vector-type", + source: r#" +(module main) + +(fn main () -> string + (std.vec.string.index 1 0)) +"#, + snapshot: "../tests/std-vec-string-index-vector-type.diag", + }, + DiagnosticCase { + name: "std-vec-string-index-index-type", + source: r#" +(module main) + +(fn main () -> string + (std.vec.string.index (std.vec.string.empty) true)) +"#, + snapshot: "../tests/std-vec-string-index-index-type.diag", + }, + DiagnosticCase { + name: "std-vec-string-result-context", + source: r#" +(module main) + +(fn main () -> i32 + (std.vec.string.empty)) +"#, + snapshot: "../tests/std-vec-string-result-context.diag", + }, + DiagnosticCase { + name: "std-vec-string-one-sided-equality", + source: r#" +(module main) + +(fn main () -> bool + (= (std.vec.string.empty) 1)) +"#, + snapshot: "../tests/std-vec-string-one-sided-equality.diag", + }, + DiagnosticCase { + name: "std-vec-string-helper-shadow", + source: r#" +(module main) + +(fn __glagol_vec_string_eq ((left (vec string)) (right (vec string))) -> bool + (= left right)) + +(fn main () -> (vec string) + (std.vec.string.empty)) +"#, + snapshot: "../tests/std-vec-string-helper-shadow.diag", + }, + DiagnosticCase { + name: "std-vec-string-push-alias", + source: r#" +(module main) + +(fn main () -> (vec string) + (std.vec.string.push (std.vec.string.empty) "x")) +"#, + snapshot: "../tests/std-vec-string-push-alias.diag", + }, + DiagnosticCase { + name: "std-print-bool-result", + source: r#" +(module main) + +(fn main () -> i32 + (std.io.print_bool true)) +"#, + snapshot: "../tests/std-print-bool-result.diag", + }, + DiagnosticCase { + name: "std-parameter-shadows-runtime", + source: r#" +(module main) + +(fn leak ((std.io.print_i32 i32)) -> i32 + std.io.print_i32) +"#, + snapshot: "../tests/std-parameter-shadows-runtime.diag", + }, + DiagnosticCase { + name: "unsafe-parameter-shadows-callable", + source: r#" +(module main) + +(fn leak ((alloc i32)) -> i32 + alloc) +"#, + snapshot: "../tests/unsafe-parameter-shadows-callable.diag", + }, + DiagnosticCase { + name: "integer-out-of-range", + source: r#" +(module main) + +(fn main () -> i32 + 2147483648) +"#, + snapshot: "../tests/integer-out-of-range.diag", + }, + DiagnosticCase { + name: "i64-literal-out-of-range", + source: r#" +(module main) + +(fn main () -> i32 + 9223372036854775808i64) +"#, + snapshot: "../tests/i64-literal-out-of-range.diag", + }, + DiagnosticCase { + name: "invalid-i64-literal", + source: r#" +(module main) + +(fn main () -> i32 + 1.0i64) +"#, + snapshot: "../tests/invalid-i64-literal.diag", + }, + DiagnosticCase { + name: "invalid-leading-plus-i64-literal", + source: r#" +(module main) + +(fn main () -> i32 + +1i64) +"#, + snapshot: "../tests/invalid-leading-plus-i64-literal.diag", + }, + DiagnosticCase { + name: "invalid-sign-only-i64-literal", + source: r#" +(module main) + +(fn main () -> i32 + -i64) +"#, + snapshot: "../tests/invalid-sign-only-i64-literal.diag", + }, + DiagnosticCase { + name: "unsupported-float-literal", + source: r#" +(module main) + +(fn main () -> i32 + (let value f64 NaN) + 0) +"#, + snapshot: "../tests/unsupported-float-literal.diag", + }, + DiagnosticCase { + name: "test-invalid-name", + source: r#" +(module main) + +(test not-a-string true) +"#, + snapshot: "../tests/test-invalid-name.diag", + }, + DiagnosticCase { + name: "test-invalid-escaped-name", + source: r#" +(module main) + +(test "bad\nname" true) +"#, + snapshot: "../tests/test-invalid-escaped-name.diag", + }, + DiagnosticCase { + name: "test-duplicate-name", + source: r#" +(module main) + +(test "same" true) +(test "same" true) +"#, + snapshot: "../tests/test-duplicate-name.diag", + }, + DiagnosticCase { + name: "test-non-bool", + source: r#" +(module main) + +(test "not bool" + 1) +"#, + snapshot: "../tests/test-non-bool.diag", + }, + DiagnosticCase { + name: "test-invalid-form", + source: r#" +(module main) + +(test "too many" true false) +"#, + snapshot: "../tests/test-invalid-form.diag", + }, + DiagnosticCase { + name: "unsupported-signature-type", + source: r#" +(module main) + +(fn id ((value (ptr i32))) -> i32 + 0) +"#, + snapshot: "../tests/unsupported-signature-type.diag", + }, + DiagnosticCase { + name: "unknown-struct-parameter", + source: r#" +(module main) + +(fn take ((value Missing)) -> i32 + 0) +"#, + snapshot: "../tests/unknown-struct-parameter.diag", + }, + DiagnosticCase { + name: "unknown-struct-return", + source: r#" +(module main) + +(fn make () -> Missing + 0) +"#, + snapshot: "../tests/unknown-struct-return.diag", + }, + DiagnosticCase { + name: "unsupported-unit-return-signature", + source: r#" +(module main) + +(fn main () -> unit + (print_i32 1)) +"#, + snapshot: "../tests/unsupported-unit-return-signature.diag", + }, + DiagnosticCase { + name: "unsupported-unit-parameter-signature", + source: r#" +(module main) + +(fn ignore ((value unit)) -> i32 + 0) +"#, + snapshot: "../tests/unsupported-unit-parameter-signature.diag", + }, + DiagnosticCase { + name: "empty-array", + source: r#" +(module main) + +(fn main () -> i32 + (index (array i32) 0)) +"#, + snapshot: "../tests/empty-array.diag", + }, + DiagnosticCase { + name: "zero-length-array-type", + source: r#" +(module main) + +(fn main () -> i32 + (let values (array i32 0) (array i32 1)) + 0) +"#, + snapshot: "../tests/zero-length-array-type.diag", + }, + DiagnosticCase { + name: "array-index-out-of-bounds", + source: r#" +(module main) + +(fn main () -> i32 + (index (array i32 1 2) 2)) +"#, + snapshot: "../tests/array-index-out-of-bounds.diag", + }, + DiagnosticCase { + name: "index-on-non-array", + source: r#" +(module main) + +(fn main () -> i32 + (index 1 0)) +"#, + snapshot: "../tests/index-on-non-array.diag", + }, + DiagnosticCase { + name: "array-index-not-i32", + source: r#" +(module main) + +(fn main () -> i32 + (index (array i32 1) true)) +"#, + snapshot: "../tests/array-index-not-i32.diag", + }, + DiagnosticCase { + name: "unsupported-array-element-type", + source: r#" +(module main) + +(fn main () -> i32 + (index (array (array i32 1) (array i32 1)) 0)) +"#, + snapshot: "../tests/unsupported-array-element-type.diag", + }, + DiagnosticCase { + name: "unsupported-array-signature-element-type", + source: r#" +(module main) + +(fn first ((values (array (array string 1) 1))) -> i32 + 0) +"#, + snapshot: "../tests/unsupported-array-signature-element-type.diag", + }, + DiagnosticCase { + name: "unsupported-array-equality", + source: r#" +(module main) + +(fn main () -> i32 + (if (= (array i32 1) (array i32 1)) 1 0)) +"#, + snapshot: "../tests/unsupported-array-equality.diag", + }, + DiagnosticCase { + name: "unsupported-array-local-mutation", + source: r#" +(module main) + +(fn main () -> i32 + (var values (array i32 1) (array i32 1)) + 0) +"#, + snapshot: "../tests/unsupported-array-local-mutation.diag", + }, + DiagnosticCase { + name: "unsupported-array-print", + source: r#" +(module main) + +(fn main () -> i32 + (print_i32 (array i32 1)) + 0) +"#, + snapshot: "../tests/unsupported-array-print.diag", + }, + DiagnosticCase { + name: "unsupported-vec-element-type", + source: r#" +(module main) + +(fn main ((values (vec f32))) -> i32 + 0) +"#, + snapshot: "../tests/unsupported-vec-element-type.diag", + }, + DiagnosticCase { + name: "unsupported-generic-vec-type", + source: r#" +(module main) + +(fn main () -> (vec) + (std.vec.i32.empty)) +"#, + snapshot: "../tests/unsupported-generic-vec-type.diag", + }, + DiagnosticCase { + name: "unsupported-vec-nesting", + source: r#" +(module main) + +(fn main ((values (array (vec i32) 1))) -> i32 + 0) +"#, + snapshot: "../tests/unsupported-vec-nesting.diag", + }, + DiagnosticCase { + name: "unsupported-vec-option", + source: r#" +(module main) + +(fn main () -> i32 + (is_some (some (vec i32) (std.vec.i32.empty)))) +"#, + snapshot: "../tests/unsupported-vec-option.diag", + }, + DiagnosticCase { + name: "unsupported-vec-result", + source: r#" +(module main) + +(fn main () -> i32 + (is_ok (ok (vec i32) i32 (std.vec.i32.empty)))) +"#, + snapshot: "../tests/unsupported-vec-result.diag", + }, + DiagnosticCase { + name: "unsupported-vec-literal", + source: r#" +(module main) + +(fn main () -> (vec i32) + (vec i32 1)) +"#, + snapshot: "../tests/unsupported-vec-literal.diag", + }, + DiagnosticCase { + name: "array-return-type-mismatch", + source: r#" +(module main) + +(fn bad () -> (array i32 2) + (array i32 1)) +"#, + snapshot: "../tests/array-return-type-mismatch.diag", + }, + DiagnosticCase { + name: "array-argument-type-mismatch", + source: r#" +(module main) + +(fn first ((values (array i32 2))) -> i32 + (index values 0)) + +(fn main () -> i32 + (first (array i32 1))) +"#, + snapshot: "../tests/array-argument-type-mismatch.diag", + }, + DiagnosticCase { + name: "array-local-initializer-type-mismatch", + source: r#" +(module main) + +(fn main () -> i32 + (let values (array i32 2) (array i32 1)) + 0) +"#, + snapshot: "../tests/array-local-initializer-type-mismatch.diag", + }, + DiagnosticCase { + name: "malformed-option-constructor", + source: r#" +(module main) + +(fn bad () -> (option i32) + (some i32)) +"#, + snapshot: "../tests/malformed-option-constructor.diag", + }, + DiagnosticCase { + name: "option-constructor-type-mismatch", + source: r#" +(module main) + +(fn bad () -> (option i32) + (some i32 true)) +"#, + snapshot: "../tests/option-constructor-type-mismatch.diag", + }, + DiagnosticCase { + name: "unsupported-option-payload-type", + source: r#" +(module main) + +(fn main () -> i32 + (some (vec i32) (std.vec.i32.empty))) +"#, + snapshot: "../tests/unsupported-option-payload-type.diag", + }, + DiagnosticCase { + name: "unsupported-option-parameter-payload-type", + source: r#" +(module main) + +(fn take ((value (option (vec i32)))) -> i32 + 0) +"#, + snapshot: "../tests/unsupported-option-parameter-payload-type.diag", + }, + DiagnosticCase { + name: "unsupported-option-return-payload-type", + source: r#" +(module main) + +(fn bad () -> (option (vec i32)) + 0) +"#, + snapshot: "../tests/unsupported-option-return-payload-type.diag", + }, + DiagnosticCase { + name: "malformed-result-constructor", + source: r#" +(module main) + +(fn bad () -> (result i32 i32) + (ok i32 i32)) +"#, + snapshot: "../tests/malformed-result-constructor.diag", + }, + DiagnosticCase { + name: "result-constructor-type-mismatch", + source: r#" +(module main) + +(fn bad () -> (result i32 i32) + (err i32 i32 true)) +"#, + snapshot: "../tests/result-constructor-type-mismatch.diag", + }, + DiagnosticCase { + name: "unsupported-result-payload-type", + source: r#" +(module main) + +(fn main () -> i32 + (ok i32 bool true)) +"#, + snapshot: "../tests/unsupported-result-payload-type.diag", + }, + DiagnosticCase { + name: "unsupported-result-string-bool", + source: r#" +(module main) + +(fn main () -> i32 + (ok string bool "value")) +"#, + snapshot: "../tests/unsupported-result-string-bool.diag", + }, + DiagnosticCase { + name: "unsupported-result-parameter-payload-type", + source: r#" +(module main) + +(fn take ((value (result i32 bool))) -> i32 + 0) +"#, + snapshot: "../tests/unsupported-result-parameter-payload-type.diag", + }, + DiagnosticCase { + name: "unsupported-result-return-payload-type", + source: r#" +(module main) + +(fn bad () -> (result i32 bool) + 0) +"#, + snapshot: "../tests/unsupported-result-return-payload-type.diag", + }, + DiagnosticCase { + name: "unsupported-option-result-equality", + source: r#" +(module main) + +(fn main () -> i32 + (if (= (some i32 1) (none i32)) 1 0)) +"#, + snapshot: "../tests/unsupported-option-result-equality.diag", + }, + DiagnosticCase { + name: "unsupported-result-string-equality", + source: r#" +(module main) + +(fn main () -> i32 + (if (= (ok string i32 "a") (err string i32 1)) 1 0)) +"#, + snapshot: "../tests/unsupported-result-string-equality.diag", + }, + DiagnosticCase { + name: "unsupported-option-result-print", + source: r#" +(module main) + +(fn main () -> i32 + (print_i32 (none i32)) + 0) +"#, + snapshot: "../tests/unsupported-option-result-print.diag", + }, + DiagnosticCase { + name: "unsupported-result-string-print", + source: r#" +(module main) + +(fn main () -> i32 + (std.io.print_i32 (ok string i32 "a")) + 0) +"#, + snapshot: "../tests/unsupported-result-string-print.diag", + }, + DiagnosticCase { + name: "match-subject-type-mismatch", + source: r#" +(module main) + +(fn main () -> i32 + (match 1 + ((some payload) + payload) + ((none) + 0))) +"#, + snapshot: "../tests/match-subject-type-mismatch.diag", + }, + DiagnosticCase { + name: "unsupported-match-payload-type", + source: r#" +(module main) + +(fn main () -> i32 + (match (some (vec i32) (std.vec.i32.empty)) + ((some payload) + payload) + ((none) + 0))) +"#, + snapshot: "../tests/unsupported-match-payload-type.diag", + }, + DiagnosticCase { + name: "non-exhaustive-match", + source: r#" +(module main) + +(fn main ((value (option i32))) -> i32 + (match value + ((some payload) + payload))) +"#, + snapshot: "../tests/non-exhaustive-match.diag", + }, + DiagnosticCase { + name: "duplicate-match-arm", + source: r#" +(module main) + +(fn main ((value (option i32))) -> i32 + (match value + ((some payload) + payload) + ((some other) + other) + ((none) + 0))) +"#, + snapshot: "../tests/duplicate-match-arm.diag", + }, + DiagnosticCase { + name: "malformed-match-pattern", + source: r#" +(module main) + +(fn main ((value (option i32))) -> i32 + (match value + ((some) + 1) + ((none) + 0))) +"#, + snapshot: "../tests/malformed-match-pattern.diag", + }, + DiagnosticCase { + name: "match-arm-type-mismatch", + source: r#" +(module main) + +(fn main ((value (option i32))) -> i32 + (match value + ((some payload) + payload) + ((none) + false))) +"#, + snapshot: "../tests/match-arm-type-mismatch.diag", + }, + DiagnosticCase { + name: "match-binding-collision", + source: r#" +(module main) + +(fn main ((value (option i32)) (payload i32)) -> i32 + (match value + ((some payload) + payload) + ((none) + 0))) +"#, + snapshot: "../tests/match-binding-collision.diag", + }, + DiagnosticCase { + name: "unsupported-match-mutation", + source: r#" +(module main) + +(fn main ((value (option i32))) -> i32 + (match (set value (none i32)) + ((some payload) + payload) + ((none) + 0))) +"#, + snapshot: "../tests/unsupported-match-mutation.diag", + }, + DiagnosticCase { + name: "unsupported-match-container", + source: r#" +(module main) + +(fn main () -> i32 + (match (array (option i32) (none i32)) + ((some payload) + payload) + ((none) + 0))) +"#, + snapshot: "../tests/unsupported-match-container.diag", + }, + DiagnosticCase { + name: "option-observation-non-option", + source: r#" +(module main) + +(fn main () -> bool + (is_some 1)) +"#, + snapshot: "../tests/option-observation-non-option.diag", + }, + DiagnosticCase { + name: "result-observation-non-result", + source: r#" +(module main) + +(fn main () -> bool + (is_ok 1)) +"#, + snapshot: "../tests/result-observation-non-result.diag", + }, + DiagnosticCase { + name: "malformed-option-unwrap", + source: r#" +(module main) + +(fn main () -> i32 + (unwrap_some)) +"#, + snapshot: "../tests/malformed-option-unwrap.diag", + }, + DiagnosticCase { + name: "malformed-result-ok-unwrap", + source: r#" +(module main) + +(fn main () -> i32 + (unwrap_ok)) +"#, + snapshot: "../tests/malformed-result-ok-unwrap.diag", + }, + DiagnosticCase { + name: "malformed-result-err-unwrap", + source: r#" +(module main) + +(fn main () -> i32 + (unwrap_err)) +"#, + snapshot: "../tests/malformed-result-err-unwrap.diag", + }, + DiagnosticCase { + name: "option-unwrap-non-option", + source: r#" +(module main) + +(fn main () -> i32 + (unwrap_some 1)) +"#, + snapshot: "../tests/option-unwrap-non-option.diag", + }, + DiagnosticCase { + name: "result-ok-unwrap-non-result", + source: r#" +(module main) + +(fn main () -> i32 + (unwrap_ok 1)) +"#, + snapshot: "../tests/result-ok-unwrap-non-result.diag", + }, + DiagnosticCase { + name: "result-err-unwrap-non-result", + source: r#" +(module main) + +(fn main () -> i32 + (unwrap_err 1)) +"#, + snapshot: "../tests/result-err-unwrap-non-result.diag", + }, + DiagnosticCase { + name: "malformed-unsafe-form", + source: r#" +(module main) + +(fn main () -> i32 + (unsafe)) +"#, + snapshot: "../tests/malformed-unsafe-form.diag", + }, + DiagnosticCase { + name: "unsafe-required-operation", + source: r#" +(module main) + +(fn main () -> i32 + (alloc i32)) +"#, + snapshot: "../tests/unsafe-required-operation.diag", + }, + DiagnosticCase { + name: "unsupported-unsafe-operation", + source: r#" +(module main) + +(fn main () -> i32 + (unsafe + (alloc i32))) +"#, + snapshot: "../tests/unsupported-unsafe-operation.diag", + }, + DiagnosticCase { + name: "unsafe-required-dealloc", + source: unsafe_required_source!("dealloc"), + snapshot: "../tests/unsafe-required-dealloc.diag", + }, + DiagnosticCase { + name: "unsafe-required-load", + source: unsafe_required_source!("load"), + snapshot: "../tests/unsafe-required-load.diag", + }, + DiagnosticCase { + name: "unsafe-required-store", + source: unsafe_required_source!("store"), + snapshot: "../tests/unsafe-required-store.diag", + }, + DiagnosticCase { + name: "unsafe-required-ptr-add", + source: unsafe_required_source!("ptr_add"), + snapshot: "../tests/unsafe-required-ptr-add.diag", + }, + DiagnosticCase { + name: "unsafe-required-unchecked-index", + source: unsafe_required_source!("unchecked_index"), + snapshot: "../tests/unsafe-required-unchecked-index.diag", + }, + DiagnosticCase { + name: "unsafe-required-reinterpret", + source: unsafe_required_source!("reinterpret"), + snapshot: "../tests/unsafe-required-reinterpret.diag", + }, + DiagnosticCase { + name: "unsafe-required-ffi-call", + source: unsafe_required_source!("ffi_call"), + snapshot: "../tests/unsafe-required-ffi-call.diag", + }, + DiagnosticCase { + name: "unsupported-unsafe-dealloc", + source: unsupported_unsafe_source!("dealloc"), + snapshot: "../tests/unsupported-unsafe-dealloc.diag", + }, + DiagnosticCase { + name: "unsupported-unsafe-load", + source: unsupported_unsafe_source!("load"), + snapshot: "../tests/unsupported-unsafe-load.diag", + }, + DiagnosticCase { + name: "unsupported-unsafe-store", + source: unsupported_unsafe_source!("store"), + snapshot: "../tests/unsupported-unsafe-store.diag", + }, + DiagnosticCase { + name: "unsupported-unsafe-ptr-add", + source: unsupported_unsafe_source!("ptr_add"), + snapshot: "../tests/unsupported-unsafe-ptr-add.diag", + }, + DiagnosticCase { + name: "unsupported-unsafe-unchecked-index", + source: unsupported_unsafe_source!("unchecked_index"), + snapshot: "../tests/unsupported-unsafe-unchecked-index.diag", + }, + DiagnosticCase { + name: "unsupported-unsafe-reinterpret", + source: unsupported_unsafe_source!("reinterpret"), + snapshot: "../tests/unsupported-unsafe-reinterpret.diag", + }, + DiagnosticCase { + name: "unsupported-unsafe-ffi-call", + source: unsupported_unsafe_source!("ffi_call"), + snapshot: "../tests/unsupported-unsafe-ffi-call.diag", + }, + DiagnosticCase { + name: "unsupported-struct-field-type", + source: r#" +(module main) + +(struct Point + (x unit)) +"#, + snapshot: "../tests/unsupported-struct-field-type.diag", + }, + DiagnosticCase { + name: "unsupported-primitive-struct-field-container", + source: r#" +(module main) + +(struct Matrix + (rows (array (array i32 2) 2))) +"#, + snapshot: "../tests/unsupported-primitive-struct-field-container.diag", + }, + DiagnosticCase { + name: "empty-struct", + source: r#" +(module main) + +(struct Empty) + +(fn main () -> i32 + 0) +"#, + snapshot: "../tests/empty-struct.diag", + }, + DiagnosticCase { + name: "duplicate-struct", + source: r#" +(module main) + +(struct Point + (x i32)) + +(struct Point + (x i32)) + +(fn main () -> i32 + 0) +"#, + snapshot: "../tests/duplicate-struct.diag", + }, + DiagnosticCase { + name: "duplicate-struct-field", + source: r#" +(module main) + +(struct Point + (x i32) + (x i32)) + +(fn main () -> i32 + 0) +"#, + snapshot: "../tests/duplicate-struct-field.diag", + }, + DiagnosticCase { + name: "recursive-struct-field", + source: r#" +(module main) + +(struct Node + (next Node)) + +(fn main () -> i32 + 0) +"#, + snapshot: "../tests/recursive-struct-field.diag", + }, + DiagnosticCase { + name: "cyclic-struct-fields", + source: r#" +(module main) + +(struct Left + (right Right)) + +(struct Right + (left Left)) + +(fn main () -> i32 + 0) +"#, + snapshot: "../tests/cyclic-struct-fields.diag", + }, + DiagnosticCase { + name: "struct-missing-field", + source: r#" +(module main) + +(struct Point + (x i32) + (y i32)) + +(fn main () -> i32 + (. (Point (x 1)) x)) +"#, + snapshot: "../tests/struct-missing-field.diag", + }, + DiagnosticCase { + name: "struct-unknown-field", + source: r#" +(module main) + +(struct Point + (x i32) + (y i32)) + +(fn main () -> i32 + (. (Point (x 1) (z 2)) x)) +"#, + snapshot: "../tests/struct-unknown-field.diag", + }, + DiagnosticCase { + name: "struct-field-order-mismatch", + source: r#" +(module main) + +(struct Point + (x i32) + (y i32)) + +(fn main () -> i32 + (. (Point (y 2) (x 1)) x)) +"#, + snapshot: "../tests/struct-field-order-mismatch.diag", + }, + DiagnosticCase { + name: "duplicate-struct-constructor-field", + source: r#" +(module main) + +(struct Point + (x i32)) + +(fn main () -> i32 + (. (Point (x 1) (x 2)) x)) +"#, + snapshot: "../tests/duplicate-struct-constructor-field.diag", + }, + DiagnosticCase { + name: "field-access-on-non-struct", + source: r#" +(module main) + +(fn main () -> i32 + (. 1 x)) +"#, + snapshot: "../tests/field-access-on-non-struct.diag", + }, + DiagnosticCase { + name: "unknown-struct-local", + source: r#" +(module main) + +(fn main () -> i32 + (let value Missing 0) + 0) +"#, + snapshot: "../tests/unknown-struct-local.diag", + }, + DiagnosticCase { + name: "unsupported-struct-field-mutation", + source: r#" +(module main) + +(struct Point + (x i32)) + +(fn main () -> i32 + (let p Point (Point (x 1))) + (set (. p x) 2) + (. p x)) +"#, + snapshot: "../tests/unsupported-struct-field-mutation.diag", + }, + DiagnosticCase { + name: "duplicate-local", + source: r#" +(module main) + +(fn main () -> i32 + (let x i32 1) + (var x i32 2) + x) +"#, + snapshot: "../tests/duplicate-local.diag", + }, + DiagnosticCase { + name: "local-redeclares-parameter", + source: r#" +(module main) + +(fn id ((value i32)) -> i32 + (let value i32 1) + value) +"#, + snapshot: "../tests/local-redeclares-parameter.diag", + }, + DiagnosticCase { + name: "local-shadows-function", + source: r#" +(module main) + +(fn helper () -> i32 + 1) + +(fn main () -> i32 + (let helper i32 2) + helper) +"#, + snapshot: "../tests/local-shadows-function.diag", + }, + DiagnosticCase { + name: "local-shadows-intrinsic", + source: r#" +(module main) + +(fn main () -> i32 + (let print_i32 i32 1) + print_i32) +"#, + snapshot: "../tests/local-shadows-intrinsic.diag", + }, + DiagnosticCase { + name: "unsupported-local-type", + source: r#" +(module main) + +(fn main () -> i32 + (let ignored unit (print_i32 1)) + 0) +"#, + snapshot: "../tests/unsupported-local-type.diag", + }, + DiagnosticCase { + name: "single-file-main-i64-return", + source: r#" +(module main) + +(fn main () -> i64 + 1i64) +"#, + snapshot: "../tests/single-file-main-i64-return.diag", + }, + DiagnosticCase { + name: "set-unknown-local", + source: r#" +(module main) + +(fn main () -> i32 + (set missing 1) + 0) +"#, + snapshot: "../tests/set-unknown-local.diag", + }, + DiagnosticCase { + name: "set-parameter", + source: r#" +(module main) + +(fn id ((value i32)) -> i32 + (set value 1) + value) +"#, + snapshot: "../tests/set-parameter.diag", + }, + DiagnosticCase { + name: "set-immutable-local", + source: r#" +(module main) + +(fn main () -> i32 + (let x i32 1) + (set x 2) + x) +"#, + snapshot: "../tests/set-immutable-local.diag", + }, + DiagnosticCase { + name: "set-type-mismatch", + source: r#" +(module main) + +(fn main () -> i32 + (var x i32 1) + (set x true) + x) +"#, + snapshot: "../tests/set-type-mismatch.diag", + }, + DiagnosticCase { + name: "nested-local-declaration", + source: r#" +(module main) + +(fn main () -> i32 + (+ (let x i32 1) 2)) +"#, + snapshot: "../tests/nested-local-declaration.diag", + }, + DiagnosticCase { + name: "malformed-if", + source: r#" +(module main) + +(fn main () -> i32 + (if true 1) +) +"#, + snapshot: "../tests/malformed-if.diag", + }, + DiagnosticCase { + name: "if-condition-not-bool", + source: r#" +(module main) + +(fn main () -> i32 + (if 1 2 3)) +"#, + snapshot: "../tests/if-condition-not-bool.diag", + }, + DiagnosticCase { + name: "if-branch-type-mismatch", + source: r#" +(module main) + +(fn main () -> i32 + (if true 1 false)) +"#, + snapshot: "../tests/if-branch-type-mismatch.diag", + }, + DiagnosticCase { + name: "malformed-while", + source: r#" +(module main) + +(fn main () -> i32 + (while) + 0) +"#, + snapshot: "../tests/malformed-while.diag", + }, + DiagnosticCase { + name: "empty-while-body", + source: r#" +(module main) + +(fn main () -> i32 + (while true) + 0) +"#, + snapshot: "../tests/empty-while-body.diag", + }, + DiagnosticCase { + name: "while-condition-not-bool", + source: r#" +(module main) + +(fn main () -> i32 + (while 1 + (print_i32 1)) + 0) +"#, + snapshot: "../tests/while-condition-not-bool.diag", + }, + DiagnosticCase { + name: "while-body-local", + source: r#" +(module main) + +(fn main () -> i32 + (while true + (let x i32 1)) + 0) +"#, + snapshot: "../tests/while-body-local.diag", + }, + DiagnosticCase { + name: "while-body-non-unit", + source: r#" +(module main) + +(fn main () -> i32 + (while true + 1) + 0) +"#, + snapshot: "../tests/while-body-non-unit.diag", + }, + DiagnosticCase { + name: "nested-while", + source: r#" +(module main) + +(fn main () -> i32 + (while true + (while false + (print_i32 1))) + 0) +"#, + snapshot: "../tests/nested-while.diag", + }, + DiagnosticCase { + name: "while-final-function", + source: r#" +(module main) + +(fn main () -> i32 + (while false + (print_i32 1)) +) +"#, + snapshot: "../tests/while-final-function.diag", + }, + DiagnosticCase { + name: "while-final-test", + source: r#" +(module main) + +(test "loop final" + (var i i32 0) + (while false + (set i 1))) +"#, + snapshot: "../tests/while-final-test.diag", + }, +]; + +#[test] +fn current_negative_cases_match_machine_diagnostic_snapshots() { + for case in CASES { + let output = run_compiler(case.name, case.source); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + !output.status.success(), + "compiler unexpectedly accepted fixture `{}`\nstdout:\n{}\nstderr:\n{}", + case.name, + stdout, + stderr, + ); + assert!( + stdout.is_empty(), + "compiler emitted stdout for rejected fixture `{}`\nstdout:\n{}\nstderr:\n{}", + case.name, + stdout, + stderr, + ); + + let actual = normalize_fixture_path(&machine_diagnostic(&stderr)); + let expected = fs::read_to_string(case.snapshot) + .unwrap_or_else(|err| panic!("read `{}`: {}", case.snapshot, err)); + + assert_eq!( + expected, actual, + "machine diagnostic snapshot mismatch for `{}`", + case.name + ); + } +} + +struct DiagnosticCase { + name: &'static str, + source: &'static str, + snapshot: &'static str, +} + +fn run_compiler(name: &str, source: &str) -> Output { + let compiler = env!("CARGO_BIN_EXE_glagol"); + let fixture = write_fixture(name, source); + + Command::new(compiler) + .arg(&fixture) + .output() + .unwrap_or_else(|err| panic!("run glagol on `{}`: {}", fixture.display(), err)) +} + +fn write_fixture(name: &str, source: &str) -> PathBuf { + let mut path = std::env::temp_dir(); + let id = NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed); + path.push(format!( + "glagol-diagnostics-contract-{}-{}-{}.slo", + std::process::id(), + id, + name + )); + fs::write(&path, source).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err)); + path +} + +fn machine_diagnostic(stderr: &str) -> String { + let mut diagnostics = Vec::new(); + let mut current = Vec::new(); + let mut in_machine_diagnostic = false; + let mut depth = 0; + + for line in stderr.lines() { + if line == "(diagnostic" { + in_machine_diagnostic = true; + current.clear(); + depth = 0; + } + + if in_machine_diagnostic { + current.push(line); + depth += paren_delta(line); + } + + if in_machine_diagnostic && depth == 0 { + diagnostics.push(current.join("\n")); + current.clear(); + in_machine_diagnostic = false; + } + } + + assert!( + !diagnostics.is_empty(), + "stderr did not contain a machine diagnostic:\n{}", + stderr + ); + + diagnostics.join("\n\n") + "\n" +} + +fn paren_delta(line: &str) -> isize { + let mut delta = 0; + let mut in_string = false; + let mut escaped = false; + + for ch in line.chars() { + if in_string { + if escaped { + escaped = false; + } else if ch == '\\' { + escaped = true; + } else if ch == '"' { + in_string = false; + } + } else if ch == '"' { + in_string = true; + } else if ch == '(' { + delta += 1; + } else if ch == ')' { + delta -= 1; + } + } + + delta +} + +fn normalize_fixture_path(diagnostic: &str) -> String { + diagnostic + .lines() + .map(|line| { + let trimmed = line.trim_start(); + if trimmed.starts_with("(file ") { + format!( + "{}(file \"\")", + " ".repeat(line.len() - trimmed.len()) + ) + } else { + line.to_string() + } + }) + .collect::>() + .join("\n") + + "\n" +} diff --git a/compiler/tests/dx_v1_7.rs b/compiler/tests/dx_v1_7.rs new file mode 100644 index 0000000..0a24c1e --- /dev/null +++ b/compiler/tests/dx_v1_7.rs @@ -0,0 +1,308 @@ +use std::{ + fs, + path::{Path, PathBuf}, + process::{Command, Output}, + sync::atomic::{AtomicUsize, Ordering}, +}; + +static NEXT_ID: AtomicUsize = AtomicUsize::new(0); + +#[test] +fn new_creates_minimal_valid_project() { + let project = unique_path("new-project"); + + let output = run_glagol(["new".as_ref(), project.as_os_str()]); + + assert_success("glagol new", &output); + assert!(output.stdout.is_empty(), "new wrote stdout"); + assert!(output.stderr.is_empty(), "new wrote stderr"); + assert_eq!( + fs::read_to_string(project.join("slovo.toml")).expect("read manifest"), + format!( + "[project]\nname = \"{}\"\nsource_root = \"src\"\nentry = \"main\"\n", + project.file_name().unwrap().to_string_lossy() + ) + ); + assert_eq!( + fs::read_to_string(project.join("src/main.slo")).expect("read main"), + "(module main)\n\n(fn main () -> i32\n 0)\n\n(test \"main returns zero\"\n (= (main) 0))\n" + ); + + let check = run_glagol(["check".as_ref(), project.as_os_str()]); + assert_success("generated project check", &check); + let fmt_check = run_glagol(["fmt".as_ref(), "--check".as_ref(), project.as_os_str()]); + assert_success("generated project fmt check", &fmt_check); + let test = run_glagol(["test".as_ref(), project.as_os_str()]); + assert_success("generated project test", &test); + assert_eq!( + String::from_utf8_lossy(&test.stdout), + "test \"main returns zero\" ... ok\n1 test(s) passed\n" + ); + let docs = unique_path("new-project-docs"); + let doc = run_glagol([ + "doc".as_ref(), + project.as_os_str(), + "-o".as_ref(), + docs.as_os_str(), + ]); + assert_success("generated project docs", &doc); + let doc_index = fs::read_to_string(docs.join("index.md")).expect("read generated docs"); + assert!(doc_index.contains("main returns zero")); + + let binary = unique_path("new-project-bin"); + let build = run_glagol([ + "build".as_ref(), + project.as_os_str(), + "-o".as_ref(), + binary.as_os_str(), + ]); + if build.status.success() { + let run = Command::new(&binary) + .output() + .expect("run generated project"); + assert_success("generated project binary", &run); + assert!( + run.stdout.is_empty(), + "generated project binary wrote stdout" + ); + } else { + assert_stderr_contains("generated project build", &build, "ToolchainUnavailable"); + } +} + +#[test] +fn new_rejects_non_empty_target_with_structured_diagnostic() { + let project = unique_path("new-non-empty"); + fs::create_dir_all(&project).expect("create project dir"); + fs::write(project.join("existing"), "").expect("write existing file"); + + let output = run_glagol(["new".as_ref(), project.as_os_str()]); + + assert_exit_code("new non-empty", &output, 1); + assert!(output.stdout.is_empty(), "new failure wrote stdout"); + assert_stderr_contains("new non-empty", &output, "ProjectScaffoldBlocked"); + assert_stderr_contains("new non-empty", &output, "(schema slovo.diagnostic)"); +} + +#[test] +fn fmt_check_and_write_work_for_files() { + let fixture = write_file("fmt-file", "(module main)\n(fn main() -> i32 0)\n"); + + let check = run_glagol(["fmt".as_ref(), "--check".as_ref(), fixture.as_os_str()]); + assert_exit_code("fmt check dirty file", &check, 1); + assert_stderr_contains("fmt check dirty file", &check, "FormatCheckFailed"); + + let write = run_glagol(["fmt".as_ref(), "--write".as_ref(), fixture.as_os_str()]); + assert_success("fmt write file", &write); + assert!(write.stdout.is_empty(), "fmt --write wrote stdout"); + assert_eq!( + fs::read_to_string(&fixture).expect("read formatted file"), + "(module main)\n\n(fn main () -> i32\n 0)\n" + ); + + let clean = run_glagol(["fmt".as_ref(), "--check".as_ref(), fixture.as_os_str()]); + assert_success("fmt check clean file", &clean); +} + +#[test] +fn fmt_check_and_write_work_for_projects_deterministically() { + let project = write_project( + "fmt-project", + &[( + "math", + "(module math (export one))\n(fn one() -> i32 1)\n", + )], + "(module main)\n(import math (one))\n(fn main() -> i32 (one))\n", + ); + + let check = run_glagol(["fmt".as_ref(), "--check".as_ref(), project.as_os_str()]); + assert_exit_code("fmt check dirty project", &check, 1); + assert_stderr_contains("fmt check dirty project", &check, "FormatCheckFailed"); + + let write = run_glagol(["fmt".as_ref(), "--write".as_ref(), project.as_os_str()]); + assert_success("fmt write project", &write); + assert!(write.stdout.is_empty(), "project fmt --write wrote stdout"); + assert_eq!( + fs::read_to_string(project.join("src/math.slo")).expect("read math"), + "(module math (export one))\n\n(fn one () -> i32\n 1)\n" + ); + assert_eq!( + fs::read_to_string(project.join("src/main.slo")).expect("read main"), + "(module main)\n\n(import math (one))\n\n(fn main () -> i32\n (one))\n" + ); +} + +#[test] +fn doc_generates_markdown_for_file_and_project() { + let fixture = write_file( + "doc-file", + "(module docs)\n\n(struct Point (x i32))\n\n(fn main () -> i32\n 0)\n\n(test \"zero\"\n (= 0 0))\n", + ); + let file_docs = unique_path("doc-file-out"); + + let file_output = run_glagol([ + "doc".as_ref(), + fixture.as_os_str(), + "-o".as_ref(), + file_docs.as_os_str(), + ]); + assert_success("doc file", &file_output); + let file_index = fs::read_to_string(file_docs.join("index.md")).expect("read file docs"); + assert!(file_index.contains("# File ")); + assert!(file_index.contains("## Module docs")); + assert!(file_index.contains("- `Point`")); + assert!(file_index.contains("- `main() -> i32`")); + assert!(file_index.contains("- `zero`")); + + let project = write_project( + "doc-project", + &[( + "math", + "(module math (export one))\n\n(fn one () -> i32\n 1)\n", + )], + "(module main)\n\n(import math (one))\n\n(fn main () -> i32\n (one))\n", + ); + let project_docs = unique_path("doc-project-out"); + let project_output = run_glagol([ + "doc".as_ref(), + project.as_os_str(), + "-o".as_ref(), + project_docs.as_os_str(), + ]); + assert_success("doc project", &project_output); + let project_index = + fs::read_to_string(project_docs.join("index.md")).expect("read project docs"); + assert!(project_index.contains("# Project doc-project")); + assert!(project_index.contains("## Module math")); + assert!(project_index.contains("## Module main")); + assert!(project_index.contains("- `math`")); +} + +#[cfg(unix)] +#[test] +fn project_tooling_rejects_symlinked_module_escape() { + use std::os::unix::fs::symlink; + + let project = write_project( + "tooling-escape", + &[], + "(module main)\n\n(fn main () -> i32\n 0)\n", + ); + let outside = write_file( + "outside-module", + "(module escape)\n\n(fn value () -> i32\n 1)\n", + ); + symlink(&outside, project.join("src/escape.slo")).expect("create module symlink"); + + let output = run_glagol(["fmt".as_ref(), "--check".as_ref(), project.as_os_str()]); + + assert_exit_code("fmt check symlink escape", &output, 1); + assert_stderr_contains( + "fmt check symlink escape", + &output, + "ProjectManifestInvalid", + ); + assert_stderr_contains( + "fmt check symlink escape", + &output, + "escapes the source root", + ); +} + +#[test] +fn release_gate_script_exists_and_names_required_commands() { + let script = Path::new("../scripts/release-gate.sh"); + let text = fs::read_to_string(script).expect("read release gate script"); + assert!(text.contains("git diff --check")); + assert!(text.contains("cargo fmt --check")); + assert!(text.contains("cargo test")); + assert!(text.contains("dx_v1_7")); + assert!(text.contains("beta_1_0_0")); + assert!(text.contains("beta_v2_0_0_beta_1")); + assert!(text.contains("promotion_gate")); + assert!(text.contains("binary_smoke")); + assert!(text.contains("llvm_smoke")); +} + +fn write_project(name: &str, modules: &[(&str, &str)], main: &str) -> PathBuf { + let project = unique_path(name); + fs::create_dir_all(project.join("src")).expect("create project src"); + fs::write( + project.join("slovo.toml"), + format!( + "[project]\nname = \"{}\"\nsource_root = \"src\"\nentry = \"main\"\n", + name + ), + ) + .expect("write manifest"); + for (module, source) in modules { + fs::write(project.join("src").join(format!("{}.slo", module)), source) + .expect("write module"); + } + fs::write(project.join("src/main.slo"), main).expect("write main"); + project +} + +fn write_file(name: &str, source: &str) -> PathBuf { + let path = unique_path(name).with_extension("slo"); + fs::write(&path, source).expect("write fixture"); + path +} + +fn unique_path(name: &str) -> PathBuf { + let id = NEXT_ID.fetch_add(1, Ordering::Relaxed); + let nanos = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .expect("system clock before UNIX_EPOCH") + .as_nanos(); + std::env::temp_dir().join(format!( + "glagol-dx-{}-{}-{}-{}", + std::process::id(), + nanos, + id, + name + )) +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .output() + .expect("run glagol") +} + +fn assert_success(context: &str, output: &Output) { + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); +} + +fn assert_exit_code(context: &str, output: &Output, expected: i32) { + assert_eq!( + output.status.code(), + Some(expected), + "{} exit code mismatch\nstdout:\n{}\nstderr:\n{}", + context, + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); +} + +fn assert_stderr_contains(context: &str, output: &Output, needle: &str) { + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + stderr.contains(needle), + "{} stderr did not contain `{}`:\n{}", + context, + needle, + stderr + ); +} diff --git a/compiler/tests/enum_payload_direct_scalars_alpha.rs b/compiler/tests/enum_payload_direct_scalars_alpha.rs new file mode 100644 index 0000000..853b5d6 --- /dev/null +++ b/compiler/tests/enum_payload_direct_scalars_alpha.rs @@ -0,0 +1,125 @@ +use std::{ + ffi::OsStr, + path::{Path, PathBuf}, + process::{Command, Output}, +}; + +#[test] +fn enum_payload_direct_scalars_alpha_fixture_emits_direct_payload_aggregates_and_tests_pass() { + let fixture = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/enum-payload-direct-scalars.slo"); + let compile = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + + assert!( + compile.status.success(), + "compiler rejected direct scalar/string enum payload fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define { i32, i32 } @value(i32 %payload)") + && stdout.contains("define { i32, i64 } @wide_value(i64 %payload)") + && stdout.contains("define { i32, double } @ratio_value(double %payload)") + && stdout.contains("define { i32, i1 } @flag_value(i1 %payload)") + && stdout.contains("define { i32, ptr } @label_value(ptr %payload)") + && stdout.contains("extractvalue { i32, ptr }") + && stdout.contains("define ptr @record_label_text(") + && stdout.contains("switch i32"), + "LLVM output did not contain expected direct payload enum aggregate shapes\nstdout:\n{}", + stdout + ); + + let run = run_glagol([OsStr::new("--run-tests"), fixture.as_os_str()]); + assert_success_stdout( + run, + concat!( + "test \"i32 enum constructor equality\" ... ok\n", + "test \"i32 enum equality compares payload\" ... ok\n", + "test \"i32 enum payloadless equality\" ... ok\n", + "test \"i32 enum local return call flow\" ... ok\n", + "test \"i64 enum constructor equality\" ... ok\n", + "test \"i64 enum local return call flow\" ... ok\n", + "test \"f64 enum constructor equality\" ... ok\n", + "test \"f64 enum equality compares payload\" ... ok\n", + "test \"bool enum constructor equality\" ... ok\n", + "test \"bool enum match value\" ... ok\n", + "test \"string enum constructor equality\" ... ok\n", + "test \"string enum equality compares payload\" ... ok\n", + "test \"string enum local return call flow\" ... ok\n", + "test \"struct field enum i32 flow\" ... ok\n", + "test \"struct field enum i64 flow\" ... ok\n", + "test \"struct field enum f64 flow\" ... ok\n", + "test \"struct field enum bool flow\" ... ok\n", + "test \"struct field enum string flow\" ... ok\n", + "18 test(s) passed\n", + ), + ); +} + +#[test] +fn enum_payload_direct_scalars_alpha_formatter_and_lowering_are_visible() { + let fixture = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/enum-payload-direct-scalars.slo"); + + let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]); + assert_success_stdout( + formatted, + &std::fs::read_to_string(&fixture).expect("read enum payload direct scalars fixture"), + ); + + let surface = run_glagol([ + OsStr::new("--inspect-lowering=surface"), + fixture.as_os_str(), + ]); + assert_success_stdout( + surface, + &std::fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../tests/enum-payload-direct-scalars.surface.lower"), + ) + .expect("read surface snapshot"), + ); + + let checked = run_glagol([ + OsStr::new("--inspect-lowering=checked"), + fixture.as_os_str(), + ]); + assert_success_stdout( + checked, + &std::fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../tests/enum-payload-direct-scalars.checked.lower"), + ) + .expect("read checked snapshot"), + ); +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(glagol_bin()) + .args(args) + .output() + .expect("run glagol") +} + +fn glagol_bin() -> PathBuf { + PathBuf::from(env!("CARGO_BIN_EXE_glagol")) +} + +fn assert_success_stdout(output: Output, expected: &str) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "command failed\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert_eq!(stdout, expected); + assert!(stderr.is_empty(), "stderr was not empty:\n{}", stderr); +} diff --git a/compiler/tests/enum_payload_i32_alpha.rs b/compiler/tests/enum_payload_i32_alpha.rs new file mode 100644 index 0000000..6e6869c --- /dev/null +++ b/compiler/tests/enum_payload_i32_alpha.rs @@ -0,0 +1,107 @@ +use std::{ + ffi::OsStr, + path::{Path, PathBuf}, + process::{Command, Output}, +}; + +#[test] +fn enum_payload_i32_alpha_fixture_emits_aggregate_enums_and_tests_pass() { + let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/enum-payload-i32.slo"); + let compile = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + + assert!( + compile.status.success(), + "compiler rejected enum payload fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define { i32, i32 } @value(i32 %payload)") + && stdout.contains("switch i32") + && stdout.contains("extractvalue { i32, i32 }") + && stdout.contains("and i1"), + "LLVM output did not contain expected enum payload aggregate shape\nstdout:\n{}", + stdout + ); + + let run = run_glagol([OsStr::new("--run-tests"), fixture.as_os_str()]); + assert_success_stdout( + run, + concat!( + "test \"enum payload constructor equality\" ... ok\n", + "test \"enum payload equality compares payload\" ... ok\n", + "test \"enum payloadless equality\" ... ok\n", + "test \"enum payload local return call flow\" ... ok\n", + "test \"enum payload parameter equality\" ... ok\n", + "test \"enum payload match missing\" ... ok\n", + "test \"enum payload match value\" ... ok\n", + "test \"enum payload match offset\" ... ok\n", + "8 test(s) passed\n", + ), + ); +} + +#[test] +fn enum_payload_i32_alpha_formatter_and_lowering_are_visible() { + let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/enum-payload-i32.slo"); + + let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]); + assert_success_stdout( + formatted, + &std::fs::read_to_string(&fixture).expect("read enum payload fixture"), + ); + + let surface = run_glagol([ + OsStr::new("--inspect-lowering=surface"), + fixture.as_os_str(), + ]); + assert_success_stdout( + surface, + &std::fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/enum-payload-i32.surface.lower"), + ) + .expect("read surface snapshot"), + ); + + let checked = run_glagol([ + OsStr::new("--inspect-lowering=checked"), + fixture.as_os_str(), + ]); + assert_success_stdout( + checked, + &std::fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/enum-payload-i32.checked.lower"), + ) + .expect("read checked snapshot"), + ); +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(glagol_bin()) + .args(args) + .output() + .expect("run glagol") +} + +fn glagol_bin() -> PathBuf { + PathBuf::from(env!("CARGO_BIN_EXE_glagol")) +} + +fn assert_success_stdout(output: Output, expected: &str) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "command failed\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert_eq!(stdout, expected); + assert!(stderr.is_empty(), "stderr was not empty:\n{}", stderr); +} diff --git a/compiler/tests/enum_payload_structs_alpha.rs b/compiler/tests/enum_payload_structs_alpha.rs new file mode 100644 index 0000000..9eece45 --- /dev/null +++ b/compiler/tests/enum_payload_structs_alpha.rs @@ -0,0 +1,114 @@ +use std::{ + ffi::OsStr, + path::{Path, PathBuf}, + process::{Command, Output}, +}; + +#[test] +fn enum_payload_structs_alpha_fixture_emits_struct_payload_aggregates_and_tests_pass() { + let fixture = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/enum-payload-structs.slo"); + let compile = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + + assert!( + compile.status.success(), + "compiler rejected struct enum payload fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define { [3 x i32], [2 x ptr], [2 x i1] } @make_packet(i32 %base, ptr %head)") + && stdout.contains("define { i32, { [3 x i32], [2 x ptr], [2 x i1] } } @live({ [3 x i32], [2 x ptr], [2 x i1] } %packet)") + && stdout.contains("define { i32, { [3 x i32], [2 x ptr], [2 x i1] } } @cached({ [3 x i32], [2 x ptr], [2 x i1] } %packet)") + && stdout.contains("define ptr @state_label({ i32, { [3 x i32], [2 x ptr], [2 x i1] } } %state, i32 %i)") + && stdout.contains("define i1 @state_flag({ i32, { [3 x i32], [2 x ptr], [2 x i1] } } %state, i32 %i)") + && stdout.contains("extractvalue { i32, { [3 x i32], [2 x ptr], [2 x i1] } }") + && stdout.contains("extractvalue { [3 x i32], [2 x ptr], [2 x i1] }") + && stdout.contains("switch i32") + && stdout.contains("call void @__glagol_array_bounds_trap()"), + "LLVM output did not contain expected struct-payload enum aggregate shape\nstdout:\n{}", + stdout + ); + + let run = run_glagol([OsStr::new("--run-tests"), fixture.as_os_str()]); + assert_success_stdout( + run, + concat!( + "test \"struct payload missing arm\" ... ok\n", + "test \"struct payload live constructor flow\" ... ok\n", + "test \"struct payload cached param return call flow\" ... ok\n", + "test \"struct payload string array field access\" ... ok\n", + "test \"struct payload bool array field access\" ... ok\n", + "test \"struct payload local return flow\" ... ok\n", + "6 test(s) passed\n", + ), + ); + assert!(stderr.is_empty(), "stderr was not empty:\n{}", stderr); +} + +#[test] +fn enum_payload_structs_alpha_formatter_and_lowering_are_visible() { + let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/enum-payload-structs.slo"); + + let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]); + assert_success_stdout( + formatted, + &std::fs::read_to_string(&fixture).expect("read struct enum payload fixture"), + ); + + let surface = run_glagol([ + OsStr::new("--inspect-lowering=surface"), + fixture.as_os_str(), + ]); + assert_success_stdout( + surface, + &std::fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../tests/enum-payload-structs.surface.lower"), + ) + .expect("read surface snapshot"), + ); + + let checked = run_glagol([ + OsStr::new("--inspect-lowering=checked"), + fixture.as_os_str(), + ]); + assert_success_stdout( + checked, + &std::fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../tests/enum-payload-structs.checked.lower"), + ) + .expect("read checked snapshot"), + ); +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(glagol_bin()) + .args(args) + .output() + .expect("run glagol") +} + +fn glagol_bin() -> PathBuf { + PathBuf::from(env!("CARGO_BIN_EXE_glagol")) +} + +fn assert_success_stdout(output: Output, expected: &str) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "command failed\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert_eq!(stdout, expected); + assert!(stderr.is_empty(), "stderr was not empty:\n{}", stderr); +} diff --git a/compiler/tests/enum_struct_fields_alpha.rs b/compiler/tests/enum_struct_fields_alpha.rs new file mode 100644 index 0000000..d219d20 --- /dev/null +++ b/compiler/tests/enum_struct_fields_alpha.rs @@ -0,0 +1,105 @@ +use std::{ + ffi::OsStr, + path::{Path, PathBuf}, + process::{Command, Output}, +}; + +#[test] +fn enum_struct_fields_alpha_fixture_emits_nested_enum_fields_and_tests_pass() { + let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/enum-struct-fields.slo"); + let compile = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + + assert!( + compile.status.success(), + "compiler rejected enum struct field fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define { i32, { i32, i32 } } @make_tagged") + && stdout.contains("extractvalue { i32, { i32, i32 } }") + && stdout.contains("switch i32") + && stdout.contains("and i1"), + "LLVM output did not contain expected enum-in-struct aggregate shape\nstdout:\n{}", + stdout + ); + + let run = run_glagol([OsStr::new("--run-tests"), fixture.as_os_str()]); + assert_success_stdout( + run, + concat!( + "test \"enum struct field payloadless equality\" ... ok\n", + "test \"enum struct field unary payload equality\" ... ok\n", + "test \"enum struct field access return\" ... ok\n", + "test \"enum struct field local param return call flow\" ... ok\n", + "test \"enum struct field match missing\" ... ok\n", + "test \"enum struct field status predicate\" ... ok\n", + "6 test(s) passed\n", + ), + ); +} + +#[test] +fn enum_struct_fields_alpha_formatter_and_lowering_are_visible() { + let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/enum-struct-fields.slo"); + + let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]); + assert_success_stdout( + formatted, + &std::fs::read_to_string(&fixture).expect("read enum struct field fixture"), + ); + + let surface = run_glagol([ + OsStr::new("--inspect-lowering=surface"), + fixture.as_os_str(), + ]); + assert_success_stdout( + surface, + &std::fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/enum-struct-fields.surface.lower"), + ) + .expect("read surface snapshot"), + ); + + let checked = run_glagol([ + OsStr::new("--inspect-lowering=checked"), + fixture.as_os_str(), + ]); + assert_success_stdout( + checked, + &std::fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/enum-struct-fields.checked.lower"), + ) + .expect("read checked snapshot"), + ); +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(glagol_bin()) + .args(args) + .output() + .expect("run glagol") +} + +fn glagol_bin() -> PathBuf { + PathBuf::from(env!("CARGO_BIN_EXE_glagol")) +} + +fn assert_success_stdout(output: Output, expected: &str) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "command failed\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert_eq!(stdout, expected); + assert!(stderr.is_empty(), "stderr was not empty:\n{}", stderr); +} diff --git a/compiler/tests/f64_numeric_primitive_alpha.rs b/compiler/tests/f64_numeric_primitive_alpha.rs new file mode 100644 index 0000000..d0b9a02 --- /dev/null +++ b/compiler/tests/f64_numeric_primitive_alpha.rs @@ -0,0 +1,254 @@ +use std::{ + env, + ffi::OsStr, + fs, + path::{Path, PathBuf}, + process::{Command, Output}, + sync::atomic::{AtomicUsize, Ordering}, +}; + +static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0); + +#[test] +fn f64_fixture_lowers_and_runs_tests() { + let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/f64-numeric-primitive.slo"); + + let llvm = run_glagol([fixture.as_os_str()]); + assert_success("compile f64 fixture", &llvm); + let stdout = String::from_utf8_lossy(&llvm.stdout); + assert!( + stdout.contains("declare void @print_f64(double)") + && stdout.contains("define double @half(double %value)") + && stdout.contains("fadd double") + && stdout.contains("fmul double") + && stdout.contains("fdiv double") + && stdout.contains("fcmp ogt double") + && stdout.contains("fcmp olt double") + && stdout.contains("fcmp oeq double") + && stdout.contains("call void @print_f64(double"), + "f64 LLVM shape drifted\nstdout:\n{}", + stdout + ); + + let tests = run_glagol([OsStr::new("test"), fixture.as_os_str()]); + assert_success("run f64 fixture tests", &tests); + assert_eq!( + String::from_utf8_lossy(&tests.stdout), + concat!( + "test \"f64 arithmetic returns exact fixture value\" ... ok\n", + "test \"f64 comparison works in predicates\" ... ok\n", + "test \"f64 division and equality\" ... ok\n", + "3 test(s) passed\n", + ), + "f64 test runner stdout drifted" + ); +} + +#[test] +fn f64_formatter_and_lowering_are_visible() { + let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/f64-numeric-primitive.slo"); + + let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]); + assert_success("format f64 fixture", &formatted); + let formatted_stdout = String::from_utf8_lossy(&formatted.stdout); + assert!( + formatted_stdout.contains("(fn half ((value f64)) -> f64") + && formatted_stdout.contains("(/ value 2.0)") + && formatted_stdout.contains("(std.io.print_f64 (local_total))"), + "f64 formatter output omitted expected forms\nstdout:\n{}", + formatted_stdout + ); + + let surface = run_glagol([ + OsStr::new("--inspect-lowering=surface"), + fixture.as_os_str(), + ]); + assert_success("inspect f64 surface lowering", &surface); + let surface_stdout = String::from_utf8_lossy(&surface.stdout); + assert_eq!( + surface_stdout, + fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../tests/f64-numeric-primitive.surface.lower") + ) + .expect("read f64 surface snapshot"), + "f64 surface lowering snapshot drifted" + ); + + let checked = run_glagol([ + OsStr::new("--inspect-lowering=checked"), + fixture.as_os_str(), + ]); + assert_success("inspect f64 checked lowering", &checked); + let checked_stdout = String::from_utf8_lossy(&checked.stdout); + assert_eq!( + checked_stdout, + fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../tests/f64-numeric-primitive.checked.lower") + ) + .expect("read f64 checked snapshot"), + "f64 checked lowering snapshot drifted" + ); +} + +#[test] +fn mixed_i32_f64_operands_are_rejected_clearly() { + let fixture = write_fixture( + "mixed-numeric", + r#" +(module main) + +(fn main () -> i32 + (if (= 1 1.0) 0 1)) +"#, + ); + let output = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + !output.status.success(), + "compiler unexpectedly accepted mixed numeric operands\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.is_empty(), + "rejected compile wrote stdout:\n{}", + stdout + ); + assert!( + stderr.contains("numeric operands must have the same primitive type") + && stderr.contains("mixed i32/i64/u32/u64/f64"), + "mixed numeric diagnostic drifted\nstderr:\n{}", + stderr + ); +} + +#[test] +fn f64_runtime_print_smoke_when_clang_is_available() { + let Some(clang) = find_clang() else { + eprintln!("skipping f64 runtime smoke: set GLAGOL_CLANG or install clang"); + return; + }; + + let fixture = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/f64-numeric-primitive.slo"); + let compile = run_glagol([fixture.as_os_str()]); + assert_success("compile f64 runtime smoke", &compile); + let run = compile_and_run_with_runtime(&clang, "f64-runtime-smoke", &compile.stdout); + assert_success("run f64 runtime smoke", &run); + assert_eq!( + String::from_utf8_lossy(&run.stdout), + "10\n", + "f64 runtime print stdout drifted" + ); +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn write_fixture(name: &str, source: &str) -> PathBuf { + let mut path = env::temp_dir(); + path.push(format!( + "glagol-f64-alpha-{}-{}-{}.slo", + name, + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + fs::write(&path, source).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err)); + path +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); +} + +fn compile_and_run_with_runtime(clang: &Path, name: &str, ir: &[u8]) -> Output { + let manifest = Path::new(env!("CARGO_MANIFEST_DIR")); + let temp_dir = env::temp_dir().join(format!( + "glagol-f64-alpha-{}-{}", + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + fs::create_dir_all(&temp_dir) + .unwrap_or_else(|err| panic!("create `{}`: {}", temp_dir.display(), err)); + + let ir_path = temp_dir.join(format!("{}.ll", name)); + let exe_path = temp_dir.join(name); + fs::write(&ir_path, ir).unwrap_or_else(|err| panic!("write `{}`: {}", ir_path.display(), err)); + + let runtime = manifest.join("../runtime/runtime.c"); + let mut clang_command = Command::new(clang); + clang_command + .arg(&runtime) + .arg(&ir_path) + .arg("-o") + .arg(&exe_path) + .current_dir(manifest); + configure_clang_runtime_env(&mut clang_command, clang); + let clang_output = clang_command + .output() + .unwrap_or_else(|err| panic!("run `{}`: {}", clang.display(), err)); + assert_success("clang f64 runtime smoke", &clang_output); + + Command::new(&exe_path) + .output() + .unwrap_or_else(|err| panic!("run `{}`: {}", exe_path.display(), err)) +} + +fn find_clang() -> Option { + if let Some(path) = env::var_os("GLAGOL_CLANG").filter(|value| !value.is_empty()) { + return Some(PathBuf::from(path)); + } + + let hermetic_clang = PathBuf::from("/tmp/glagol-clang-root/usr/bin/clang"); + if hermetic_clang.is_file() { + return Some(hermetic_clang); + } + + find_on_path("clang") +} + +fn find_on_path(program: &str) -> Option { + let path = env::var_os("PATH")?; + env::split_paths(&path) + .map(|dir| dir.join(program)) + .find(|candidate| candidate.is_file()) +} + +fn configure_clang_runtime_env(command: &mut Command, clang: &Path) { + if !clang.starts_with("/tmp/glagol-clang-root") { + return; + } + + let root = Path::new("/tmp/glagol-clang-root"); + let lib64 = root.join("usr/lib64"); + let lib = root.join("usr/lib"); + let mut paths = vec![lib64, lib]; + + if let Some(existing) = env::var_os("LD_LIBRARY_PATH") { + paths.extend(env::split_paths(&existing)); + } + + let joined = env::join_paths(paths).expect("join LD_LIBRARY_PATH"); + command.env("LD_LIBRARY_PATH", joined); +} diff --git a/compiler/tests/f64_to_i32_result_alpha.rs b/compiler/tests/f64_to_i32_result_alpha.rs new file mode 100644 index 0000000..b297f26 --- /dev/null +++ b/compiler/tests/f64_to_i32_result_alpha.rs @@ -0,0 +1,232 @@ +use std::{ + env, + ffi::OsStr, + fs, + path::{Path, PathBuf}, + process::{Command, Output}, + sync::atomic::{AtomicUsize, Ordering}, +}; + +static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0); + +#[test] +fn f64_to_i32_result_fixture_lowers_and_runs_tests() { + let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/f64-to-i32-result.slo"); + + let llvm = run_glagol([fixture.as_os_str()]); + assert_success("compile f64-to-i32-result fixture", &llvm); + let stdout = String::from_utf8_lossy(&llvm.stdout); + assert!( + stdout.contains("fcmp oge double") + && stdout.contains("-2147483648.0") + && stdout.contains("fcmp ole double") + && stdout.contains("2147483647.0") + && stdout.contains("br i1 %") + && stdout.contains("f64.to_i32.integral") + && stdout.contains("f64.to_i32.exponent") + && stdout.contains("f64.to_i32.fraction") + && stdout.contains("bitcast double") + && stdout.contains("lshr i64") + && stdout.contains("fptosi double") + && !stdout.contains("frem double") + && stdout.contains("phi { i1, i32 }") + && !stdout.contains("@std.num.f64_to_i32_result") + && !stdout.contains("__glagol_num_f64_to_i32_result"), + "f64-to-i32-result LLVM shape drifted\nstdout:\n{}", + stdout + ); + let integral_pos = stdout + .find("f64.to_i32.integral") + .expect("integral check block is present"); + let fraction_pos = stdout + .find("f64.to_i32.fraction") + .expect("fraction bit check block is present"); + let fptosi_pos = stdout + .find("fptosi double") + .expect("narrowing conversion is present"); + assert!( + integral_pos < fraction_pos && fraction_pos < fptosi_pos, + "f64-to-i32-result must prove integrality before fptosi\nstdout:\n{}", + stdout + ); + + let tests = run_glagol([OsStr::new("test"), fixture.as_os_str()]); + assert_success("run f64-to-i32-result fixture tests", &tests); + assert_eq!( + String::from_utf8_lossy(&tests.stdout), + concat!( + "test \"f64 zero narrows to i32\" ... ok\n", + "test \"negative f64 narrows to i32\" ... ok\n", + "test \"fractional f64 returns err\" ... ok\n", + "test \"above i32 range f64 returns err\" ... ok\n", + "4 test(s) passed\n", + ), + "f64-to-i32-result test runner stdout drifted" + ); +} + +#[test] +fn f64_to_i32_result_non_finite_returns_err_in_test_runner() { + let fixture = write_fixture( + "non-finite", + r#" +(module main) + +(fn main () -> i32 + 0) + +(test "infinity returns err" + (let value (result i32 i32) (std.num.f64_to_i32_result (/ 1.0 0.0))) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) +"#, + ); + + let tests = run_glagol([OsStr::new("test"), fixture.as_os_str()]); + assert_success("run f64-to-i32-result non-finite test", &tests); + assert_eq!( + String::from_utf8_lossy(&tests.stdout), + concat!( + "test \"infinity returns err\" ... ok\n", + "1 test(s) passed\n", + ), + "f64-to-i32-result non-finite test runner stdout drifted" + ); +} + +#[test] +fn f64_to_i32_result_formatter_and_lowering_are_visible() { + let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/f64-to-i32-result.slo"); + + let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]); + assert_success("format f64-to-i32-result fixture", &formatted); + let formatted_stdout = String::from_utf8_lossy(&formatted.stdout); + assert!( + formatted_stdout.contains("(std.num.f64_to_i32_result value)") + && formatted_stdout.contains("(narrow 2147483648.0)") + && formatted_stdout.contains("(std.result.unwrap_err value)"), + "f64-to-i32-result formatter output omitted expected calls\nstdout:\n{}", + formatted_stdout + ); + + let surface = run_glagol([ + OsStr::new("--inspect-lowering=surface"), + fixture.as_os_str(), + ]); + assert_success("inspect f64-to-i32-result surface lowering", &surface); + assert_eq!( + String::from_utf8_lossy(&surface.stdout), + fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/f64-to-i32-result.surface.lower") + ) + .expect("read f64-to-i32-result surface snapshot"), + "f64-to-i32-result surface lowering snapshot drifted" + ); + + let checked = run_glagol([ + OsStr::new("--inspect-lowering=checked"), + fixture.as_os_str(), + ]); + assert_success("inspect f64-to-i32-result checked lowering", &checked); + assert_eq!( + String::from_utf8_lossy(&checked.stdout), + fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/f64-to-i32-result.checked.lower") + ) + .expect("read f64-to-i32-result checked snapshot"), + "f64-to-i32-result checked lowering snapshot drifted" + ); +} + +#[test] +fn f64_to_i32_result_rejections_are_explicit() { + for (name, source, expected) in [ + ( + "arity", + "(module main)\n\n(fn main () -> (result i32 i32)\n (std.num.f64_to_i32_result))\n", + "wrong number of arguments", + ), + ( + "type", + "(module main)\n\n(fn main () -> (result i32 i32)\n (std.num.f64_to_i32_result 1))\n", + "cannot call `std.num.f64_to_i32_result` with argument of wrong type", + ), + ( + "context", + "(module main)\n\n(fn main () -> i32\n (std.num.f64_to_i32_result 1.0))\n", + "function `main` returns wrong type", + ), + ( + "unchecked", + "(module main)\n\n(fn main () -> i32\n (std.num.f64_to_i32 1.0)\n 0)\n", + "standard library call `std.num.f64_to_i32` is not supported", + ), + ( + "cast-checked", + "(module main)\n\n(fn main () -> i32\n (std.num.cast_checked 1.0)\n 0)\n", + "standard library call `std.num.cast_checked` is not supported", + ), + ] { + let fixture = write_fixture(name, source); + let output = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + !output.status.success(), + "compiler unexpectedly accepted f64-to-i32-result rejection fixture `{}`\nstdout:\n{}\nstderr:\n{}", + name, + stdout, + stderr + ); + assert!( + stdout.is_empty(), + "rejected compile wrote stdout for `{}`:\n{}", + name, + stdout + ); + assert!( + stderr.contains(expected), + "f64-to-i32-result diagnostic drifted for `{}`\nstderr:\n{}", + name, + stderr + ); + } +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn write_fixture(name: &str, source: &str) -> PathBuf { + let mut path = env::temp_dir(); + path.push(format!( + "glagol-f64-to-i32-result-alpha-{}-{}-{}.slo", + name, + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + fs::write(&path, source).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err)); + path +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); +} diff --git a/compiler/tests/f64_to_i64_result_alpha.rs b/compiler/tests/f64_to_i64_result_alpha.rs new file mode 100644 index 0000000..18c6499 --- /dev/null +++ b/compiler/tests/f64_to_i64_result_alpha.rs @@ -0,0 +1,306 @@ +use std::{ + env, + ffi::OsStr, + fs, + path::{Path, PathBuf}, + process::{Command, Output}, + sync::atomic::{AtomicUsize, Ordering}, +}; + +static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0); + +#[test] +fn f64_to_i64_result_fixture_lowers_and_runs_tests() { + let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/f64-to-i64-result.slo"); + + let llvm = run_glagol([fixture.as_os_str()]); + assert_success("compile f64-to-i64-result fixture", &llvm); + let stdout = String::from_utf8_lossy(&llvm.stdout); + assert!( + stdout.contains("fcmp oge double") + && stdout.contains("-9223372036854775808.0") + && stdout.contains("fcmp olt double") + && stdout.contains("9223372036854775808.0") + && stdout.contains("br i1 %") + && stdout.contains("f64.to_i64.integral") + && stdout.contains("f64.to_i64.exponent") + && stdout.contains("f64.to_i64.mantissa") + && stdout.contains("f64.to_i64.fraction") + && stdout.contains("bitcast double") + && stdout.contains("lshr i64") + && stdout.contains("icmp uge i64") + && stdout.contains("fptosi double") + && stdout.contains("to i64") + && !stdout.contains("frem double") + && stdout.contains("phi { i1, i64, i32 }") + && !stdout.contains("@std.num.f64_to_i64_result") + && !stdout.contains("__glagol_num_f64_to_i64_result"), + "f64-to-i64-result LLVM shape drifted\nstdout:\n{}", + stdout + ); + let integral_pos = stdout + .find("f64.to_i64.integral") + .expect("integral check block is present"); + let fraction_pos = stdout + .find("f64.to_i64.fraction") + .expect("fraction bit check block is present"); + let fptosi_pos = stdout + .find("fptosi double") + .expect("narrowing conversion is present"); + assert!( + integral_pos < fraction_pos && fraction_pos < fptosi_pos, + "f64-to-i64-result must prove integrality before fptosi\nstdout:\n{}", + stdout + ); + + let tests = run_glagol([OsStr::new("test"), fixture.as_os_str()]); + assert_success("run f64-to-i64-result fixture tests", &tests); + assert_eq!( + String::from_utf8_lossy(&tests.stdout), + concat!( + "test \"f64 zero narrows to i64\" ... ok\n", + "test \"negative f64 narrows to i64\" ... ok\n", + "test \"fractional f64 returns err for i64\" ... ok\n", + "test \"above i64 range f64 returns err\" ... ok\n", + "4 test(s) passed\n", + ), + "f64-to-i64-result test runner stdout drifted" + ); +} + +#[test] +fn f64_to_i64_result_non_finite_returns_err_in_test_runner() { + let fixture = write_fixture( + "non-finite", + r#" +(module main) + +(fn main () -> i32 + 0) + +(test "infinity returns err for i64" + (let value (result i64 i32) (std.num.f64_to_i64_result (/ 1.0 0.0))) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) +"#, + ); + + let tests = run_glagol([OsStr::new("test"), fixture.as_os_str()]); + assert_success("run f64-to-i64-result non-finite test", &tests); + assert_eq!( + String::from_utf8_lossy(&tests.stdout), + concat!( + "test \"infinity returns err for i64\" ... ok\n", + "1 test(s) passed\n", + ), + "f64-to-i64-result non-finite test runner stdout drifted" + ); +} + +#[test] +fn f64_to_i64_result_non_finite_returns_err_in_hosted_runtime_when_available() { + let fixture = write_fixture( + "runtime-non-finite", + r#" +(module main) + +(fn main () -> i32 + (let value (result i64 i32) (std.num.f64_to_i64_result (/ 1.0 0.0))) + (if (std.result.is_err value) + (std.result.unwrap_err value) + 99)) +"#, + ); + let binary = unique_path("f64-to-i64-result-bin"); + + let build = run_glagol([ + OsStr::new("build"), + fixture.as_os_str(), + OsStr::new("-o"), + binary.as_os_str(), + ]); + if !build.status.success() { + let stdout = String::from_utf8_lossy(&build.stdout); + let stderr = String::from_utf8_lossy(&build.stderr); + assert!( + stdout.is_empty(), + "failed f64-to-i64-result build wrote stdout:\n{}", + stdout + ); + assert!( + stderr.contains("ToolchainUnavailable"), + "f64-to-i64-result build failed unexpectedly\nstderr:\n{}", + stderr + ); + return; + } + + let run = Command::new(&binary) + .output() + .expect("run f64-to-i64-result binary"); + assert_eq!( + run.status.code(), + Some(1), + "f64-to-i64-result binary exit code drifted\nstdout:\n{}\nstderr:\n{}", + String::from_utf8_lossy(&run.stdout), + String::from_utf8_lossy(&run.stderr) + ); + assert!( + run.stdout.is_empty(), + "f64-to-i64-result binary wrote stdout:\n{}", + String::from_utf8_lossy(&run.stdout) + ); + assert!( + run.stderr.is_empty(), + "f64-to-i64-result binary wrote stderr:\n{}", + String::from_utf8_lossy(&run.stderr) + ); +} + +#[test] +fn f64_to_i64_result_formatter_and_lowering_are_visible() { + let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/f64-to-i64-result.slo"); + + let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]); + assert_success("format f64-to-i64-result fixture", &formatted); + let formatted_stdout = String::from_utf8_lossy(&formatted.stdout); + assert!( + formatted_stdout.contains("(std.num.f64_to_i64_result value)") + && formatted_stdout.contains("(narrow 9223372036854776000.0)") + && formatted_stdout.contains("(std.result.unwrap_err value)"), + "f64-to-i64-result formatter output omitted expected calls\nstdout:\n{}", + formatted_stdout + ); + + let surface = run_glagol([ + OsStr::new("--inspect-lowering=surface"), + fixture.as_os_str(), + ]); + assert_success("inspect f64-to-i64-result surface lowering", &surface); + assert_eq!( + String::from_utf8_lossy(&surface.stdout), + fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/f64-to-i64-result.surface.lower") + ) + .expect("read f64-to-i64-result surface snapshot"), + "f64-to-i64-result surface lowering snapshot drifted" + ); + + let checked = run_glagol([ + OsStr::new("--inspect-lowering=checked"), + fixture.as_os_str(), + ]); + assert_success("inspect f64-to-i64-result checked lowering", &checked); + assert_eq!( + String::from_utf8_lossy(&checked.stdout), + fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/f64-to-i64-result.checked.lower") + ) + .expect("read f64-to-i64-result checked snapshot"), + "f64-to-i64-result checked lowering snapshot drifted" + ); +} + +#[test] +fn f64_to_i64_result_rejections_are_explicit() { + for (name, source, expected) in [ + ( + "arity", + "(module main)\n\n(fn main () -> (result i64 i32)\n (std.num.f64_to_i64_result))\n", + "wrong number of arguments", + ), + ( + "type", + "(module main)\n\n(fn main () -> (result i64 i32)\n (std.num.f64_to_i64_result 1))\n", + "cannot call `std.num.f64_to_i64_result` with argument of wrong type", + ), + ( + "context", + "(module main)\n\n(fn main () -> i32\n (std.num.f64_to_i64_result 1.0))\n", + "function `main` returns wrong type", + ), + ( + "unchecked", + "(module main)\n\n(fn main () -> i32\n (std.num.f64_to_i64 1.0)\n 0)\n", + "standard library call `std.num.f64_to_i64` is not supported", + ), + ( + "cast-checked", + "(module main)\n\n(fn main () -> i32\n (std.num.cast_checked 1.0)\n 0)\n", + "standard library call `std.num.cast_checked` is not supported", + ), + ] { + let fixture = write_fixture(name, source); + let output = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + !output.status.success(), + "compiler unexpectedly accepted f64-to-i64-result rejection fixture `{}`\nstdout:\n{}\nstderr:\n{}", + name, + stdout, + stderr + ); + assert!( + stdout.is_empty(), + "rejected compile wrote stdout for `{}`:\n{}", + name, + stdout + ); + assert!( + stderr.contains(expected), + "f64-to-i64-result diagnostic drifted for `{}`\nstderr:\n{}", + name, + stderr + ); + } +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn write_fixture(name: &str, source: &str) -> PathBuf { + let mut path = env::temp_dir(); + path.push(format!( + "glagol-f64-to-i64-result-alpha-{}-{}-{}.slo", + name, + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + fs::write(&path, source).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err)); + path +} + +fn unique_path(stem: &str) -> PathBuf { + let mut path = env::temp_dir(); + path.push(format!( + "glagol-{}-{}-{}", + stem, + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + path +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); +} diff --git a/compiler/tests/f64_to_string_alpha.rs b/compiler/tests/f64_to_string_alpha.rs new file mode 100644 index 0000000..1a55384 --- /dev/null +++ b/compiler/tests/f64_to_string_alpha.rs @@ -0,0 +1,222 @@ +use std::{ + env, + ffi::OsStr, + fs, + path::{Path, PathBuf}, + process::{Command, Output}, + sync::atomic::{AtomicUsize, Ordering}, +}; + +static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0); + +#[test] +fn f64_to_string_fixture_lowers_and_runs_tests() { + let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/f64-to-string.slo"); + + let llvm = run_glagol([fixture.as_os_str()]); + assert_success("compile f64-to-string fixture", &llvm); + let stdout = String::from_utf8_lossy(&llvm.stdout); + assert!( + stdout.contains("declare ptr @__glagol_num_f64_to_string(double)") + && stdout.contains("call ptr @__glagol_num_f64_to_string(double ") + && stdout.contains("call void @print_string(ptr %") + && !stdout.contains("@std.num.f64_to_string"), + "f64-to-string LLVM shape drifted\nstdout:\n{}", + stdout + ); + + let tests = run_glagol([OsStr::new("test"), fixture.as_os_str()]); + assert_success("run f64-to-string fixture tests", &tests); + assert_eq!( + String::from_utf8_lossy(&tests.stdout), + concat!( + "test \"f64 zero to string\" ... ok\n", + "test \"f64 fractional to string\" ... ok\n", + "test \"f64 negative to string\" ... ok\n", + "test \"f64 whole to string\" ... ok\n", + "test \"f64 negative string length\" ... ok\n", + "test \"f64 whole string length\" ... ok\n", + "6 test(s) passed\n", + ), + "f64-to-string test runner stdout drifted" + ); +} + +#[test] +fn f64_to_string_hosted_runtime_formats_fixture_values_when_clang_is_available() { + let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/f64-to-string.slo"); + let binary = unique_path("f64-to-string-bin"); + + let build = run_glagol([ + OsStr::new("build"), + fixture.as_os_str(), + OsStr::new("-o"), + binary.as_os_str(), + ]); + if !build.status.success() { + let stdout = String::from_utf8_lossy(&build.stdout); + let stderr = String::from_utf8_lossy(&build.stderr); + assert!( + stdout.is_empty(), + "failed f64-to-string build wrote stdout:\n{}", + stdout + ); + assert!( + stderr.contains("ToolchainUnavailable"), + "f64-to-string build failed unexpectedly\nstderr:\n{}", + stderr + ); + return; + } + + let run = Command::new(&binary) + .output() + .expect("run f64-to-string binary"); + assert_success("run f64-to-string binary", &run); + assert_eq!( + String::from_utf8_lossy(&run.stdout), + "0.0\n3.5\n-1.5\n10.0\n", + "f64-to-string binary stdout drifted" + ); +} + +#[test] +fn f64_to_string_formatter_and_lowering_are_visible() { + let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/f64-to-string.slo"); + + let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]); + assert_success("format f64-to-string fixture", &formatted); + let formatted_stdout = String::from_utf8_lossy(&formatted.stdout); + assert!( + formatted_stdout.contains("(std.num.f64_to_string (/ 7.0 2.0))") + && formatted_stdout.contains("(std.io.print_string (f64_whole_text))"), + "f64-to-string formatter output omitted expected calls\nstdout:\n{}", + formatted_stdout + ); + + let surface = run_glagol([ + OsStr::new("--inspect-lowering=surface"), + fixture.as_os_str(), + ]); + assert_success("inspect f64-to-string surface lowering", &surface); + assert_eq!( + String::from_utf8_lossy(&surface.stdout), + fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/f64-to-string.surface.lower") + ) + .expect("read f64-to-string surface snapshot"), + "f64-to-string surface lowering snapshot drifted" + ); + + let checked = run_glagol([ + OsStr::new("--inspect-lowering=checked"), + fixture.as_os_str(), + ]); + assert_success("inspect f64-to-string checked lowering", &checked); + assert_eq!( + String::from_utf8_lossy(&checked.stdout), + fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/f64-to-string.checked.lower") + ) + .expect("read f64-to-string checked snapshot"), + "f64-to-string checked lowering snapshot drifted" + ); +} + +#[test] +fn f64_to_string_rejections_are_explicit() { + for (name, source, expected) in [ + ( + "arity", + "(module main)\n\n(fn main () -> string\n (std.num.f64_to_string))\n", + "wrong number of arguments", + ), + ( + "type", + "(module main)\n\n(fn main () -> string\n (std.num.f64_to_string 1))\n", + "cannot call `std.num.f64_to_string` with argument of wrong type", + ), + ( + "generic-to-string", + "(module main)\n\n(fn main () -> i32\n (std.num.to_string 1.0)\n 0)\n", + "standard library call `std.num.to_string` is not supported", + ), + ( + "f64-parse", + "(module main)\n\n(fn main () -> i32\n (std.string.parse_f64 \"1.0\")\n 0)\n", + "standard library call `std.string.parse_f64` is not supported", + ), + ] { + let fixture = write_fixture(name, source); + let output = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + !output.status.success(), + "compiler unexpectedly accepted f64-to-string rejection fixture `{}`\nstdout:\n{}\nstderr:\n{}", + name, + stdout, + stderr + ); + assert!( + stdout.is_empty(), + "rejected compile wrote stdout for `{}`:\n{}", + name, + stdout + ); + assert!( + stderr.contains(expected), + "f64-to-string diagnostic drifted for `{}`\nstderr:\n{}", + name, + stderr + ); + } +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn write_fixture(name: &str, source: &str) -> PathBuf { + let mut path = env::temp_dir(); + path.push(format!( + "glagol-f64-to-string-alpha-{}-{}-{}.slo", + name, + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + fs::write(&path, source).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err)); + path +} + +fn unique_path(name: &str) -> PathBuf { + let mut path = env::temp_dir(); + path.push(format!( + "glagol-{}-{}-{}", + name, + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + path +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); +} diff --git a/compiler/tests/formatter.rs b/compiler/tests/formatter.rs new file mode 100644 index 0000000..a8bd604 --- /dev/null +++ b/compiler/tests/formatter.rs @@ -0,0 +1,590 @@ +use std::{ + fs, + path::{Path, PathBuf}, + process::{Command, Output}, + sync::atomic::{AtomicUsize, Ordering}, +}; + +static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0); + +#[test] +fn local_canonical_fixture_is_formatter_stable() { + let compiler = env!("CARGO_BIN_EXE_glagol"); + let fixture = Path::new("../tests/canonical.fmt"); + let expected = fs::read_to_string("../tests/canonical.fmt").expect("read canonical.fmt"); + + let output = run_formatter(compiler, fixture); + + assert_success(&output); + assert_eq!( + String::from_utf8(output.stdout).expect("formatter output is UTF-8"), + expected, + ); + assert!( + output.stderr.is_empty(), + "formatter wrote stderr:\n{}", + String::from_utf8_lossy(&output.stderr), + ); +} + +#[test] +fn local_top_level_test_fixture_is_formatter_stable() { + let compiler = env!("CARGO_BIN_EXE_glagol"); + let fixture = Path::new("../tests/top-level-test.fmt"); + let expected = + fs::read_to_string("../tests/top-level-test.fmt").expect("read top-level-test.fmt"); + + let output = run_formatter(compiler, fixture); + + assert_success(&output); + assert_eq!( + String::from_utf8(output.stdout).expect("formatter output is UTF-8"), + expected, + ); + assert!( + output.stderr.is_empty(), + "formatter wrote stderr:\n{}", + String::from_utf8_lossy(&output.stderr), + ); +} + +#[test] +fn local_v1_formatter_stability_fixture_is_formatter_stable() { + let compiler = env!("CARGO_BIN_EXE_glagol"); + let fixture = Path::new("../tests/formatter-stability-v1.fmt"); + let expected = fs::read_to_string("../tests/formatter-stability-v1.fmt") + .expect("read formatter-stability-v1.fmt"); + + let output = run_formatter(compiler, fixture); + + assert_success(&output); + assert_eq!( + String::from_utf8(output.stdout).expect("formatter output is UTF-8"), + expected, + ); + assert!( + output.stderr.is_empty(), + "formatter wrote stderr:\n{}", + String::from_utf8_lossy(&output.stderr), + ); +} + +#[test] +fn local_comments_fixture_is_formatter_stable() { + let compiler = env!("CARGO_BIN_EXE_glagol"); + let fixture = Path::new("../tests/comments.slo"); + let expected = fs::read_to_string("../tests/comments.slo").expect("read comments.slo"); + + let output = run_formatter(compiler, fixture); + + assert_success(&output); + assert_eq!( + String::from_utf8(output.stdout).expect("formatter output is UTF-8"), + expected, + ); + assert!( + output.stderr.is_empty(), + "formatter wrote stderr:\n{}", + String::from_utf8_lossy(&output.stderr), + ); +} + +#[test] +fn formatter_canonicalizes_supported_syntax() { + let compiler = env!("CARGO_BIN_EXE_glagol"); + let fixture = write_fixture( + "supported-messy", + r#"; status: formatter-canonical + ; Scope: current strict supported syntax only. +(module main) +(fn add ( + (a i32) + (b i32)) -> i32 (+ a b)) + +(fn main() -> i32 +(print_i32(add 20 22)) +0) +"#, + ); + let expected = fs::read_to_string("../tests/canonical.fmt").expect("read canonical.fmt"); + + let output = run_formatter(compiler, &fixture); + + assert_success(&output); + assert_eq!( + String::from_utf8(output.stdout).expect("formatter output is UTF-8"), + expected, + ); +} + +#[test] +fn formatter_canonicalizes_struct_forms() { + let compiler = env!("CARGO_BIN_EXE_glagol"); + let fixture = write_fixture( + "struct", + r#" +(module main) + +(struct Point (x i32) (y i32)) + +(fn point_sum () -> i32 + (+ (. (Point (x 20) (y 22)) x) (. (Point (x 20) (y 22)) y))) + +(fn main () -> i32 + (point_sum)) +"#, + ); + let expected = concat!( + "(module main)\n", + "\n", + "(struct Point\n", + " (x i32)\n", + " (y i32))\n", + "\n", + "(fn point_sum () -> i32\n", + " (+ (. (Point (x 20) (y 22)) x) (. (Point (x 20) (y 22)) y)))\n", + "\n", + "(fn main () -> i32\n", + " (point_sum))\n", + ); + + let output = run_formatter(compiler, &fixture); + + assert_success(&output); + assert_eq!( + String::from_utf8(output.stdout).expect("formatter output is UTF-8"), + expected, + ); +} + +#[test] +fn formatter_canonicalizes_top_level_tests() { + let compiler = env!("CARGO_BIN_EXE_glagol"); + let fixture = write_fixture( + "top-level-test", + r#"; status: formatter-canonical + ; Scope: promoted top-level test formatter contract. +(module tests) + +(fn add ( + (a i32) + (b i32)) -> i32 (+ a b)) + +(test "add works" (= (add 2 3) 5)) + +(fn main() -> i32 0) +"#, + ); + let expected = + fs::read_to_string("../tests/top-level-test.fmt").expect("read top-level-test.fmt"); + + let output = run_formatter(compiler, &fixture); + + assert_success(&output); + assert_eq!( + String::from_utf8(output.stdout).expect("formatter output is UTF-8"), + expected, + ); +} + +#[test] +fn formatter_keeps_long_inline_forms_inline() { + let compiler = env!("CARGO_BIN_EXE_glagol"); + let fixture = write_fixture( + "long-inline", + r#" +(module main) + +(fn accept_many ( + (a i32) + (b i32) + (c i32) + (d i32) + (e i32) + (f i32) + (g i32) + (h i32) + (i i32) + (j i32)) -> i32 + (+ a j)) + +(fn main () -> i32 + (accept_many + 100000001 + 100000002 + 100000003 + 100000004 + 100000005 + 100000006 + 100000007 + 100000008 + 100000009 + 100000010)) +"#, + ); + let expected = concat!( + "(module main)\n", + "\n", + "(fn accept_many ((a i32) (b i32) (c i32) (d i32) (e i32) (f i32) (g i32) (h i32) (i i32) (j i32)) -> i32\n", + " (+ a j))\n", + "\n", + "(fn main () -> i32\n", + " (accept_many 100000001 100000002 100000003 100000004 100000005 100000006 100000007 100000008 100000009 100000010))\n", + ); + + let output = run_formatter(compiler, &fixture); + + assert_success(&output); + assert_eq!( + String::from_utf8(output.stdout).expect("formatter output is UTF-8"), + expected, + ); +} + +#[test] +fn formatter_rejects_test_names_outside_v0_subset() { + let compiler = env!("CARGO_BIN_EXE_glagol"); + let fixture = write_fixture( + "invalid-test-name", + r#" +(module main) + +(test "bad\nname" + true) +"#, + ); + + let output = run_formatter(compiler, &fixture); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + !output.status.success(), + "formatter unexpectedly accepted invalid test name\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr, + ); + assert!( + stdout.is_empty(), + "formatter emitted stdout for rejected test name\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr, + ); + assert!( + stderr.contains("error[InvalidTestName]") || stderr.contains("(code InvalidTestName)"), + "stderr did not contain InvalidTestName\nstderr:\n{}", + stderr, + ); +} + +#[test] +fn formatter_reports_unsupported_standard_library_calls() { + let compiler = env!("CARGO_BIN_EXE_glagol"); + let cases = [ + ( + "unsupported-std-call", + r#" +(module main) + +(fn main () -> i32 + (std.io.print_unit 0) + 0) +"#, + ), + ( + "unsupported-std-user-call", + r#" +(module main) + +(fn std.io.print_unit ((value i32)) -> i32 + value) + +(fn main () -> i32 + (std.io.print_unit 0)) +"#, + ), + ]; + + for (name, source) in cases { + let fixture = write_fixture(name, source); + let output = run_formatter(compiler, &fixture); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + !output.status.success(), + "formatter unexpectedly accepted unsupported std call `{}`\nstdout:\n{}\nstderr:\n{}", + name, + stdout, + stderr, + ); + assert!( + stdout.is_empty(), + "formatter emitted stdout for unsupported std call `{}`\nstdout:\n{}\nstderr:\n{}", + name, + stdout, + stderr, + ); + assert!( + stderr.contains("UnsupportedStandardLibraryCall"), + "formatter stderr did not contain UnsupportedStandardLibraryCall for `{}`\nstderr:\n{}", + name, + stderr, + ); + } +} + +#[test] +fn formatter_preserves_full_line_comments_inside_function_bodies() { + let compiler = env!("CARGO_BIN_EXE_glagol"); + let fixture = write_fixture( + "body-comments", + r#" +(module main) + +(fn add ((a i32) (b i32)) -> i32 + ; keep this in add + (+ a b)) + +(fn main () -> i32 + ; before effect + (print_i32 (add 20 21)) + ; before result + (+ 20 22) + ; after result +) +"#, + ); + + let output = run_formatter(compiler, &fixture); + + assert_success(&output); + assert_eq!( + String::from_utf8(output.stdout).expect("formatter output is UTF-8"), + concat!( + "(module main)\n", + "\n", + "(fn add ((a i32) (b i32)) -> i32\n", + " ; keep this in add\n", + " (+ a b))\n", + "\n", + "(fn main () -> i32\n", + " ; before effect\n", + " (print_i32 (add 20 21))\n", + " ; before result\n", + " (+ 20 22)\n", + " ; after result\n", + ")\n", + ), + ); +} + +#[test] +fn formatter_preserves_full_line_comments_after_last_form() { + let compiler = env!("CARGO_BIN_EXE_glagol"); + let fixture = write_fixture( + "trailing-comments", + r#" +(module main) + +(fn main () -> i32 + 0) + +; keep this file trailer + ; and normalize indentation +"#, + ); + + let output = run_formatter(compiler, &fixture); + + assert_success(&output); + assert_eq!( + String::from_utf8(output.stdout).expect("formatter output is UTF-8"), + concat!( + "(module main)\n", + "\n", + "(fn main () -> i32\n", + " 0)\n", + "\n", + "; keep this file trailer\n", + "; and normalize indentation\n", + ), + ); +} + +#[test] +fn formatter_rejects_comments_inside_inline_expression_forms() { + let compiler = env!("CARGO_BIN_EXE_glagol"); + let fixture = write_fixture( + "expression-comment", + r#" +(module main) + +(fn main () -> i32 + (+ 20 + ; cannot keep this while preserving inline calls + 22)) +"#, + ); + + let output = run_formatter(compiler, &fixture); + assert_formatter_comment_rejection(&output, "inline expression form"); +} + +#[test] +fn formatter_rejects_same_line_and_trailing_comments() { + let compiler = env!("CARGO_BIN_EXE_glagol"); + let cases = [ + ( + "same-line-module-comment", + r#" +(module main) ; unsupported trailing module comment + +(fn main () -> i32 + 0) +"#, + "same-line module comment", + ), + ( + "same-line-expression-comment", + r#" +(module main) + +(fn main () -> i32 + (+ 20 22) ; unsupported trailing expression comment +) +"#, + "same-line expression comment", + ), + ]; + + for (name, source, label) in cases { + let fixture = write_fixture(name, source); + let output = run_formatter(compiler, &fixture); + assert_formatter_comment_rejection(&output, label); + } +} + +#[test] +fn formatter_rejects_comments_in_headers_and_signatures() { + let compiler = env!("CARGO_BIN_EXE_glagol"); + let cases = [ + ( + "module-header-comment", + r#" +(module + ; unsupported module header comment + main) +"#, + "module header", + ), + ( + "struct-signature-comment", + r#" +(module main) + +(struct + ; unsupported struct signature comment + Pair + (left i32) + (right i32)) +"#, + "struct signature", + ), + ( + "function-signature-comment", + r#" +(module main) + +(fn add ((a i32) + ; unsupported function signature comment + (b i32)) -> i32 + (+ a b)) +"#, + "function signature", + ), + ( + "test-header-comment", + r#" +(module main) + +(test + ; unsupported test header comment + "addition works" + true) +"#, + "test header", + ), + ]; + + for (name, source, label) in cases { + let fixture = write_fixture(name, source); + let output = run_formatter(compiler, &fixture); + assert_formatter_comment_rejection(&output, label); + } +} + +fn run_formatter(compiler: &str, fixture: &Path) -> Output { + Command::new(compiler) + .arg("--format") + .arg(fixture) + .output() + .unwrap_or_else(|err| panic!("run glagol --format on `{}`: {}", fixture.display(), err)) +} + +fn assert_success(output: &Output) { + assert!( + output.status.success(), + "formatter failed\nstdout:\n{}\nstderr:\n{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr), + ); +} + +fn assert_formatter_comment_rejection(output: &Output, label: &str) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + !output.status.success(), + "formatter unexpectedly accepted unsupported comment position in {}\nstdout:\n{}\nstderr:\n{}", + label, + stdout, + stderr, + ); + assert!( + stdout.is_empty(), + "formatter emitted stdout for rejected {}\nstdout:\n{}\nstderr:\n{}", + label, + stdout, + stderr, + ); + assert!( + stderr.contains("error[UnsupportedFormatterComment]"), + "stderr did not contain human UnsupportedFormatterComment for {}\nstderr:\n{}", + label, + stderr, + ); + assert!( + stderr.contains(" (schema slovo.diagnostic)\n") + && stderr.contains(" (version 1)\n") + && stderr.contains(" (code UnsupportedFormatterComment)\n") + && stderr.contains(" (span\n"), + "stderr did not contain structured UnsupportedFormatterComment diagnostic for {}\nstderr:\n{}", + label, + stderr, + ); +} + +fn write_fixture(name: &str, source: &str) -> PathBuf { + let mut path = std::env::temp_dir(); + let id = NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed); + path.push(format!( + "glagol-formatter-{}-{}-{}.slo", + std::process::id(), + id, + name + )); + fs::write(&path, source).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err)); + path +} diff --git a/compiler/tests/i64_numeric_primitive_alpha.rs b/compiler/tests/i64_numeric_primitive_alpha.rs new file mode 100644 index 0000000..8c24eec --- /dev/null +++ b/compiler/tests/i64_numeric_primitive_alpha.rs @@ -0,0 +1,271 @@ +use std::{ + env, + ffi::OsStr, + fs, + path::{Path, PathBuf}, + process::{Command, Output}, + sync::atomic::{AtomicUsize, Ordering}, +}; + +static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0); + +#[test] +fn i64_fixture_lowers_and_runs_tests() { + let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/i64-numeric-primitive.slo"); + + let llvm = run_glagol([fixture.as_os_str()]); + assert_success("compile i64 fixture", &llvm); + let stdout = String::from_utf8_lossy(&llvm.stdout); + assert!( + stdout.contains("declare void @print_i64(i64)") + && stdout.contains("define i64 @base()") + && stdout.contains("add i64") + && stdout.contains("mul i64") + && stdout.contains("icmp sgt i64") + && stdout.contains("icmp slt i64") + && stdout.contains("icmp eq i64") + && stdout.contains("call void @print_i64(i64"), + "i64 LLVM shape drifted\nstdout:\n{}", + stdout + ); + + let division_fixture = write_fixture( + "division-lowering", + "(module main)\n\n(fn quotient ((value i64)) -> i64\n (/ value 3i64))\n\n(fn main () -> i32\n (if (>= (quotient 4294967289i64) 1431655763i64) 0 1))\n", + ); + let division_llvm = run_glagol([division_fixture.as_os_str()]); + assert_success("compile i64 division lowering", &division_llvm); + let division_stdout = String::from_utf8_lossy(&division_llvm.stdout); + assert!( + division_stdout.contains("sdiv i64") && division_stdout.contains("icmp sge i64"), + "i64 division/order LLVM shape drifted\nstdout:\n{}", + division_stdout + ); + + let tests = run_glagol([OsStr::new("test"), fixture.as_os_str()]); + assert_success("run i64 fixture tests", &tests); + assert_eq!( + String::from_utf8_lossy(&tests.stdout), + concat!( + "test \"i64 arithmetic returns exact fixture value\" ... ok\n", + "test \"i64 comparison works in predicates\" ... ok\n", + "test \"i64 division and ordering\" ... ok\n", + "3 test(s) passed\n", + ), + "i64 test runner stdout drifted" + ); +} + +#[test] +fn i64_formatter_and_lowering_are_visible() { + let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/i64-numeric-primitive.slo"); + + let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]); + assert_success("format i64 fixture", &formatted); + let formatted_stdout = String::from_utf8_lossy(&formatted.stdout); + assert!( + formatted_stdout.contains("(fn base () -> i64") + && formatted_stdout.contains("2147483648i64") + && formatted_stdout.contains("(std.io.print_i64 (local_total))"), + "i64 formatter output omitted expected forms\nstdout:\n{}", + formatted_stdout + ); + + let surface = run_glagol([ + OsStr::new("--inspect-lowering=surface"), + fixture.as_os_str(), + ]); + assert_success("inspect i64 surface lowering", &surface); + let surface_stdout = String::from_utf8_lossy(&surface.stdout); + assert_eq!( + surface_stdout, + fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../tests/i64-numeric-primitive.surface.lower") + ) + .expect("read i64 surface snapshot"), + "i64 surface lowering snapshot drifted" + ); + + let checked = run_glagol([ + OsStr::new("--inspect-lowering=checked"), + fixture.as_os_str(), + ]); + assert_success("inspect i64 checked lowering", &checked); + let checked_stdout = String::from_utf8_lossy(&checked.stdout); + assert_eq!( + checked_stdout, + fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../tests/i64-numeric-primitive.checked.lower") + ) + .expect("read i64 checked snapshot"), + "i64 checked lowering snapshot drifted" + ); +} + +#[test] +fn mixed_i32_i64_f64_operands_are_rejected_clearly() { + for (name, expression, found) in [ + ("mixed-i32-i64", "(= 1 1i64)", "i32 and i64"), + ("mixed-i64-f64", "(= 1i64 1.0)", "i64 and f64"), + ] { + let fixture = write_fixture( + name, + &format!( + "(module main)\n\n(fn main () -> i32\n (if {} 0 1))\n", + expression + ), + ); + let output = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + !output.status.success(), + "compiler unexpectedly accepted mixed numeric operands\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.is_empty(), + "rejected compile wrote stdout:\n{}", + stdout + ); + assert!( + stderr.contains("numeric operands must have the same primitive type") + && stderr.contains("mixed i32/i64/u32/u64/f64") + && stderr.contains(found), + "mixed numeric diagnostic drifted for {}\nstderr:\n{}", + name, + stderr + ); + } +} + +#[test] +fn i64_runtime_print_smoke_when_clang_is_available() { + let Some(clang) = find_clang() else { + eprintln!("skipping i64 runtime smoke: set GLAGOL_CLANG or install clang"); + return; + }; + + let fixture = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/i64-numeric-primitive.slo"); + let compile = run_glagol([fixture.as_os_str()]); + assert_success("compile i64 runtime smoke", &compile); + let run = compile_and_run_with_runtime(&clang, "i64-runtime-smoke", &compile.stdout); + assert_success("run i64 runtime smoke", &run); + assert_eq!( + String::from_utf8_lossy(&run.stdout), + "4294967289\n", + "i64 runtime print stdout drifted" + ); +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn write_fixture(name: &str, source: &str) -> PathBuf { + let mut path = env::temp_dir(); + path.push(format!( + "glagol-i64-alpha-{}-{}-{}.slo", + name, + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + fs::write(&path, source).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err)); + path +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); +} + +fn compile_and_run_with_runtime(clang: &Path, name: &str, ir: &[u8]) -> Output { + let manifest = Path::new(env!("CARGO_MANIFEST_DIR")); + let temp_dir = env::temp_dir().join(format!( + "glagol-i64-alpha-{}-{}", + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + fs::create_dir_all(&temp_dir) + .unwrap_or_else(|err| panic!("create `{}`: {}", temp_dir.display(), err)); + + let ir_path = temp_dir.join(format!("{}.ll", name)); + let exe_path = temp_dir.join(name); + fs::write(&ir_path, ir).unwrap_or_else(|err| panic!("write `{}`: {}", ir_path.display(), err)); + + let runtime = manifest.join("../runtime/runtime.c"); + let mut clang_command = Command::new(clang); + clang_command + .arg(&runtime) + .arg(&ir_path) + .arg("-o") + .arg(&exe_path) + .current_dir(manifest); + configure_clang_runtime_env(&mut clang_command, clang); + let clang_output = clang_command + .output() + .unwrap_or_else(|err| panic!("run `{}`: {}", clang.display(), err)); + assert_success("clang i64 runtime smoke", &clang_output); + + Command::new(&exe_path) + .output() + .unwrap_or_else(|err| panic!("run `{}`: {}", exe_path.display(), err)) +} + +fn find_clang() -> Option { + if let Some(path) = env::var_os("GLAGOL_CLANG").filter(|value| !value.is_empty()) { + return Some(PathBuf::from(path)); + } + + let hermetic_clang = PathBuf::from("/tmp/glagol-clang-root/usr/bin/clang"); + if hermetic_clang.is_file() { + return Some(hermetic_clang); + } + + find_on_path("clang") +} + +fn find_on_path(program: &str) -> Option { + let path = env::var_os("PATH")?; + env::split_paths(&path) + .map(|dir| dir.join(program)) + .find(|candidate| candidate.is_file()) +} + +fn configure_clang_runtime_env(command: &mut Command, clang: &Path) { + if !clang.starts_with("/tmp/glagol-clang-root") { + return; + } + + let root = Path::new("/tmp/glagol-clang-root"); + let lib64 = root.join("usr/lib64"); + let lib = root.join("usr/lib"); + let mut paths = vec![lib64, lib]; + + if let Some(existing) = env::var_os("LD_LIBRARY_PATH") { + paths.extend(env::split_paths(&existing)); + } + + let joined = env::join_paths(paths).expect("join LD_LIBRARY_PATH"); + command.env("LD_LIBRARY_PATH", joined); +} diff --git a/compiler/tests/if_expression.rs b/compiler/tests/if_expression.rs new file mode 100644 index 0000000..562747b --- /dev/null +++ b/compiler/tests/if_expression.rs @@ -0,0 +1,130 @@ +use std::{fs, path::Path, process::Command}; + +#[test] +fn if_fixture_emits_llvm_branch_shape() { + let output = run_glagol(["../examples/if.slo"]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "compiler rejected if fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define i32 @choose(i32 %value)") + && stdout.contains("if.then") + && stdout.contains("if.else") + && stdout.contains("if.end") + && stdout.contains("br i1") + && stdout.contains(" phi i32 "), + "LLVM output did not contain expected if shape\nstdout:\n{}", + stdout + ); + assert!( + !stdout.contains("if chooses"), + "compiler emitted test metadata into LLVM output\nstdout:\n{}", + stdout + ); + assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr); +} + +#[test] +fn if_fixture_runs_top_level_tests() { + let output = run_glagol(["--run-tests", "../examples/if.slo"]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "test runner rejected if fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert_eq!( + stdout, + concat!( + "test \"if chooses then\" ... ok\n", + "test \"if chooses else\" ... ok\n", + "test \"if returns bool\" ... ok\n", + "3 test(s) passed\n", + ), + "test runner output drifted" + ); + assert!(stderr.is_empty(), "test runner wrote stderr:\n{}", stderr); +} + +#[test] +fn if_fixture_is_formatter_stable() { + let expected = fs::read_to_string("../tests/if.slo").expect("read fixture"); + let output = run_glagol(["--format", "../tests/if.slo"]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "formatter rejected if fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert_eq!(stdout, expected, "formatter output drifted"); + assert!(stderr.is_empty(), "formatter wrote stderr:\n{}", stderr); +} + +#[test] +fn if_fixture_prints_lowered_shape() { + let surface = run_glagol(["--inspect-lowering=surface", "../examples/if.slo"]); + let surface_stdout = String::from_utf8_lossy(&surface.stdout); + let surface_stderr = String::from_utf8_lossy(&surface.stderr); + assert!( + surface.status.success(), + "surface lowering rejected if fixture\nstdout:\n{}\nstderr:\n{}", + surface_stdout, + surface_stderr + ); + assert!( + surface_stdout.contains("if\n") + && surface_stdout.contains("binary <") + && surface_stdout.contains("int 10") + && surface_stdout.contains("int 20"), + "surface lowering output lost if shape\nstdout:\n{}", + surface_stdout + ); + assert!( + surface_stderr.is_empty(), + "surface lowering wrote stderr:\n{}", + surface_stderr + ); + + let checked = run_glagol(["--inspect-lowering=checked", "../examples/if.slo"]); + let checked_stdout = String::from_utf8_lossy(&checked.stdout); + let checked_stderr = String::from_utf8_lossy(&checked.stderr); + assert!( + checked.status.success(), + "checked lowering rejected if fixture\nstdout:\n{}\nstderr:\n{}", + checked_stdout, + checked_stderr + ); + assert!( + checked_stdout.contains("if : i32") + && checked_stdout.contains("binary < : bool") + && checked_stdout.contains("int 10 : i32") + && checked_stdout.contains("int 20 : i32"), + "checked lowering output lost typed if shape\nstdout:\n{}", + checked_stdout + ); + assert!( + checked_stderr.is_empty(), + "checked lowering wrote stderr:\n{}", + checked_stderr + ); +} + +fn run_glagol(args: [&str; N]) -> std::process::Output { + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} diff --git a/compiler/tests/immutable_bool_locals_alpha.rs b/compiler/tests/immutable_bool_locals_alpha.rs new file mode 100644 index 0000000..50459d8 --- /dev/null +++ b/compiler/tests/immutable_bool_locals_alpha.rs @@ -0,0 +1,174 @@ +use std::{ + env, + ffi::OsStr, + fs, + path::{Path, PathBuf}, + process::{Command, Output}, + sync::atomic::{AtomicUsize, Ordering}, +}; + +static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0); + +const BOOL_LOCAL_FIXTURE: &str = r#"(module main) + +(fn choose ((flag bool)) -> i32 + (let saved bool flag) + (if saved + 42 + 7)) + +(fn choose_from_compare ((value i32)) -> i32 + (let negative bool (< value 0)) + (if negative + 1 + 0)) + +(test "immutable bool local from param" + (= (choose true) 42)) + +(test "immutable bool local false branch" + (= (choose false) 7)) + +(test "immutable bool local from compare" + (= (choose_from_compare -4) 1)) + +(fn main () -> i32 + (+ (choose true) (choose_from_compare -4)))"#; + +#[test] +fn immutable_bool_let_locals_compile_lower_and_test() { + let fixture = write_fixture("immutable-bool-local", BOOL_LOCAL_FIXTURE); + + let compile = run_glagol([fixture.as_os_str()]); + let llvm_stdout = String::from_utf8_lossy(&compile.stdout); + let llvm_stderr = String::from_utf8_lossy(&compile.stderr); + assert!( + compile.status.success(), + "compiler rejected immutable bool local fixture\nstdout:\n{}\nstderr:\n{}", + llvm_stdout, + llvm_stderr + ); + assert!( + llvm_stdout.contains("define i32 @choose(i1 %flag)") + && llvm_stdout.contains("define i32 @choose_from_compare(i32 %value)") + && llvm_stdout.contains("icmp slt i32 %value, 0") + && llvm_stdout.contains("br i1"), + "LLVM output lost immutable bool local shape\nstdout:\n{}", + llvm_stdout + ); + assert!( + llvm_stderr.is_empty(), + "compiler wrote stderr:\n{}", + llvm_stderr + ); + + let tests = run_glagol([OsStr::new("--run-tests"), fixture.as_os_str()]); + assert_success_stdout( + tests, + concat!( + "test \"immutable bool local from param\" ... ok\n", + "test \"immutable bool local false branch\" ... ok\n", + "test \"immutable bool local from compare\" ... ok\n", + "3 test(s) passed\n", + ), + "immutable bool local test runner output", + ); + + let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]); + assert_success_stdout( + formatted, + &format!("{}\n", BOOL_LOCAL_FIXTURE), + "immutable bool local formatter output", + ); + + let surface = run_glagol([ + OsStr::new("--inspect-lowering=surface"), + fixture.as_os_str(), + ]); + let surface_stdout = String::from_utf8_lossy(&surface.stdout); + let surface_stderr = String::from_utf8_lossy(&surface.stderr); + assert!( + surface.status.success(), + "surface lowering rejected immutable bool local fixture\nstdout:\n{}\nstderr:\n{}", + surface_stdout, + surface_stderr + ); + assert!( + surface_stdout.contains("local let saved: bool") + && surface_stdout.contains("local let negative: bool") + && surface_stdout.contains("var saved") + && surface_stdout.contains("var negative"), + "surface lowering lost immutable bool local declarations\nstdout:\n{}", + surface_stdout + ); + assert!( + surface_stderr.is_empty(), + "surface lowering wrote stderr:\n{}", + surface_stderr + ); + + let checked = run_glagol([ + OsStr::new("--inspect-lowering=checked"), + fixture.as_os_str(), + ]); + let checked_stdout = String::from_utf8_lossy(&checked.stdout); + let checked_stderr = String::from_utf8_lossy(&checked.stderr); + assert!( + checked.status.success(), + "checked lowering rejected immutable bool local fixture\nstdout:\n{}\nstderr:\n{}", + checked_stdout, + checked_stderr + ); + assert!( + checked_stdout.contains("local let saved : unit") + && checked_stdout.contains("local let negative : unit") + && checked_stdout.contains("var saved : bool") + && checked_stdout.contains("var negative : bool"), + "checked lowering lost typed immutable bool local flow\nstdout:\n{}", + checked_stdout + ); + assert!( + checked_stderr.is_empty(), + "checked lowering wrote stderr:\n{}", + checked_stderr + ); +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn write_fixture(name: &str, source: &str) -> PathBuf { + let mut path = env::temp_dir(); + path.push(format!( + "glagol-immutable-bool-locals-{}-{}-{}.slo", + name, + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + fs::write(&path, source).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err)); + path +} + +fn assert_success_stdout(output: Output, expected: &str, context: &str) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert_eq!(stdout, expected, "{} stdout drifted", context); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} diff --git a/compiler/tests/integer_bitwise_alpha.rs b/compiler/tests/integer_bitwise_alpha.rs new file mode 100644 index 0000000..ace9383 --- /dev/null +++ b/compiler/tests/integer_bitwise_alpha.rs @@ -0,0 +1,147 @@ +use std::{ + env, + ffi::OsStr, + fs, + path::{Path, PathBuf}, + process::{Command, Output}, + sync::atomic::{AtomicUsize, Ordering}, +}; + +static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0); + +const EXPECTED_TEST_OUTPUT: &str = concat!( + "test \"i32 bit and\" ... ok\n", + "test \"i32 bit or\" ... ok\n", + "test \"i32 bit xor\" ... ok\n", + "test \"i64 bit and\" ... ok\n", + "test \"i64 bit or\" ... ok\n", + "test \"i64 bit xor\" ... ok\n", + "test \"integer bitwise summary\" ... ok\n", + "7 test(s) passed\n", +); + +#[test] +fn integer_bitwise_fixture_formats_lowers_and_runs() { + let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/integer-bitwise.slo"); + + let fmt = run_glagol([ + OsStr::new("fmt"), + OsStr::new("--check"), + fixture.as_os_str(), + ]); + assert_success("format integer bitwise fixture", &fmt); + + let check = run_glagol([OsStr::new("check"), fixture.as_os_str()]); + assert_success_stdout(check, "", "check integer bitwise fixture"); + + let llvm = run_glagol([fixture.as_os_str()]); + assert_success("compile integer bitwise fixture", &llvm); + let stdout = String::from_utf8_lossy(&llvm.stdout); + assert!( + stdout.contains("and i32") + && stdout.contains("or i32") + && stdout.contains("xor i32") + && stdout.contains("and i64") + && stdout.contains("or i64") + && stdout.contains("xor i64"), + "integer bitwise LLVM shape drifted\nstdout:\n{}", + stdout + ); + + let tests = run_glagol([OsStr::new("test"), fixture.as_os_str()]); + assert_success_stdout(tests, EXPECTED_TEST_OUTPUT, "test integer bitwise fixture"); +} + +#[test] +fn integer_bitwise_rejections_are_explicit() { + let f64_fixture = write_fixture( + "f64-bitwise", + "(module main)\n\n(fn main () -> i32\n (if (= (bit_and 5.0 2.0) 0.0) 0 1))\n", + ); + let f64_output = run_glagol([f64_fixture.as_os_str()]); + assert_failure_contains( + &f64_output, + "f64 bitwise rejection", + &[ + "UnsupportedF64Bitwise", + "floating-point bitwise operations are not supported", + "f64 bitwise operations remain deferred", + ], + ); +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn write_fixture(name: &str, source: &str) -> PathBuf { + let mut path = env::temp_dir(); + path.push(format!( + "glagol-{}-{}-{}.slo", + name, + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + fs::write(&path, source).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err)); + path +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} + +fn assert_success_stdout(output: Output, expected: &str, context: &str) { + assert_success(context, &output); + assert_eq!( + String::from_utf8_lossy(&output.stdout), + expected, + "{} stdout drifted", + context + ); +} + +fn assert_failure_contains(output: &Output, context: &str, expected: &[&str]) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + !output.status.success(), + "{} unexpectedly succeeded\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert!( + stdout.is_empty(), + "{} wrote stdout on failure:\n{}", + context, + stdout + ); + for needle in expected { + assert!( + stderr.contains(needle), + "{} stderr missing `{}`\nstderr:\n{}", + context, + needle, + stderr + ); + } +} diff --git a/compiler/tests/integer_remainder_alpha.rs b/compiler/tests/integer_remainder_alpha.rs new file mode 100644 index 0000000..86abb81 --- /dev/null +++ b/compiler/tests/integer_remainder_alpha.rs @@ -0,0 +1,155 @@ +use std::{ + env, + ffi::OsStr, + fs, + path::{Path, PathBuf}, + process::{Command, Output}, + sync::atomic::{AtomicUsize, Ordering}, +}; + +static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0); + +const EXPECTED_TEST_OUTPUT: &str = concat!( + "test \"i32 remainder\" ... ok\n", + "test \"i32 signed remainder\" ... ok\n", + "test \"i64 remainder\" ... ok\n", + "test \"i64 signed remainder\" ... ok\n", + "test \"integer remainder summary\" ... ok\n", + "5 test(s) passed\n", +); + +#[test] +fn integer_remainder_fixture_formats_lowers_and_runs() { + let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/integer-remainder.slo"); + + let fmt = run_glagol([ + OsStr::new("fmt"), + OsStr::new("--check"), + fixture.as_os_str(), + ]); + assert_success("format integer remainder fixture", &fmt); + + let check = run_glagol([OsStr::new("check"), fixture.as_os_str()]); + assert_success_stdout(check, "", "check integer remainder fixture"); + + let llvm = run_glagol([fixture.as_os_str()]); + assert_success("compile integer remainder fixture", &llvm); + let stdout = String::from_utf8_lossy(&llvm.stdout); + assert!( + stdout.contains("srem i32") && stdout.contains("srem i64"), + "integer remainder LLVM shape drifted\nstdout:\n{}", + stdout + ); + + let tests = run_glagol([OsStr::new("test"), fixture.as_os_str()]); + assert_success_stdout( + tests, + EXPECTED_TEST_OUTPUT, + "test integer remainder fixture", + ); +} + +#[test] +fn integer_remainder_rejections_are_explicit() { + let f64_fixture = write_fixture( + "f64-remainder", + "(module main)\n\n(fn main () -> i32\n (if (= (% 5.0 2.0) 1.0) 0 1))\n", + ); + let f64_output = run_glagol([f64_fixture.as_os_str()]); + assert_failure_contains( + &f64_output, + "f64 remainder rejection", + &[ + "UnsupportedF64Remainder", + "floating-point remainder is not supported", + "f64 remainder remains deferred", + ], + ); + + let zero_fixture = write_fixture( + "remainder-by-zero", + "(module main)\n\n(test \"remainder by zero\"\n (= (% 1 0) 0))\n\n(fn main () -> i32\n 0)\n", + ); + let zero_output = run_glagol([OsStr::new("test"), zero_fixture.as_os_str()]); + assert_failure_contains( + &zero_output, + "remainder by zero test runtime", + &["TestRuntimeError", "remainder by zero in test"], + ); +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn write_fixture(name: &str, source: &str) -> PathBuf { + let mut path = env::temp_dir(); + path.push(format!( + "glagol-{}-{}-{}.slo", + name, + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + fs::write(&path, source).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err)); + path +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} + +fn assert_success_stdout(output: Output, expected: &str, context: &str) { + assert_success(context, &output); + assert_eq!( + String::from_utf8_lossy(&output.stdout), + expected, + "{} stdout drifted", + context + ); +} + +fn assert_failure_contains(output: &Output, context: &str, expected: &[&str]) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + !output.status.success(), + "{} unexpectedly succeeded\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert!( + stdout.is_empty(), + "{} wrote stdout on failure:\n{}", + context, + stdout + ); + for needle in expected { + assert!( + stderr.contains(needle), + "{} stderr missing `{}`\nstderr:\n{}", + context, + needle, + stderr + ); + } +} diff --git a/compiler/tests/integer_to_string_alpha.rs b/compiler/tests/integer_to_string_alpha.rs new file mode 100644 index 0000000..128dcb7 --- /dev/null +++ b/compiler/tests/integer_to_string_alpha.rs @@ -0,0 +1,235 @@ +use std::{ + env, + ffi::OsStr, + fs, + path::{Path, PathBuf}, + process::{Command, Output}, + sync::atomic::{AtomicUsize, Ordering}, +}; + +static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0); + +#[test] +fn integer_to_string_fixture_lowers_and_runs_tests() { + let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/integer-to-string.slo"); + + let llvm = run_glagol([fixture.as_os_str()]); + assert_success("compile integer-to-string fixture", &llvm); + let stdout = String::from_utf8_lossy(&llvm.stdout); + assert!( + stdout.contains("declare ptr @__glagol_num_i32_to_string(i32)") + && stdout.contains("declare ptr @__glagol_num_i64_to_string(i64)") + && stdout.contains("call ptr @__glagol_num_i32_to_string(i32 0)") + && stdout.contains("call ptr @__glagol_num_i32_to_string(i32 -7)") + && stdout.contains("call ptr @__glagol_num_i64_to_string(i64 -9223372036854775808)") + && stdout.contains("call ptr @__glagol_num_i64_to_string(i64 9223372036854775807)") + && stdout.contains("call void @print_string(ptr %") + && !stdout.contains("@std.num."), + "integer-to-string LLVM shape drifted\nstdout:\n{}", + stdout + ); + + let tests = run_glagol([OsStr::new("test"), fixture.as_os_str()]); + assert_success("run integer-to-string fixture tests", &tests); + assert_eq!( + String::from_utf8_lossy(&tests.stdout), + concat!( + "test \"i32 zero to string\" ... ok\n", + "test \"i32 negative to string\" ... ok\n", + "test \"i32 high to string\" ... ok\n", + "test \"i32 negative string length\" ... ok\n", + "test \"i64 low to string\" ... ok\n", + "test \"i64 high to string\" ... ok\n", + "test \"i64 beyond i32 to string\" ... ok\n", + "test \"i64 low string length\" ... ok\n", + "8 test(s) passed\n", + ), + "integer-to-string test runner stdout drifted" + ); +} + +#[test] +fn integer_to_string_hosted_runtime_formats_bounds_when_clang_is_available() { + let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/integer-to-string.slo"); + let binary = unique_path("integer-to-string-bin"); + + let build = run_glagol([ + OsStr::new("build"), + fixture.as_os_str(), + OsStr::new("-o"), + binary.as_os_str(), + ]); + if !build.status.success() { + let stdout = String::from_utf8_lossy(&build.stdout); + let stderr = String::from_utf8_lossy(&build.stderr); + assert!( + stdout.is_empty(), + "failed integer-to-string build wrote stdout:\n{}", + stdout + ); + assert!( + stderr.contains("ToolchainUnavailable"), + "integer-to-string build failed unexpectedly\nstderr:\n{}", + stderr + ); + return; + } + + let run = Command::new(&binary) + .output() + .expect("run integer-to-string binary"); + assert_success("run integer-to-string binary", &run); + assert_eq!( + String::from_utf8_lossy(&run.stdout), + "0\n-7\n2147483647\n-9223372036854775808\n9223372036854775807\n2147483648\n", + "integer-to-string binary stdout drifted" + ); + assert!( + run.stderr.is_empty(), + "integer-to-string binary wrote stderr:\n{}", + String::from_utf8_lossy(&run.stderr) + ); +} + +#[test] +fn integer_to_string_formatter_and_lowering_are_visible() { + let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/integer-to-string.slo"); + + let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]); + assert_success("format integer-to-string fixture", &formatted); + let formatted_stdout = String::from_utf8_lossy(&formatted.stdout); + assert!( + formatted_stdout.contains("(std.num.i32_to_string -7)") + && formatted_stdout.contains("(std.num.i64_to_string -9223372036854775808i64)") + && formatted_stdout.contains("(std.string.len (i64_low_text))"), + "integer-to-string formatter output omitted expected calls\nstdout:\n{}", + formatted_stdout + ); + + let surface = run_glagol([ + OsStr::new("--inspect-lowering=surface"), + fixture.as_os_str(), + ]); + assert_success("inspect integer-to-string surface lowering", &surface); + assert_eq!( + String::from_utf8_lossy(&surface.stdout), + fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/integer-to-string.surface.lower") + ) + .expect("read integer-to-string surface snapshot"), + "integer-to-string surface lowering snapshot drifted" + ); + + let checked = run_glagol([ + OsStr::new("--inspect-lowering=checked"), + fixture.as_os_str(), + ]); + assert_success("inspect integer-to-string checked lowering", &checked); + assert_eq!( + String::from_utf8_lossy(&checked.stdout), + fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/integer-to-string.checked.lower") + ) + .expect("read integer-to-string checked snapshot"), + "integer-to-string checked lowering snapshot drifted" + ); +} + +#[test] +fn integer_to_string_rejections_are_explicit() { + for (name, source, expected) in [ + ( + "i32-arity", + "(module main)\n\n(fn main () -> string\n (std.num.i32_to_string))\n", + "wrong number of arguments", + ), + ( + "i64-type", + "(module main)\n\n(fn main () -> string\n (std.num.i64_to_string 1))\n", + "cannot call `std.num.i64_to_string` with argument of wrong type", + ), + ( + "generic-to-string", + "(module main)\n\n(fn main () -> i32\n (std.num.to_string 1)\n 0)\n", + "standard library call `std.num.to_string` is not supported", + ), + ( + "string-from-i64", + "(module main)\n\n(fn main () -> i32\n (std.string.from_i64 1i64)\n 0)\n", + "standard library call `std.string.from_i64` is not supported", + ), + ] { + let fixture = write_fixture(name, source); + let output = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + !output.status.success(), + "compiler unexpectedly accepted integer-to-string rejection fixture `{}`\nstdout:\n{}\nstderr:\n{}", + name, + stdout, + stderr + ); + assert!( + stdout.is_empty(), + "rejected compile wrote stdout for `{}`:\n{}", + name, + stdout + ); + assert!( + stderr.contains(expected), + "integer-to-string diagnostic drifted for `{}`\nstderr:\n{}", + name, + stderr + ); + } +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn write_fixture(name: &str, source: &str) -> PathBuf { + let mut path = env::temp_dir(); + path.push(format!( + "glagol-integer-to-string-alpha-{}-{}-{}.slo", + name, + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + fs::write(&path, source).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err)); + path +} + +fn unique_path(name: &str) -> PathBuf { + let mut path = env::temp_dir(); + path.push(format!( + "glagol-{}-{}-{}", + name, + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + path +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} diff --git a/compiler/tests/llvm_smoke.rs b/compiler/tests/llvm_smoke.rs new file mode 100644 index 0000000..a985156 --- /dev/null +++ b/compiler/tests/llvm_smoke.rs @@ -0,0 +1,155 @@ +use std::{ + env, fs, + path::{Path, PathBuf}, + process::Command, +}; + +const SUPPORTED_COMPILE_FIXTURES: &[&str] = &[ + "../examples/add.slo", + "../examples/array-direct-scalars-value-flow.slo", + "../examples/array-direct-scalars.slo", + "../examples/array-enum.slo", + "../examples/array-struct-elements.slo", + "../examples/array-struct-fields.slo", + "../examples/array-string-value-flow.slo", + "../examples/array-string.slo", + "../examples/array-value-flow.slo", + "../examples/array.slo", + "../examples/enum-payload-structs.slo", + "../examples/if.slo", + "../examples/local-variables.slo", + "../examples/option-result-flow.slo", + "../examples/option-result-match.slo", + "../examples/option-result-payload.slo", + "../examples/option-result.slo", + "../examples/owned-string-concat.slo", + "../examples/f64-to-i32-result.slo", + "../examples/f64-to-i64-result.slo", + "../examples/f64-to-string.slo", + "../examples/print-bool.slo", + "../examples/standard-runtime.slo", + "../examples/string-parse-bool-result.slo", + "../examples/string-parse-i64-result.slo", + "../examples/string-parse-f64-result.slo", + "../examples/string-print.slo", + "../examples/string-value-flow.slo", + "../examples/struct-value-flow.slo", + "../examples/struct.slo", + "../examples/unsafe.slo", + "../examples/while.slo", +]; + +#[test] +#[ignore = "requires clang or GLAGOL_CLANG; optional promotion smoke for emitted LLVM IR"] +fn emitted_supported_fixture_ir_compiles_with_clang_when_available() { + let Some(clang) = find_clang() else { + eprintln!("skipping LLVM smoke test: set GLAGOL_CLANG or install clang"); + return; + }; + + let manifest = Path::new(env!("CARGO_MANIFEST_DIR")); + let runtime = manifest.join("../runtime/runtime.c"); + let temp_dir = env::temp_dir().join(format!("glagol-llvm-smoke-{}", std::process::id())); + fs::create_dir_all(&temp_dir) + .unwrap_or_else(|err| panic!("create `{}`: {}", temp_dir.display(), err)); + + for fixture in SUPPORTED_COMPILE_FIXTURES { + let ir = emit_fixture_ir(manifest, fixture); + let stem = fixture_stem(fixture); + let ir_path = temp_dir.join(format!("{}.ll", stem)); + let exe_path = temp_dir.join(stem); + fs::write(&ir_path, ir) + .unwrap_or_else(|err| panic!("write `{}`: {}", ir_path.display(), err)); + + let mut command = Command::new(&clang); + command + .arg(&runtime) + .arg(&ir_path) + .arg("-o") + .arg(&exe_path) + .current_dir(manifest); + configure_clang_runtime_env(&mut command, &clang); + let output = command + .output() + .unwrap_or_else(|err| panic!("run `{}`: {}", clang.display(), err)); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "clang rejected emitted LLVM for `{}`\nstdout:\n{}\nstderr:\n{}", + fixture, + stdout, + stderr + ); + } +} + +fn emit_fixture_ir(manifest: &Path, fixture: &str) -> String { + let output = Command::new(env!("CARGO_BIN_EXE_glagol")) + .arg(fixture) + .current_dir(manifest) + .output() + .unwrap_or_else(|err| panic!("run glagol on `{}`: {}", fixture, err)); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "glagol rejected `{}`\nstdout:\n{}\nstderr:\n{}", + fixture, + stdout, + stderr + ); + assert!( + stderr.is_empty(), + "glagol wrote stderr for `{}`: {}", + fixture, + stderr + ); + stdout.into_owned() +} + +fn find_clang() -> Option { + if let Some(path) = env::var_os("GLAGOL_CLANG").filter(|value| !value.is_empty()) { + return Some(PathBuf::from(path)); + } + + let hermetic_clang = PathBuf::from("/tmp/glagol-clang-root/usr/bin/clang"); + if hermetic_clang.is_file() { + return Some(hermetic_clang); + } + + find_on_path("clang") +} + +fn find_on_path(program: &str) -> Option { + let path = env::var_os("PATH")?; + env::split_paths(&path) + .map(|dir| dir.join(program)) + .find(|candidate| candidate.is_file()) +} + +fn configure_clang_runtime_env(command: &mut Command, clang: &Path) { + if !clang.starts_with("/tmp/glagol-clang-root") { + return; + } + + let root = Path::new("/tmp/glagol-clang-root"); + let lib64 = root.join("usr/lib64"); + let lib = root.join("usr/lib"); + let mut paths = vec![lib64, lib]; + + if let Some(existing) = env::var_os("LD_LIBRARY_PATH") { + paths.extend(env::split_paths(&existing)); + } + + let joined = env::join_paths(paths).expect("join LD_LIBRARY_PATH"); + command.env("LD_LIBRARY_PATH", joined); +} + +fn fixture_stem(fixture: &str) -> String { + Path::new(fixture) + .file_stem() + .and_then(|stem| stem.to_str()) + .expect("fixture has utf-8 file stem") + .replace('-', "_") +} diff --git a/compiler/tests/local_variables.rs b/compiler/tests/local_variables.rs new file mode 100644 index 0000000..6b2cfec --- /dev/null +++ b/compiler/tests/local_variables.rs @@ -0,0 +1,96 @@ +use std::{fs, path::Path, process::Command}; + +#[test] +fn local_variables_emit_llvm_storage_and_loads() { + let output = run_glagol(["../tests/local-variables.slo"]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "compiler rejected local variable fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define i32 @add_local(i32 %a)") + && stdout.contains("%total.addr = alloca i32") + && stdout.contains("add i32 %a, 1") + && stdout.contains("store i32") + && stdout.contains("load i32") + && stdout.contains("define i1 @keep_flag(i1 %flag)") + && !stdout.contains("%local_flag.addr = alloca i1") + && stdout.contains("define i1 @flip_flag(i1 %flag)") + && stdout.contains("%current.addr = alloca i1") + && stdout.contains("%count.addr = alloca i64") + && stdout.contains("%amount.addr = alloca double"), + "LLVM output did not contain expected local variable value/storage shape\nstdout:\n{}", + stdout + ); + assert!( + !stdout.contains("locals work") + && !stdout.contains("bool let locals work") + && !stdout.contains("bool let locals preserve false") + && !stdout.contains("bool var set flips true") + && !stdout.contains("bool var set flips false") + && !stdout.contains("i64 var set works") + && !stdout.contains("f64 var set works"), + "compiler emitted test metadata into LLVM output\nstdout:\n{}", + stdout + ); + assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr); +} + +#[test] +fn local_variables_work_in_top_level_tests() { + let output = run_glagol(["--run-tests", "../tests/local-variables.slo"]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "test runner rejected local variable fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert_eq!( + stdout, + concat!( + "test \"locals work\" ... ok\n", + "test \"bool let locals work\" ... ok\n", + "test \"bool let locals preserve false\" ... ok\n", + "test \"bool var set flips true\" ... ok\n", + "test \"bool var set flips false\" ... ok\n", + "test \"i64 var set works\" ... ok\n", + "test \"f64 var set works\" ... ok\n", + "7 test(s) passed\n", + ), + "test runner output drifted" + ); + assert!(stderr.is_empty(), "test runner wrote stderr:\n{}", stderr); +} + +#[test] +fn local_variable_fixture_is_formatter_stable() { + let expected = fs::read_to_string("../tests/local-variables.slo").expect("read fixture"); + let output = run_glagol(["--format", "../tests/local-variables.slo"]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "formatter rejected local variable fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert_eq!(stdout, expected, "formatter output drifted"); + assert!(stderr.is_empty(), "formatter wrote stderr:\n{}", stderr); +} + +fn run_glagol(args: [&str; N]) -> std::process::Output { + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} diff --git a/compiler/tests/lowering_inspector.rs b/compiler/tests/lowering_inspector.rs new file mode 100644 index 0000000..663cd36 --- /dev/null +++ b/compiler/tests/lowering_inspector.rs @@ -0,0 +1,393 @@ +use std::{fs, path::Path, process::Command}; + +const LOWERING_FIXTURES: &[LoweringFixture] = &[ + LoweringFixture { + name: "add", + source: "../examples/add.slo", + surface_snapshot: "../tests/add.surface.lower", + checked_snapshot: "../tests/add.checked.lower", + }, + LoweringFixture { + name: "top-level-test", + source: "../tests/top-level-test.slo", + surface_snapshot: "../tests/top-level-test.surface.lower", + checked_snapshot: "../tests/top-level-test.checked.lower", + }, + LoweringFixture { + name: "comments", + source: "../tests/comments.slo", + surface_snapshot: "../tests/comments.surface.lower", + checked_snapshot: "../tests/comments.checked.lower", + }, + LoweringFixture { + name: "formatter-stability-v1", + source: "../tests/formatter-stability-v1.fmt", + surface_snapshot: "../tests/formatter-stability-v1.surface.lower", + checked_snapshot: "../tests/formatter-stability-v1.checked.lower", + }, + LoweringFixture { + name: "unsafe", + source: "../tests/unsafe.slo", + surface_snapshot: "../tests/unsafe.surface.lower", + checked_snapshot: "../tests/unsafe.checked.lower", + }, + LoweringFixture { + name: "local-variables", + source: "../examples/local-variables.slo", + surface_snapshot: "../tests/local-variables.surface.lower", + checked_snapshot: "../tests/local-variables.checked.lower", + }, + LoweringFixture { + name: "composite-locals", + source: "../examples/composite-locals.slo", + surface_snapshot: "../tests/composite-locals.surface.lower", + checked_snapshot: "../tests/composite-locals.checked.lower", + }, + LoweringFixture { + name: "composite-struct-fields", + source: "../examples/composite-struct-fields.slo", + surface_snapshot: "../tests/composite-struct-fields.surface.lower", + checked_snapshot: "../tests/composite-struct-fields.checked.lower", + }, + LoweringFixture { + name: "array-struct-fields", + source: "../examples/array-struct-fields.slo", + surface_snapshot: "../tests/array-struct-fields.surface.lower", + checked_snapshot: "../tests/array-struct-fields.checked.lower", + }, + LoweringFixture { + name: "array-struct-elements", + source: "../examples/array-struct-elements.slo", + surface_snapshot: "../tests/array-struct-elements.surface.lower", + checked_snapshot: "../tests/array-struct-elements.checked.lower", + }, + LoweringFixture { + name: "if", + source: "../examples/if.slo", + surface_snapshot: "../tests/if.surface.lower", + checked_snapshot: "../tests/if.checked.lower", + }, + LoweringFixture { + name: "while", + source: "../examples/while.slo", + surface_snapshot: "../tests/while.surface.lower", + checked_snapshot: "../tests/while.checked.lower", + }, + LoweringFixture { + name: "struct", + source: "../examples/struct.slo", + surface_snapshot: "../tests/struct.surface.lower", + checked_snapshot: "../tests/struct.checked.lower", + }, + LoweringFixture { + name: "struct-value-flow", + source: "../examples/struct-value-flow.slo", + surface_snapshot: "../tests/struct-value-flow.surface.lower", + checked_snapshot: "../tests/struct-value-flow.checked.lower", + }, + LoweringFixture { + name: "enum-struct-fields", + source: "../examples/enum-struct-fields.slo", + surface_snapshot: "../tests/enum-struct-fields.surface.lower", + checked_snapshot: "../tests/enum-struct-fields.checked.lower", + }, + LoweringFixture { + name: "enum-payload-direct-scalars", + source: "../examples/enum-payload-direct-scalars.slo", + surface_snapshot: "../tests/enum-payload-direct-scalars.surface.lower", + checked_snapshot: "../tests/enum-payload-direct-scalars.checked.lower", + }, + LoweringFixture { + name: "enum-payload-structs", + source: "../examples/enum-payload-structs.slo", + surface_snapshot: "../tests/enum-payload-structs.surface.lower", + checked_snapshot: "../tests/enum-payload-structs.checked.lower", + }, + LoweringFixture { + name: "primitive-struct-fields", + source: "../examples/primitive-struct-fields.slo", + surface_snapshot: "../tests/primitive-struct-fields.surface.lower", + checked_snapshot: "../tests/primitive-struct-fields.checked.lower", + }, + LoweringFixture { + name: "numeric-struct-fields", + source: "../examples/numeric-struct-fields.slo", + surface_snapshot: "../tests/numeric-struct-fields.surface.lower", + checked_snapshot: "../tests/numeric-struct-fields.checked.lower", + }, + LoweringFixture { + name: "array", + source: "../examples/array.slo", + surface_snapshot: "../tests/array.surface.lower", + checked_snapshot: "../tests/array.checked.lower", + }, + LoweringFixture { + name: "array-direct-scalars", + source: "../examples/array-direct-scalars.slo", + surface_snapshot: "../tests/array-direct-scalars.surface.lower", + checked_snapshot: "../tests/array-direct-scalars.checked.lower", + }, + LoweringFixture { + name: "array-direct-scalars-value-flow", + source: "../examples/array-direct-scalars-value-flow.slo", + surface_snapshot: "../tests/array-direct-scalars-value-flow.surface.lower", + checked_snapshot: "../tests/array-direct-scalars-value-flow.checked.lower", + }, + LoweringFixture { + name: "array-enum", + source: "../examples/array-enum.slo", + surface_snapshot: "../tests/array-enum.surface.lower", + checked_snapshot: "../tests/array-enum.checked.lower", + }, + LoweringFixture { + name: "array-string", + source: "../examples/array-string.slo", + surface_snapshot: "../tests/array-string.surface.lower", + checked_snapshot: "../tests/array-string.checked.lower", + }, + LoweringFixture { + name: "array-string-value-flow", + source: "../examples/array-string-value-flow.slo", + surface_snapshot: "../tests/array-string-value-flow.surface.lower", + checked_snapshot: "../tests/array-string-value-flow.checked.lower", + }, + LoweringFixture { + name: "array-value-flow", + source: "../examples/array-value-flow.slo", + surface_snapshot: "../tests/array-value-flow.surface.lower", + checked_snapshot: "../tests/array-value-flow.checked.lower", + }, + LoweringFixture { + name: "option-result", + source: "../examples/option-result.slo", + surface_snapshot: "../tests/option-result.surface.lower", + checked_snapshot: "../tests/option-result.checked.lower", + }, + LoweringFixture { + name: "option-result-flow", + source: "../examples/option-result-flow.slo", + surface_snapshot: "../tests/option-result-flow.surface.lower", + checked_snapshot: "../tests/option-result-flow.checked.lower", + }, + LoweringFixture { + name: "option-result-payload", + source: "../examples/option-result-payload.slo", + surface_snapshot: "../tests/option-result-payload.surface.lower", + checked_snapshot: "../tests/option-result-payload.checked.lower", + }, + LoweringFixture { + name: "option-result-match", + source: "../examples/option-result-match.slo", + surface_snapshot: "../tests/option-result-match.surface.lower", + checked_snapshot: "../tests/option-result-match.checked.lower", + }, + LoweringFixture { + name: "string-print", + source: "../examples/string-print.slo", + surface_snapshot: "../tests/string-print.surface.lower", + checked_snapshot: "../tests/string-print.checked.lower", + }, + LoweringFixture { + name: "string-value-flow", + source: "../examples/string-value-flow.slo", + surface_snapshot: "../tests/string-value-flow.surface.lower", + checked_snapshot: "../tests/string-value-flow.checked.lower", + }, + LoweringFixture { + name: "print-bool", + source: "../examples/print-bool.slo", + surface_snapshot: "../tests/print-bool.surface.lower", + checked_snapshot: "../tests/print-bool.checked.lower", + }, + LoweringFixture { + name: "random", + source: "../examples/random.slo", + surface_snapshot: "../tests/random.surface.lower", + checked_snapshot: "../tests/random.checked.lower", + }, + LoweringFixture { + name: "stdin-result", + source: "../examples/stdin-result.slo", + surface_snapshot: "../tests/stdin-result.surface.lower", + checked_snapshot: "../tests/stdin-result.checked.lower", + }, + LoweringFixture { + name: "string-parse-i32-result", + source: "../examples/string-parse-i32-result.slo", + surface_snapshot: "../tests/string-parse-i32-result.surface.lower", + checked_snapshot: "../tests/string-parse-i32-result.checked.lower", + }, + LoweringFixture { + name: "string-parse-i64-result", + source: "../examples/string-parse-i64-result.slo", + surface_snapshot: "../tests/string-parse-i64-result.surface.lower", + checked_snapshot: "../tests/string-parse-i64-result.checked.lower", + }, + LoweringFixture { + name: "string-parse-f64-result", + source: "../examples/string-parse-f64-result.slo", + surface_snapshot: "../tests/string-parse-f64-result.surface.lower", + checked_snapshot: "../tests/string-parse-f64-result.checked.lower", + }, + LoweringFixture { + name: "string-parse-bool-result", + source: "../examples/string-parse-bool-result.slo", + surface_snapshot: "../tests/string-parse-bool-result.surface.lower", + checked_snapshot: "../tests/string-parse-bool-result.checked.lower", + }, + LoweringFixture { + name: "result-helpers", + source: "../examples/result-helpers.slo", + surface_snapshot: "../tests/result-helpers.surface.lower", + checked_snapshot: "../tests/result-helpers.checked.lower", + }, + LoweringFixture { + name: "standard-runtime", + source: "../examples/standard-runtime.slo", + surface_snapshot: "../tests/standard-runtime.surface.lower", + checked_snapshot: "../tests/standard-runtime.checked.lower", + }, + LoweringFixture { + name: "owned-string-concat", + source: "../examples/owned-string-concat.slo", + surface_snapshot: "../tests/owned-string-concat.surface.lower", + checked_snapshot: "../tests/owned-string-concat.checked.lower", + }, + LoweringFixture { + name: "standard-io-host-env", + source: "../tests/standard-io-host-env.slo", + surface_snapshot: "../tests/standard-io-host-env.surface.lower", + checked_snapshot: "../tests/standard-io-host-env.checked.lower", + }, + LoweringFixture { + name: "host-io-result", + source: "../tests/host-io-result.slo", + surface_snapshot: "../tests/host-io-result.surface.lower", + checked_snapshot: "../tests/host-io-result.checked.lower", + }, + LoweringFixture { + name: "integer-to-string", + source: "../examples/integer-to-string.slo", + surface_snapshot: "../tests/integer-to-string.surface.lower", + checked_snapshot: "../tests/integer-to-string.checked.lower", + }, + LoweringFixture { + name: "f64-to-string", + source: "../examples/f64-to-string.slo", + surface_snapshot: "../tests/f64-to-string.surface.lower", + checked_snapshot: "../tests/f64-to-string.checked.lower", + }, + LoweringFixture { + name: "f64-to-i32-result", + source: "../examples/f64-to-i32-result.slo", + surface_snapshot: "../tests/f64-to-i32-result.surface.lower", + checked_snapshot: "../tests/f64-to-i32-result.checked.lower", + }, + LoweringFixture { + name: "f64-to-i64-result", + source: "../examples/f64-to-i64-result.slo", + surface_snapshot: "../tests/f64-to-i64-result.surface.lower", + checked_snapshot: "../tests/f64-to-i64-result.checked.lower", + }, + LoweringFixture { + name: "checked-i64-to-i32-conversion", + source: "../examples/checked-i64-to-i32-conversion.slo", + surface_snapshot: "../tests/checked-i64-to-i32-conversion.surface.lower", + checked_snapshot: "../tests/checked-i64-to-i32-conversion.checked.lower", + }, + LoweringFixture { + name: "vec-i32", + source: "../examples/vec-i32.slo", + surface_snapshot: "../tests/vec-i32.surface.lower", + checked_snapshot: "../tests/vec-i32.checked.lower", + }, +]; + +#[test] +fn promoted_fixtures_print_expected_surface_ast() { + for fixture in LOWERING_FIXTURES { + assert_lowering_fixture(fixture, LoweringMode::Surface); + } +} + +#[test] +fn promoted_fixtures_print_expected_checked_ast() { + for fixture in LOWERING_FIXTURES { + assert_lowering_fixture(fixture, LoweringMode::Checked); + } +} + +struct LoweringFixture { + name: &'static str, + source: &'static str, + surface_snapshot: &'static str, + checked_snapshot: &'static str, +} + +#[derive(Clone, Copy)] +enum LoweringMode { + Surface, + Checked, +} + +impl LoweringMode { + fn flag(self) -> &'static str { + match self { + LoweringMode::Surface => "--inspect-lowering=surface", + LoweringMode::Checked => "--inspect-lowering=checked", + } + } + + fn snapshot(self, fixture: &LoweringFixture) -> &'static str { + match self { + LoweringMode::Surface => fixture.surface_snapshot, + LoweringMode::Checked => fixture.checked_snapshot, + } + } +} + +fn assert_lowering_fixture(fixture: &LoweringFixture, mode: LoweringMode) { + let expected = + fs::read_to_string(Path::new(env!("CARGO_MANIFEST_DIR")).join(mode.snapshot(fixture))) + .expect("read lowering inspector fixture"); + let output = run_lowering_inspector(mode.flag(), fixture.source); + + assert_success_output(output, expected, fixture.name, mode.flag()); +} + +fn run_lowering_inspector(mode: &str, fixture: &str) -> std::process::Output { + let compiler = env!("CARGO_BIN_EXE_glagol"); + let fixture = Path::new(fixture); + + Command::new(compiler) + .arg(mode) + .arg(fixture) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .unwrap_or_else(|err| panic!("run glagol {} on fixture: {}", mode, err)) +} + +fn assert_success_output(output: std::process::Output, expected: String, name: &str, mode: &str) { + assert!( + output.status.success(), + "lowering inspector failed for `{}` `{}`\nstdout:\n{}\nstderr:\n{}", + name, + mode, + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr), + ); + + let actual = String::from_utf8(output.stdout).expect("lowering inspector output is UTF-8"); + assert_eq!( + actual, expected, + "lowering inspector fixture drifted for `{}` `{}`", + name, mode + ); + assert!( + output.stderr.is_empty(), + "lowering inspector wrote stderr for `{}` `{}`:\n{}", + name, + mode, + String::from_utf8_lossy(&output.stderr), + ); +} diff --git a/compiler/tests/mutable_composite_locals_alpha.rs b/compiler/tests/mutable_composite_locals_alpha.rs new file mode 100644 index 0000000..f7879b7 --- /dev/null +++ b/compiler/tests/mutable_composite_locals_alpha.rs @@ -0,0 +1,156 @@ +use std::{fs, path::Path, process::Command}; + +#[test] +fn mutable_composite_locals_compile_lower_and_test() { + let fixture = "../examples/composite-locals.slo"; + + let compile = run_glagol([fixture]); + let llvm_stdout = String::from_utf8_lossy(&compile.stdout); + let llvm_stderr = String::from_utf8_lossy(&compile.stderr); + assert!( + compile.status.success(), + "compiler rejected mutable composite locals fixture\nstdout:\n{}\nstderr:\n{}", + llvm_stdout, + llvm_stderr + ); + assert!( + llvm_stdout.contains("define ptr @swap_string(ptr %first, ptr %second)") + && llvm_stdout.contains("define i64 @vec_i64_local_value()") + && llvm_stdout.contains("define ptr @option_string_local_value()") + && llvm_stdout.contains("define ptr @result_string_local_value()") + && llvm_stdout.contains("define ptr @primitive_record_local_label()") + && llvm_stdout.contains("define i32 @tagged_local_code()") + && llvm_stdout.contains("alloca ptr") + && llvm_stdout.contains("alloca { i1, ptr }") + && llvm_stdout.contains("alloca { i1, ptr, i32 }") + && llvm_stdout.contains("alloca { i32, i1, ptr }") + && llvm_stdout.contains("alloca { i64, double }") + && llvm_stdout.contains("alloca { i32, { i32, i32 } }"), + "LLVM output lost mutable composite local storage shape\nstdout:\n{}", + llvm_stdout + ); + assert!( + llvm_stderr.is_empty(), + "compiler wrote stderr:\n{}", + llvm_stderr + ); + + let tests = run_glagol(["--run-tests", fixture]); + let tests_stdout = String::from_utf8_lossy(&tests.stdout); + let tests_stderr = String::from_utf8_lossy(&tests.stderr); + assert!( + tests.status.success(), + "test runner rejected mutable composite locals fixture\nstdout:\n{}\nstderr:\n{}", + tests_stdout, + tests_stderr + ); + assert_eq!( + tests_stdout, + concat!( + "test \"mutable string local set works\" ... ok\n", + "test \"mutable vec i32 local set works\" ... ok\n", + "test \"mutable vec i64 local set works\" ... ok\n", + "test \"mutable vec f64 local set works\" ... ok\n", + "test \"mutable vec bool local set works\" ... ok\n", + "test \"mutable vec string local set works\" ... ok\n", + "test \"mutable option i32 local set works\" ... ok\n", + "test \"mutable option i64 local set works\" ... ok\n", + "test \"mutable option f64 local set works\" ... ok\n", + "test \"mutable option bool local set works\" ... ok\n", + "test \"mutable option string local set works\" ... ok\n", + "test \"mutable result i32 local set works\" ... ok\n", + "test \"mutable result i64 local set works\" ... ok\n", + "test \"mutable result f64 local set works\" ... ok\n", + "test \"mutable result bool local set works\" ... ok\n", + "test \"mutable result string local set works\" ... ok\n", + "test \"mutable plain struct local set works\" ... ok\n", + "test \"mutable primitive struct local set works\" ... ok\n", + "test \"mutable numeric struct local set works\" ... ok\n", + "test \"mutable payloadless enum local set works\" ... ok\n", + "test \"mutable payload enum local set works\" ... ok\n", + "test \"mutable enum struct local set works\" ... ok\n", + "22 test(s) passed\n", + ), + "test runner output drifted" + ); + assert!( + tests_stderr.is_empty(), + "test runner wrote stderr:\n{}", + tests_stderr + ); + + let expected = fs::read_to_string("../examples/composite-locals.slo").expect("read fixture"); + let format = run_glagol(["--format", fixture]); + let format_stdout = String::from_utf8_lossy(&format.stdout); + let format_stderr = String::from_utf8_lossy(&format.stderr); + assert!( + format.status.success(), + "formatter rejected mutable composite locals fixture\nstdout:\n{}\nstderr:\n{}", + format_stdout, + format_stderr + ); + assert_eq!(format_stdout, expected, "formatter output drifted"); + assert!( + format_stderr.is_empty(), + "formatter wrote stderr:\n{}", + format_stderr + ); + + let surface = run_glagol(["--inspect-lowering=surface", fixture]); + let surface_stdout = String::from_utf8_lossy(&surface.stdout); + let surface_stderr = String::from_utf8_lossy(&surface.stderr); + assert!( + surface.status.success(), + "surface lowering rejected mutable composite locals fixture\nstdout:\n{}\nstderr:\n{}", + surface_stdout, + surface_stderr + ); + assert!( + surface_stdout.contains("local var current: string") + && surface_stdout.contains("local var current: (vec i64)") + && surface_stdout.contains("local var current: (option string)") + && surface_stdout.contains("local var current: (result string i32)") + && surface_stdout.contains("local var current: PrimitiveRecord") + && surface_stdout.contains("local var current: Signal"), + "surface lowering lost mutable composite local declarations\nstdout:\n{}", + surface_stdout + ); + assert!( + surface_stderr.is_empty(), + "surface lowering wrote stderr:\n{}", + surface_stderr + ); + + let checked = run_glagol(["--inspect-lowering=checked", fixture]); + let checked_stdout = String::from_utf8_lossy(&checked.stdout); + let checked_stderr = String::from_utf8_lossy(&checked.stderr); + assert!( + checked.status.success(), + "checked lowering rejected mutable composite locals fixture\nstdout:\n{}\nstderr:\n{}", + checked_stdout, + checked_stderr + ); + assert!( + checked_stdout.contains("var current : string") + && checked_stdout.contains("var current : (vec i64)") + && checked_stdout.contains("var current : (option string)") + && checked_stdout.contains("var current : (result string i32)") + && checked_stdout.contains("var current : PrimitiveRecord") + && checked_stdout.contains("var current : Signal"), + "checked lowering lost typed mutable composite local flow\nstdout:\n{}", + checked_stdout + ); + assert!( + checked_stderr.is_empty(), + "checked lowering wrote stderr:\n{}", + checked_stderr + ); +} + +fn run_glagol(args: [&str; N]) -> std::process::Output { + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} diff --git a/compiler/tests/mutable_scalar_locals_alpha.rs b/compiler/tests/mutable_scalar_locals_alpha.rs new file mode 100644 index 0000000..9353e5b --- /dev/null +++ b/compiler/tests/mutable_scalar_locals_alpha.rs @@ -0,0 +1,197 @@ +use std::{ + env, + ffi::OsStr, + fs, + path::{Path, PathBuf}, + process::{Command, Output}, + sync::atomic::{AtomicUsize, Ordering}, +}; + +static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0); + +const MUTABLE_SCALAR_FIXTURE: &str = r#"(module main) + +(fn flip_flag ((flag bool)) -> bool + (var current bool flag) + (set current (if current + false + true)) + current) + +(fn add_wide_local ((base i64)) -> i64 + (var count i64 base) + (set count (+ count 2i64)) + count) + +(fn add_ratio_local ((base f64)) -> f64 + (var amount f64 base) + (set amount (+ amount 0.5)) + amount) + +(test "mutable bool local true branch" + (if (flip_flag true) + false + true)) + +(test "mutable bool local false branch" + (flip_flag false)) + +(test "mutable i64 local" + (= (add_wide_local 40i64) 42i64)) + +(test "mutable f64 local" + (= (add_ratio_local 41.5) 42.0)) + +(fn main () -> i32 + 0)"#; + +#[test] +fn mutable_scalar_locals_compile_lower_and_test() { + let fixture = write_fixture("mutable-scalar-local", MUTABLE_SCALAR_FIXTURE); + + let compile = run_glagol([fixture.as_os_str()]); + let llvm_stdout = String::from_utf8_lossy(&compile.stdout); + let llvm_stderr = String::from_utf8_lossy(&compile.stderr); + assert!( + compile.status.success(), + "compiler rejected mutable scalar local fixture\nstdout:\n{}\nstderr:\n{}", + llvm_stdout, + llvm_stderr + ); + assert!( + llvm_stdout.contains("define i1 @flip_flag(i1 %flag)") + && llvm_stdout.contains("%current.addr = alloca i1") + && llvm_stdout.contains("define i64 @add_wide_local(i64 %base)") + && llvm_stdout.contains("%count.addr = alloca i64") + && llvm_stdout.contains("define double @add_ratio_local(double %base)") + && llvm_stdout.contains("%amount.addr = alloca double") + && llvm_stdout.contains("store i1") + && llvm_stdout.contains("store i64") + && llvm_stdout.contains("store double"), + "LLVM output lost mutable scalar local shape\nstdout:\n{}", + llvm_stdout + ); + assert!( + llvm_stderr.is_empty(), + "compiler wrote stderr:\n{}", + llvm_stderr + ); + + let tests = run_glagol([OsStr::new("--run-tests"), fixture.as_os_str()]); + assert_success_stdout( + tests, + concat!( + "test \"mutable bool local true branch\" ... ok\n", + "test \"mutable bool local false branch\" ... ok\n", + "test \"mutable i64 local\" ... ok\n", + "test \"mutable f64 local\" ... ok\n", + "4 test(s) passed\n", + ), + "mutable scalar local test runner output", + ); + + let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]); + assert_success_stdout( + formatted, + &format!("{}\n", MUTABLE_SCALAR_FIXTURE), + "mutable scalar local formatter output", + ); + + let surface = run_glagol([ + OsStr::new("--inspect-lowering=surface"), + fixture.as_os_str(), + ]); + let surface_stdout = String::from_utf8_lossy(&surface.stdout); + let surface_stderr = String::from_utf8_lossy(&surface.stderr); + assert!( + surface.status.success(), + "surface lowering rejected mutable scalar local fixture\nstdout:\n{}\nstderr:\n{}", + surface_stdout, + surface_stderr + ); + assert!( + surface_stdout.contains("local var current: bool") + && surface_stdout.contains("set current") + && surface_stdout.contains("local var count: i64") + && surface_stdout.contains("set count") + && surface_stdout.contains("local var amount: f64") + && surface_stdout.contains("set amount"), + "surface lowering lost mutable scalar local declarations\nstdout:\n{}", + surface_stdout + ); + assert!( + surface_stderr.is_empty(), + "surface lowering wrote stderr:\n{}", + surface_stderr + ); + + let checked = run_glagol([ + OsStr::new("--inspect-lowering=checked"), + fixture.as_os_str(), + ]); + let checked_stdout = String::from_utf8_lossy(&checked.stdout); + let checked_stderr = String::from_utf8_lossy(&checked.stderr); + assert!( + checked.status.success(), + "checked lowering rejected mutable scalar local fixture\nstdout:\n{}\nstderr:\n{}", + checked_stdout, + checked_stderr + ); + assert!( + checked_stdout.contains("local var current : unit") + && checked_stdout.contains("set current : unit") + && checked_stdout.contains("var current : bool") + && checked_stdout.contains("local var count : unit") + && checked_stdout.contains("set count : unit") + && checked_stdout.contains("var count : i64") + && checked_stdout.contains("local var amount : unit") + && checked_stdout.contains("set amount : unit") + && checked_stdout.contains("var amount : f64"), + "checked lowering lost typed mutable scalar local flow\nstdout:\n{}", + checked_stdout + ); + assert!( + checked_stderr.is_empty(), + "checked lowering wrote stderr:\n{}", + checked_stderr + ); +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn write_fixture(name: &str, source: &str) -> PathBuf { + let mut path = env::temp_dir(); + path.push(format!( + "glagol-mutable-scalar-locals-{}-{}-{}.slo", + name, + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + fs::write(&path, source).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err)); + path +} + +fn assert_success_stdout(output: Output, expected: &str, context: &str) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert_eq!(stdout, expected, "{} stdout drifted", context); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} diff --git a/compiler/tests/numeric_struct_fields_alpha.rs b/compiler/tests/numeric_struct_fields_alpha.rs new file mode 100644 index 0000000..57f4882 --- /dev/null +++ b/compiler/tests/numeric_struct_fields_alpha.rs @@ -0,0 +1,164 @@ +use std::{ + ffi::OsStr, + path::{Path, PathBuf}, + process::{Command, Output}, +}; + +#[test] +fn numeric_struct_fields_alpha_fixture_emits_numeric_fields_and_tests_pass() { + let fixture = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/numeric-struct-fields.slo"); + let compile = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + + assert!( + compile.status.success(), + "compiler rejected numeric struct field fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define { i64, double } @make_record") + && stdout.contains("insertvalue { i64, double }") + && stdout.contains("extractvalue { i64, double }") + && stdout.contains("add i64") + && stdout.contains("fadd double") + && stdout.contains("call void @print_i64(i64") + && stdout.contains("call void @print_f64(double"), + "LLVM output did not contain expected numeric-in-struct aggregate shape\nstdout:\n{}", + stdout + ); + assert!(stderr.is_empty(), "stderr was not empty:\n{}", stderr); + + let run = run_glagol([OsStr::new("--run-tests"), fixture.as_os_str()]); + assert_success_stdout( + run, + concat!( + "test \"numeric struct i64 field access\" ... ok\n", + "test \"numeric struct f64 field access\" ... ok\n", + "test \"numeric struct i64 arithmetic after access\" ... ok\n", + "test \"numeric struct f64 arithmetic after access\" ... ok\n", + "test \"numeric struct i64 comparison after access\" ... ok\n", + "test \"numeric struct f64 comparison after access\" ... ok\n", + "test \"numeric struct local param return call flow\" ... ok\n", + "test \"numeric struct i64 format after access\" ... ok\n", + "test \"numeric struct f64 format after access\" ... ok\n", + "9 test(s) passed\n", + ), + ); +} + +#[test] +fn numeric_struct_fields_alpha_formatter_and_lowering_are_visible() { + let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/numeric-struct-fields.slo"); + + let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]); + assert_success_stdout( + formatted, + &std::fs::read_to_string(&fixture).expect("read numeric struct field fixture"), + ); + + let surface = run_glagol([ + OsStr::new("--inspect-lowering=surface"), + fixture.as_os_str(), + ]); + assert_success_stdout( + surface, + &std::fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../tests/numeric-struct-fields.surface.lower"), + ) + .expect("read surface snapshot"), + ); + + let checked = run_glagol([ + OsStr::new("--inspect-lowering=checked"), + fixture.as_os_str(), + ]); + assert_success_stdout( + checked, + &std::fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../tests/numeric-struct-fields.checked.lower"), + ) + .expect("read checked snapshot"), + ); +} + +#[test] +fn f64_struct_fields_keep_non_finite_literals_rejected() { + let fixture = write_fixture( + "non-finite", + r#" +(module main) + +(struct Sample + (value f64)) + +(fn main () -> i32 + (let sample Sample (Sample (value inf))) + 0) +"#, + ); + let output = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + !output.status.success(), + "compiler unexpectedly accepted non-finite f64 struct field literal\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.is_empty(), + "rejected compile wrote stdout:\n{}", + stdout + ); + assert!( + stderr.contains("UnsupportedFloatLiteral") + && stderr.contains("f64 literals must be finite"), + "non-finite literal rejection drifted\nstderr:\n{}", + stderr + ); +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(glagol_bin()) + .args(args) + .output() + .expect("run glagol") +} + +fn glagol_bin() -> PathBuf { + PathBuf::from(env!("CARGO_BIN_EXE_glagol")) +} + +fn write_fixture(name: &str, contents: &str) -> PathBuf { + let mut path = std::env::temp_dir(); + path.push(format!( + "glagol-numeric-struct-fields-{}-{}.slo", + name, + std::process::id() + )); + std::fs::write(&path, contents).expect("write fixture"); + path +} + +fn assert_success_stdout(output: Output, expected: &str) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "command failed\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert_eq!(stdout, expected); + assert!(stderr.is_empty(), "stderr was not empty:\n{}", stderr); +} diff --git a/compiler/tests/numeric_widening_conversions_alpha.rs b/compiler/tests/numeric_widening_conversions_alpha.rs new file mode 100644 index 0000000..009b6de --- /dev/null +++ b/compiler/tests/numeric_widening_conversions_alpha.rs @@ -0,0 +1,184 @@ +use std::{ + env, + ffi::OsStr, + fs, + path::{Path, PathBuf}, + process::{Command, Output}, + sync::atomic::{AtomicUsize, Ordering}, +}; + +static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0); + +#[test] +fn numeric_widening_fixture_lowers_and_runs_tests() { + let fixture = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/numeric-widening-conversions.slo"); + + let llvm = run_glagol([fixture.as_os_str()]); + assert_success("compile numeric widening fixture", &llvm); + let stdout = String::from_utf8_lossy(&llvm.stdout); + assert!( + stdout.contains("define i64 @base_i64()") + && stdout.contains("sext i32 2147483647 to i64") + && stdout.contains("sitofp i32 5 to double") + && stdout.contains("sitofp i64 %") + && stdout.contains("call void @print_i64(i64") + && stdout.contains("call void @print_f64(double") + && !stdout.contains("@std.num."), + "numeric widening LLVM shape drifted\nstdout:\n{}", + stdout + ); + + let tests = run_glagol([OsStr::new("test"), fixture.as_os_str()]); + assert_success("run numeric widening fixture tests", &tests); + assert_eq!( + String::from_utf8_lossy(&tests.stdout), + concat!( + "test \"i32 widens to i64\" ... ok\n", + "test \"i32 to i64 feeds i64 arithmetic\" ... ok\n", + "test \"i32 widens to f64\" ... ok\n", + "test \"i64 widens to f64\" ... ok\n", + "4 test(s) passed\n", + ), + "numeric widening test runner stdout drifted" + ); +} + +#[test] +fn numeric_widening_formatter_and_lowering_are_visible() { + let fixture = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/numeric-widening-conversions.slo"); + + let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]); + assert_success("format numeric widening fixture", &formatted); + let formatted_stdout = String::from_utf8_lossy(&formatted.stdout); + assert!( + formatted_stdout.contains("(std.num.i32_to_i64 2147483647)") + && formatted_stdout.contains("(std.num.i32_to_f64 5)") + && formatted_stdout.contains("(std.num.i64_to_f64 (widened_i64))"), + "numeric widening formatter output omitted expected calls\nstdout:\n{}", + formatted_stdout + ); + + let surface = run_glagol([ + OsStr::new("--inspect-lowering=surface"), + fixture.as_os_str(), + ]); + assert_success("inspect numeric widening surface lowering", &surface); + assert_eq!( + String::from_utf8_lossy(&surface.stdout), + fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../tests/numeric-widening-conversions.surface.lower") + ) + .expect("read numeric widening surface snapshot"), + "numeric widening surface lowering snapshot drifted" + ); + + let checked = run_glagol([ + OsStr::new("--inspect-lowering=checked"), + fixture.as_os_str(), + ]); + assert_success("inspect numeric widening checked lowering", &checked); + assert_eq!( + String::from_utf8_lossy(&checked.stdout), + fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../tests/numeric-widening-conversions.checked.lower") + ) + .expect("read numeric widening checked snapshot"), + "numeric widening checked lowering snapshot drifted" + ); +} + +#[test] +fn numeric_widening_rejections_are_explicit() { + for (name, source, expected) in [ + ( + "arity", + "(module main)\n\n(fn main () -> i64\n (std.num.i32_to_i64))\n", + "wrong number of arguments", + ), + ( + "type", + "(module main)\n\n(fn main () -> f64\n (std.num.i32_to_f64 1i64))\n", + "cannot call `std.num.i32_to_f64` with argument of wrong type", + ), + ( + "i64-to-i32", + "(module main)\n\n(fn main () -> i32\n (std.num.i64_to_i32 1i64)\n 0)\n", + "standard library call `std.num.i64_to_i32` is not supported", + ), + ( + "f64-to-i32", + "(module main)\n\n(fn main () -> i32\n (std.num.f64_to_i32 1.0)\n 0)\n", + "standard library call `std.num.f64_to_i32` is not supported", + ), + ( + "cast", + "(module main)\n\n(fn main () -> i32\n (std.num.cast 1)\n 0)\n", + "standard library call `std.num.cast` is not supported", + ), + ] { + let fixture = write_fixture(name, source); + let output = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + !output.status.success(), + "compiler unexpectedly accepted numeric widening rejection fixture `{}`\nstdout:\n{}\nstderr:\n{}", + name, + stdout, + stderr + ); + assert!( + stdout.is_empty(), + "rejected compile wrote stdout for `{}`:\n{}", + name, + stdout + ); + assert!( + stderr.contains(expected), + "numeric widening diagnostic drifted for `{}`\nstderr:\n{}", + name, + stderr + ); + } +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn write_fixture(name: &str, source: &str) -> PathBuf { + let mut path = env::temp_dir(); + path.push(format!( + "glagol-numeric-widening-alpha-{}-{}-{}.slo", + name, + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + fs::write(&path, source).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err)); + path +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); +} diff --git a/compiler/tests/option_result.rs b/compiler/tests/option_result.rs new file mode 100644 index 0000000..5c6f402 --- /dev/null +++ b/compiler/tests/option_result.rs @@ -0,0 +1,749 @@ +use std::{fs, path::Path, process::Command}; + +#[test] +fn option_result_fixture_emits_llvm_aggregate_shape() { + let output = run_glagol(["../examples/option-result.slo"]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "compiler rejected option/result fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define { i1, i32 } @maybe_value()") + && stdout.contains("define { i1, i32 } @maybe_empty()") + && stdout.contains("define { i1, i64 } @maybe_wide_value()") + && stdout.contains("define { i1, i64 } @maybe_wide_empty()") + && stdout.contains("define { i1, double } @maybe_float_value()") + && stdout.contains("define { i1, double } @maybe_float_empty()") + && stdout.contains("define { i1, ptr } @maybe_string_value()") + && stdout.contains("define { i1, ptr } @maybe_string_empty()") + && stdout.contains("define { i1, i32 } @result_ok()") + && stdout.contains("define { i1, i32 } @result_err()") + && stdout.contains("insertvalue { i1, i32 } undef, i1 1, 0") + && stdout.contains("insertvalue { i1, i32 } undef, i1 0, 0") + && stdout.contains("insertvalue { i1, i64 } undef, i1 1, 0") + && stdout.contains("insertvalue { i1, i64 } undef, i1 0, 0") + && stdout.contains("insertvalue { i1, double } undef, i1 1, 0") + && stdout.contains("insertvalue { i1, double } %") + && stdout.contains("double 0.0") + && stdout.contains("insertvalue { i1, ptr } undef, i1 1, 0") + && stdout.contains("insertvalue { i1, ptr } undef, i1 0, 0") + && stdout.contains("insertvalue { i1, i32 } %") + && stdout.contains("insertvalue { i1, i64 } %") + && stdout.contains("insertvalue { i1, ptr } %") + && stdout.contains("ret { i1, i32 } %"), + "LLVM output did not contain expected option/result aggregate shape\nstdout:\n{}", + stdout + ); + assert!( + !stdout.contains("define ptr @maybe_value") + && !stdout.contains("define ptr @maybe_empty") + && !stdout.contains("define ptr @maybe_string_value") + && !stdout.contains("define ptr @maybe_string_empty") + && !stdout.contains("define ptr @result_ok") + && !stdout.contains("define ptr @result_err"), + "LLVM output used ptr fallback for option/result returns\nstdout:\n{}", + stdout + ); + assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr); +} + +#[test] +fn option_result_fixture_is_formatter_stable() { + let expected = fs::read_to_string("../tests/option-result.slo").expect("read fixture"); + let output = run_glagol(["--format", "../tests/option-result.slo"]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "formatter rejected option/result fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert_eq!(stdout, expected, "formatter output drifted"); + assert!(stderr.is_empty(), "formatter wrote stderr:\n{}", stderr); +} + +#[test] +fn option_result_fixture_prints_lowered_shape() { + let surface = run_glagol([ + "--inspect-lowering=surface", + "../examples/option-result.slo", + ]); + let surface_stdout = String::from_utf8_lossy(&surface.stdout); + let surface_stderr = String::from_utf8_lossy(&surface.stderr); + assert!( + surface.status.success(), + "surface lowering rejected option/result fixture\nstdout:\n{}\nstderr:\n{}", + surface_stdout, + surface_stderr + ); + assert!( + surface_stdout.contains("fn maybe_value() -> (option i32)") + && surface_stdout.contains("fn maybe_wide_value() -> (option i64)") + && surface_stdout.contains("fn maybe_string_value() -> (option string)") + && surface_stdout.contains("some i32") + && surface_stdout.contains("some i64") + && surface_stdout.contains("some string") + && surface_stdout.contains("none i32") + && surface_stdout.contains("none i64") + && surface_stdout.contains("none string") + && surface_stdout.contains("ok i32 i32") + && surface_stdout.contains("err i32 i32"), + "surface lowering output lost option/result shape\nstdout:\n{}", + surface_stdout + ); + assert!( + surface_stderr.is_empty(), + "surface lowering wrote stderr:\n{}", + surface_stderr + ); + + let checked = run_glagol([ + "--inspect-lowering=checked", + "../examples/option-result.slo", + ]); + let checked_stdout = String::from_utf8_lossy(&checked.stdout); + let checked_stderr = String::from_utf8_lossy(&checked.stderr); + assert!( + checked.status.success(), + "checked lowering rejected option/result fixture\nstdout:\n{}\nstderr:\n{}", + checked_stdout, + checked_stderr + ); + assert!( + checked_stdout.contains("some : (option i32)") + && checked_stdout.contains("none : (option i32)") + && checked_stdout.contains("some : (option i64)") + && checked_stdout.contains("none : (option i64)") + && checked_stdout.contains("some : (option string)") + && checked_stdout.contains("none : (option string)") + && checked_stdout.contains("ok : (result i32 i32)") + && checked_stdout.contains("err : (result i32 i32)"), + "checked lowering output lost typed option/result shape\nstdout:\n{}", + checked_stdout + ); + assert!( + checked_stderr.is_empty(), + "checked lowering wrote stderr:\n{}", + checked_stderr + ); +} + +#[test] +fn option_result_flow_fixture_emits_llvm_value_flow_shape() { + let output = run_glagol(["../examples/option-result-flow.slo"]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "compiler rejected option/result flow fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define i32 @option_score({ i1, i32 } %value)") + && stdout.contains("define i32 @option_empty_score({ i1, i32 } %value)") + && stdout.contains("define i32 @option_float_score({ i1, double } %value)") + && stdout.contains("define i32 @option_float_empty_score({ i1, double } %value)") + && stdout.contains("define i32 @option_string_score({ i1, ptr } %value)") + && stdout.contains("define i32 @option_string_empty_score({ i1, ptr } %value)") + && stdout.contains("define i32 @result_success_score({ i1, i32 } %value)") + && stdout.contains("define i32 @result_failure_score({ i1, i32 } %value)") + && stdout.contains("%value.addr = alloca { i1, i32 }") + && stdout.contains("store { i1, i32 } %") + && stdout.contains("load { i1, i32 }, ptr %value.addr") + && stdout.contains("extractvalue { i1, i32 } %") + && stdout.contains("%value.addr = alloca { i1, double }") + && stdout.contains("store { i1, double } %") + && stdout.contains("load { i1, double }, ptr %value.addr") + && stdout.contains("extractvalue { i1, double } %") + && stdout.contains("%value.addr = alloca { i1, ptr }") + && stdout.contains("store { i1, ptr } %") + && stdout.contains("load { i1, ptr }, ptr %value.addr") + && stdout.contains("extractvalue { i1, ptr } %") + && stdout.contains("xor i1 %"), + "LLVM output did not contain expected option/result value-flow shape\nstdout:\n{}", + stdout + ); + assert!( + !stdout.contains("define ptr @option_score") + && !stdout.contains("define ptr @result_success_score"), + "LLVM output used ptr fallback for option/result params\nstdout:\n{}", + stdout + ); + assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr); +} + +#[test] +fn option_result_flow_fixture_runs_top_level_tests() { + let output = run_glagol(["--run-tests", "../examples/option-result-flow.slo"]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "test runner rejected option/result flow fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert_eq!( + stdout, + concat!( + "test \"option local value flow\" ... ok\n", + "test \"option call observation\" ... ok\n", + "test \"option i64 local value flow\" ... ok\n", + "test \"option i64 call observation\" ... ok\n", + "test \"option f64 local value flow\" ... ok\n", + "test \"option f64 call observation\" ... ok\n", + "test \"option bool local value flow\" ... ok\n", + "test \"option bool call observation\" ... ok\n", + "test \"option string local value flow\" ... ok\n", + "test \"option string call observation\" ... ok\n", + "test \"result call observation\" ... ok\n", + "test \"result local value flow\" ... ok\n", + "12 test(s) passed\n", + ) + ); + assert!(stderr.is_empty(), "test runner wrote stderr:\n{}", stderr); +} + +#[test] +fn option_result_flow_fixture_is_formatter_stable() { + let expected = fs::read_to_string("../tests/option-result-flow.slo").expect("read fixture"); + let output = run_glagol(["--format", "../tests/option-result-flow.slo"]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "formatter rejected option/result flow fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert_eq!(stdout, expected, "formatter output drifted"); + assert!(stderr.is_empty(), "formatter wrote stderr:\n{}", stderr); +} + +#[test] +fn option_result_flow_fixture_prints_lowered_shape() { + let surface = run_glagol([ + "--inspect-lowering=surface", + "../examples/option-result-flow.slo", + ]); + let surface_stdout = String::from_utf8_lossy(&surface.stdout); + let surface_stderr = String::from_utf8_lossy(&surface.stderr); + assert!( + surface.status.success(), + "surface lowering rejected option/result flow fixture\nstdout:\n{}\nstderr:\n{}", + surface_stdout, + surface_stderr + ); + assert!( + surface_stdout.contains("fn option_score(value: (option i32)) -> i32") + && surface_stdout.contains("fn option_wide_score(value: (option i64)) -> i32") + && surface_stdout.contains("fn option_string_score(value: (option string)) -> i32") + && surface_stdout.contains("is_some") + && surface_stdout.contains("is_none") + && surface_stdout.contains("is_ok") + && surface_stdout.contains("is_err"), + "surface lowering output lost option/result flow shape\nstdout:\n{}", + surface_stdout + ); + assert!( + surface_stderr.is_empty(), + "surface lowering wrote stderr:\n{}", + surface_stderr + ); + + let checked = run_glagol([ + "--inspect-lowering=checked", + "../examples/option-result-flow.slo", + ]); + let checked_stdout = String::from_utf8_lossy(&checked.stdout); + let checked_stderr = String::from_utf8_lossy(&checked.stderr); + assert!( + checked.status.success(), + "checked lowering rejected option/result flow fixture\nstdout:\n{}\nstderr:\n{}", + checked_stdout, + checked_stderr + ); + assert!( + checked_stdout.contains("is_some : bool") + && checked_stdout.contains("is_none : bool") + && checked_stdout.contains("is_ok : bool") + && checked_stdout.contains("is_err : bool") + && checked_stdout.contains("call maybe_wide_value : (option i64)") + && checked_stdout.contains("call maybe_string_value : (option string)") + && checked_stdout.contains("var value : (option i64)") + && checked_stdout.contains("var value : (option string)") + && checked_stdout.contains("local let value : unit"), + "checked lowering output lost typed option/result flow shape\nstdout:\n{}", + checked_stdout + ); + assert!( + checked_stderr.is_empty(), + "checked lowering wrote stderr:\n{}", + checked_stderr + ); +} + +#[test] +fn option_result_payload_fixture_emits_llvm_unwrap_shape() { + let output = run_glagol(["../examples/option-result-payload.slo"]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "compiler rejected option/result payload fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("declare void @__glagol_unwrap_some_trap()") + && stdout.contains("declare void @__glagol_unwrap_ok_trap()") + && stdout.contains("declare void @__glagol_unwrap_err_trap()") + && stdout.contains("define i32 @option_direct_payload()") + && stdout.contains("define i32 @option_param_payload({ i1, i32 } %value)") + && stdout.contains("define i32 @option_local_payload()") + && stdout.contains("define i32 @option_call_payload()") + && stdout.contains("define i32 @option_guarded_payload({ i1, i32 } %value)") + && stdout.contains("define ptr @option_string_direct_payload()") + && stdout.contains("define ptr @option_string_param_payload({ i1, ptr } %value)") + && stdout.contains("define ptr @option_string_local_payload()") + && stdout.contains("define ptr @option_string_call_payload()") + && stdout.contains("define ptr @option_string_guarded_payload({ i1, ptr } %value)") + && stdout.contains("define i32 @result_ok_direct_payload()") + && stdout.contains("define i32 @result_err_direct_payload()") + && stdout.contains("define i32 @result_ok_param_payload({ i1, i32 } %value)") + && stdout.contains("define i32 @result_err_param_payload({ i1, i32 } %value)") + && stdout.contains("define i32 @result_ok_local_payload()") + && stdout.contains("define i32 @result_err_call_payload()") + && stdout.contains("call void @__glagol_unwrap_some_trap()") + && stdout.contains("call void @__glagol_unwrap_ok_trap()") + && stdout.contains("call void @__glagol_unwrap_err_trap()") + && stdout.contains("unwrap.trap") + && stdout.contains("if.then") + && stdout.contains(" phi i32 ") + && stdout.contains(" phi ptr ") + && stdout.contains("extractvalue { i1, i32 } %"), + "LLVM output did not contain expected option/result payload-access shape\nstdout:\n{}", + stdout + ); + assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr); +} + +#[test] +fn option_result_payload_fixture_runs_top_level_tests() { + let output = run_glagol(["--run-tests", "../examples/option-result-payload.slo"]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "test runner rejected option/result payload fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert_eq!( + stdout, + concat!( + "test \"unwrap some direct constructor\" ... ok\n", + "test \"unwrap some local\" ... ok\n", + "test \"unwrap some call\" ... ok\n", + "test \"unwrap some parameter\" ... ok\n", + "test \"guarded unwrap some present\" ... ok\n", + "test \"guarded unwrap some absent\" ... ok\n", + "test \"unwrap some i64 direct constructor\" ... ok\n", + "test \"unwrap some i64 local\" ... ok\n", + "test \"unwrap some i64 call\" ... ok\n", + "test \"unwrap some i64 parameter\" ... ok\n", + "test \"guarded unwrap some i64 present\" ... ok\n", + "test \"guarded unwrap some i64 absent\" ... ok\n", + "test \"unwrap some f64 direct constructor\" ... ok\n", + "test \"unwrap some f64 local\" ... ok\n", + "test \"unwrap some f64 call\" ... ok\n", + "test \"unwrap some f64 parameter\" ... ok\n", + "test \"guarded unwrap some f64 present\" ... ok\n", + "test \"guarded unwrap some f64 absent\" ... ok\n", + "test \"unwrap some bool direct constructor\" ... ok\n", + "test \"unwrap some bool local\" ... ok\n", + "test \"unwrap some bool call\" ... ok\n", + "test \"unwrap some bool parameter\" ... ok\n", + "test \"guarded unwrap some bool present\" ... ok\n", + "test \"guarded unwrap some bool absent\" ... ok\n", + "test \"unwrap some string direct constructor\" ... ok\n", + "test \"unwrap some string local\" ... ok\n", + "test \"unwrap some string call\" ... ok\n", + "test \"unwrap some string parameter\" ... ok\n", + "test \"guarded unwrap some string present\" ... ok\n", + "test \"guarded unwrap some string absent\" ... ok\n", + "test \"unwrap ok direct constructor\" ... ok\n", + "test \"unwrap err direct constructor\" ... ok\n", + "test \"unwrap ok parameter\" ... ok\n", + "test \"unwrap err parameter\" ... ok\n", + "test \"unwrap ok local\" ... ok\n", + "test \"unwrap err call\" ... ok\n", + "36 test(s) passed\n", + ) + ); + assert!(stderr.is_empty(), "test runner wrote stderr:\n{}", stderr); +} + +#[test] +fn option_result_payload_fixture_is_formatter_stable() { + let expected = fs::read_to_string("../tests/option-result-payload.slo").expect("read fixture"); + let output = run_glagol(["--format", "../tests/option-result-payload.slo"]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "formatter rejected option/result payload fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert_eq!(stdout, expected, "formatter output drifted"); + assert!(stderr.is_empty(), "formatter wrote stderr:\n{}", stderr); +} + +#[test] +fn option_result_payload_fixture_prints_lowered_shape() { + let surface = run_glagol([ + "--inspect-lowering=surface", + "../examples/option-result-payload.slo", + ]); + let surface_stdout = String::from_utf8_lossy(&surface.stdout); + let surface_stderr = String::from_utf8_lossy(&surface.stderr); + assert!( + surface.status.success(), + "surface lowering rejected option/result payload fixture\nstdout:\n{}\nstderr:\n{}", + surface_stdout, + surface_stderr + ); + assert!( + surface_stdout.contains("fn option_direct_payload() -> i32") + && surface_stdout.contains("fn option_param_payload(value: (option i32)) -> i32") + && surface_stdout.contains("fn option_guarded_payload(value: (option i32)) -> i32") + && surface_stdout.contains("fn option_wide_direct_payload() -> i64") + && surface_stdout.contains("fn option_wide_param_payload(value: (option i64)) -> i64") + && surface_stdout + .contains("fn option_wide_guarded_payload(value: (option i64)) -> i64") + && surface_stdout.contains("fn option_string_direct_payload() -> string") + && surface_stdout + .contains("fn option_string_param_payload(value: (option string)) -> string") + && surface_stdout + .contains("fn option_string_guarded_payload(value: (option string)) -> string") + && surface_stdout.contains("fn result_ok_direct_payload() -> i32") + && surface_stdout + .contains("fn result_err_param_payload(value: (result i32 i32)) -> i32") + && surface_stdout.contains("is_some") + && surface_stdout.contains("unwrap_some") + && surface_stdout.contains("unwrap_ok") + && surface_stdout.contains("unwrap_err"), + "surface lowering output lost option/result payload shape\nstdout:\n{}", + surface_stdout + ); + assert!( + surface_stderr.is_empty(), + "surface lowering wrote stderr:\n{}", + surface_stderr + ); + + let checked = run_glagol([ + "--inspect-lowering=checked", + "../examples/option-result-payload.slo", + ]); + let checked_stdout = String::from_utf8_lossy(&checked.stdout); + let checked_stderr = String::from_utf8_lossy(&checked.stderr); + assert!( + checked.status.success(), + "checked lowering rejected option/result payload fixture\nstdout:\n{}\nstderr:\n{}", + checked_stdout, + checked_stderr + ); + assert!( + checked_stdout.contains("unwrap_some : i32") + && checked_stdout.contains("unwrap_some : i64") + && checked_stdout.contains("unwrap_some : string") + && checked_stdout.contains("unwrap_ok : i32") + && checked_stdout.contains("unwrap_err : i32") + && checked_stdout.contains("some : (option i32)") + && checked_stdout.contains("some : (option i64)") + && checked_stdout.contains("some : (option string)") + && checked_stdout.contains("err : (result i32 i32)") + && checked_stdout.contains("is_some : bool"), + "checked lowering output lost typed option/result payload shape\nstdout:\n{}", + checked_stdout + ); + assert!( + checked_stderr.is_empty(), + "checked lowering wrote stderr:\n{}", + checked_stderr + ); +} + +#[test] +fn option_result_payload_llvm_traps_before_extracting_payload() { + let output = run_glagol(["../examples/option-result-payload.slo"]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "compiler rejected option/result payload fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + + assert_unwrap_traps_before_payload_extract( + &function_body( + &stdout, + "define i32 @option_param_payload({ i1, i32 } %value)", + ), + false, + ); + assert_unwrap_traps_before_payload_extract( + &function_body( + &stdout, + "define ptr @option_string_param_payload({ i1, ptr } %value)", + ), + false, + ); + assert_unwrap_traps_before_payload_extract( + &function_body( + &stdout, + "define i32 @result_ok_param_payload({ i1, i32 } %value)", + ), + false, + ); + assert_unwrap_traps_before_payload_extract( + &function_body( + &stdout, + "define i32 @result_err_param_payload({ i1, i32 } %value)", + ), + true, + ); + assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr); +} + +#[test] +fn option_result_match_fixture_emits_llvm_branch_shape() { + let output = run_glagol(["../examples/option-result-match.slo"]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "compiler rejected option/result match fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define i32 @option_value_or({ i1, i32 } %value, i32 %fallback)") + && stdout.contains("define i32 @option_bump_or_zero({ i1, i32 } %value)") + && stdout + .contains("define ptr @option_string_value_or({ i1, ptr } %value, ptr %fallback)") + && stdout.contains( + "define ptr @option_string_selected_or({ i1, ptr } %value, ptr %fallback)" + ) + && stdout.contains("define i32 @result_value_or_code({ i1, i32 } %value)") + && stdout.contains("define i32 @result_score({ i1, i32 } %value)") + && stdout.contains("match.some") + && stdout.contains("match.none") + && stdout.contains("match.ok") + && stdout.contains("match.err") + && stdout.contains("match.end") + && stdout.contains("br i1") + && stdout.contains(" phi i32 ") + && stdout.contains(" phi ptr ") + && stdout.contains("extractvalue { i1, i32 } %"), + "LLVM output did not contain expected match branch shape\nstdout:\n{}", + stdout + ); + assert!( + !function_body( + &stdout, + "define i32 @option_value_or({ i1, i32 } %value, i32 %fallback)", + ) + .contains("__glagol_unwrap"), + "match lowering must not call unwrap traps\nstdout:\n{}", + stdout + ); + assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr); +} + +#[test] +fn option_result_match_fixture_runs_top_level_tests() { + let output = run_glagol(["--run-tests", "../examples/option-result-match.slo"]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "test runner rejected option/result match fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert_eq!( + stdout, + concat!( + "test \"option match some payload\" ... ok\n", + "test \"option match none fallback\" ... ok\n", + "test \"option match multi expression arm\" ... ok\n", + "test \"option i64 match some payload\" ... ok\n", + "test \"option i64 match none fallback\" ... ok\n", + "test \"option i64 match multi expression arm\" ... ok\n", + "test \"option f64 match some payload\" ... ok\n", + "test \"option f64 match none fallback\" ... ok\n", + "test \"option f64 match multi expression arm\" ... ok\n", + "test \"option bool match some payload\" ... ok\n", + "test \"option bool match none fallback\" ... ok\n", + "test \"option bool match multi expression arm\" ... ok\n", + "test \"option string match some payload\" ... ok\n", + "test \"option string match none fallback\" ... ok\n", + "test \"option string match multi expression arm\" ... ok\n", + "test \"result match ok payload\" ... ok\n", + "test \"result match err payload\" ... ok\n", + "test \"result match computed arm\" ... ok\n", + "18 test(s) passed\n", + ) + ); + assert!(stderr.is_empty(), "test runner wrote stderr:\n{}", stderr); +} + +#[test] +fn option_result_match_fixture_is_formatter_stable() { + let expected = fs::read_to_string("../tests/option-result-match.slo").expect("read fixture"); + let output = run_glagol(["--format", "../tests/option-result-match.slo"]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "formatter rejected option/result match fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert_eq!(stdout, expected, "formatter output drifted"); + assert!(stderr.is_empty(), "formatter wrote stderr:\n{}", stderr); +} + +#[test] +fn option_result_match_fixture_prints_lowered_shape() { + let surface = run_glagol([ + "--inspect-lowering=surface", + "../examples/option-result-match.slo", + ]); + let surface_stdout = String::from_utf8_lossy(&surface.stdout); + let surface_stderr = String::from_utf8_lossy(&surface.stderr); + assert!( + surface.status.success(), + "surface lowering rejected option/result match fixture\nstdout:\n{}\nstderr:\n{}", + surface_stdout, + surface_stderr + ); + assert!( + surface_stdout.contains("match") + && surface_stdout.contains("subject") + && surface_stdout.contains("arm some payload") + && surface_stdout.contains("arm none") + && surface_stdout.contains("arm ok payload") + && surface_stdout.contains("arm err code"), + "surface lowering output lost match shape\nstdout:\n{}", + surface_stdout + ); + assert!( + surface_stderr.is_empty(), + "surface lowering wrote stderr:\n{}", + surface_stderr + ); + + let checked = run_glagol([ + "--inspect-lowering=checked", + "../examples/option-result-match.slo", + ]); + let checked_stdout = String::from_utf8_lossy(&checked.stdout); + let checked_stderr = String::from_utf8_lossy(&checked.stderr); + assert!( + checked.status.success(), + "checked lowering rejected option/result match fixture\nstdout:\n{}\nstderr:\n{}", + checked_stdout, + checked_stderr + ); + assert!( + checked_stdout.contains("match : i32") + && checked_stdout.contains("var payload : i32") + && checked_stdout.contains("var payload : i64") + && checked_stdout.contains("var fallback : i32") + && checked_stdout.contains("var fallback : i64") + && checked_stdout.contains("var code : i32"), + "checked lowering output lost typed match shape\nstdout:\n{}", + checked_stdout + ); + assert!( + checked_stderr.is_empty(), + "checked lowering wrote stderr:\n{}", + checked_stderr + ); +} + +fn function_body<'a>(ir: &'a str, signature: &str) -> &'a str { + let start = ir + .find(signature) + .unwrap_or_else(|| panic!("missing function signature `{}`\n{}", signature, ir)); + let rest = &ir[start..]; + let end = rest + .find("\n}\n") + .unwrap_or_else(|| panic!("missing function end for `{}`", signature)); + &rest[..end] +} + +fn assert_unwrap_traps_before_payload_extract(body: &str, expects_inverted_tag: bool) { + let tag_extract = body + .find(", 0") + .unwrap_or_else(|| panic!("missing tag extract in function body:\n{}", body)); + let branch = body + .find("br i1") + .unwrap_or_else(|| panic!("missing unwrap branch in function body:\n{}", body)); + let trap_call = body + .find("call void @__glagol_unwrap") + .unwrap_or_else(|| panic!("missing unwrap trap call in function body:\n{}", body)); + let payload_extract = body + .rfind(", 1") + .unwrap_or_else(|| panic!("missing payload extract in function body:\n{}", body)); + + assert!( + tag_extract < branch && branch < trap_call && trap_call < payload_extract, + "unwrap payload extraction was not guarded by a prior tag trap path\n{}", + body + ); + assert!( + body.contains("unreachable"), + "unwrap trap block must terminate with unreachable\n{}", + body + ); + + if expects_inverted_tag { + assert!( + body.contains("icmp eq i1"), + "unwrap_err must branch on the inverted result tag\n{}", + body + ); + } +} + +fn run_glagol(args: [&str; N]) -> std::process::Output { + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} diff --git a/compiler/tests/primitive_struct_fields_alpha.rs b/compiler/tests/primitive_struct_fields_alpha.rs new file mode 100644 index 0000000..215846b --- /dev/null +++ b/compiler/tests/primitive_struct_fields_alpha.rs @@ -0,0 +1,112 @@ +use std::{ + ffi::OsStr, + path::{Path, PathBuf}, + process::{Command, Output}, +}; + +#[test] +fn primitive_struct_fields_alpha_fixture_emits_primitive_fields_and_tests_pass() { + let fixture = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/primitive-struct-fields.slo"); + let compile = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + + assert!( + compile.status.success(), + "compiler rejected primitive struct field fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define { i32, i1, ptr } @make_record") + && stdout.contains("insertvalue { i32, i1, ptr }") + && stdout.contains("extractvalue { i32, i1, ptr }") + && stdout.contains("call i1 @__glagol_string_eq") + && stdout.contains("call i32 @string_len"), + "LLVM output did not contain expected primitive-in-struct aggregate shape\nstdout:\n{}", + stdout + ); + assert!(stderr.is_empty(), "stderr was not empty:\n{}", stderr); + + let run = run_glagol([OsStr::new("--run-tests"), fixture.as_os_str()]); + assert_success_stdout( + run, + concat!( + "test \"primitive struct i32 field access\" ... ok\n", + "test \"primitive struct bool field predicate\" ... ok\n", + "test \"primitive struct bool field false branch\" ... ok\n", + "test \"primitive struct string field equality\" ... ok\n", + "test \"primitive struct string field length\" ... ok\n", + "test \"primitive struct local param return call flow\" ... ok\n", + "test \"primitive struct string field access return\" ... ok\n", + "7 test(s) passed\n", + ), + ); +} + +#[test] +fn primitive_struct_fields_alpha_formatter_and_lowering_are_visible() { + let fixture = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/primitive-struct-fields.slo"); + + let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]); + assert_success_stdout( + formatted, + &std::fs::read_to_string(&fixture).expect("read primitive struct field fixture"), + ); + + let surface = run_glagol([ + OsStr::new("--inspect-lowering=surface"), + fixture.as_os_str(), + ]); + assert_success_stdout( + surface, + &std::fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../tests/primitive-struct-fields.surface.lower"), + ) + .expect("read surface snapshot"), + ); + + let checked = run_glagol([ + OsStr::new("--inspect-lowering=checked"), + fixture.as_os_str(), + ]); + assert_success_stdout( + checked, + &std::fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../tests/primitive-struct-fields.checked.lower"), + ) + .expect("read checked snapshot"), + ); +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(glagol_bin()) + .args(args) + .output() + .expect("run glagol") +} + +fn glagol_bin() -> PathBuf { + PathBuf::from(env!("CARGO_BIN_EXE_glagol")) +} + +fn assert_success_stdout(output: Output, expected: &str) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "command failed\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert_eq!(stdout, expected); + assert!(stderr.is_empty(), "stderr was not empty:\n{}", stderr); +} diff --git a/compiler/tests/project_mode.rs b/compiler/tests/project_mode.rs new file mode 100644 index 0000000..423b53c --- /dev/null +++ b/compiler/tests/project_mode.rs @@ -0,0 +1,1622 @@ +use std::{ + fs, + path::{Path, PathBuf}, + process::{Command, Output}, + sync::atomic::{AtomicUsize, Ordering}, + time::{SystemTime, UNIX_EPOCH}, +}; + +static NEXT_PROJECT_ID: AtomicUsize = AtomicUsize::new(0); + +const UNSAFE_HEADS: &[&str] = &[ + "alloc", + "dealloc", + "load", + "store", + "ptr_add", + "unchecked_index", + "reinterpret", + "ffi_call", +]; + +#[test] +fn check_accepts_basic_project_root_and_manifest_path() { + let project = write_project( + "basic", + &[( + "math", + "(module math (export add_one))\n\n(fn add_one ((value i32)) -> i32\n (+ value 1))\n", + )], + "(module main)\n\n(import math (add_one))\n\n(fn main () -> i32\n (add_one 41))\n", + ); + + let root = run_glagol(["check".as_ref(), project.as_os_str()]); + assert_success_stdout("project root check", root, ""); + + let manifest = project.join("slovo.toml"); + let manifest_path = run_glagol(["check".as_ref(), manifest.as_os_str()]); + assert_success_stdout("manifest path check", manifest_path, ""); +} + +#[test] +fn project_check_preserves_enum_payload_layout_metadata() { + let project = write_project( + "project-enum-payload", + &[], + "(module main)\n\n\ +(enum Reading\n Missing\n (Value string))\n\n\ +(fn same ((left Reading) (right Reading)) -> bool\n (= left right))\n\n\ +(fn main () -> i32\n (if (same (Reading.Value \"hello\") (Reading.Value \"hello\")) 0 1))\n", + ); + + let output = run_glagol(["check".as_ref(), project.as_os_str()]); + + assert_success_stdout("project enum payload check", output, ""); +} + +#[test] +fn project_imports_exported_direct_string_enum_values_across_modules() { + let project = write_project( + "project-direct-string-enum-imports", + &[( + "readings", + "(module readings (export LabelReading make text same))\n\n\ +(enum LabelReading\n Missing\n (Value string))\n\n\ +(fn make ((value string)) -> LabelReading\n (LabelReading.Value value))\n\n\ +(fn text ((reading LabelReading)) -> string\n (match reading\n ((LabelReading.Missing)\n \"\")\n ((LabelReading.Value payload)\n payload)))\n\n\ +(fn same ((left LabelReading) (right LabelReading)) -> bool\n (= left right))\n", + )], + "(module main)\n\n\ +(import readings (LabelReading make text same))\n\n\ +(fn pass ((reading LabelReading)) -> LabelReading\n reading)\n\n\ +(fn main () -> i32\n (if (same (pass (make \"hello\")) (LabelReading.Value \"hello\")) 0 1))\n\n\ +(test \"project string enum import match\"\n (= (text (pass (make \"hello\"))) \"hello\"))\n\n\ +(test \"project string enum import equality\"\n (same (pass (LabelReading.Value \"hello\")) (make \"hello\")))\n", + ); + + let check = run_glagol(["check".as_ref(), project.as_os_str()]); + assert_success_stdout("project string enum import check", check, ""); + + let test = run_glagol(["test".as_ref(), project.as_os_str()]); + assert_success_stdout( + "project string enum import test", + test, + "test \"project string enum import match\" ... ok\n\ +test \"project string enum import equality\" ... ok\n\ +2 test(s) passed\n", + ); +} + +#[test] +fn project_imports_exported_enum_values_across_modules() { + let project = write_project( + "project-enum-imports", + &[( + "readings", + "(module readings (export Reading make score same))\n\n\ +(enum Reading\n Missing\n (Value i32))\n\n\ +(fn make ((value i32)) -> Reading\n (Reading.Value value))\n\n\ +(fn score ((reading Reading)) -> i32\n (match reading\n ((Reading.Missing)\n 0)\n ((Reading.Value payload)\n payload)))\n\n\ +(fn same ((left Reading) (right Reading)) -> bool\n (= left right))\n", + )], + "(module main)\n\n\ +(import readings (Reading make score same))\n\n\ +(fn pass ((reading Reading)) -> Reading\n reading)\n\n\ +(fn main () -> i32\n (let reading Reading (pass (make 41)))\n (score reading))\n\n\ +(test \"project enum import match\"\n (let reading Reading (pass (make 41)))\n (= (score reading) 41))\n\n\ +(test \"project enum import equality\"\n (same (pass (Reading.Value 41)) (make 41)))\n", + ); + + let check = run_glagol(["check".as_ref(), project.as_os_str()]); + assert_success_stdout("project enum import check", check, ""); + + let test = run_glagol(["test".as_ref(), project.as_os_str()]); + assert_success_stdout( + "project enum import test", + test, + "test \"project enum import match\" ... ok\n\ +test \"project enum import equality\" ... ok\n\ +2 test(s) passed\n", + ); +} + +#[test] +fn project_imports_direct_scalar_and_string_enum_payloads_across_modules() { + let project = write_project( + "project-enum-direct-scalars", + &[( + "readings", + "(module readings (export WideReading LabelReading make_wide make_label wide_code label_text same_wide same_label))\n\n\ +(enum WideReading\n Missing\n (Value i64))\n\n\ +(enum LabelReading\n Missing\n (Value string))\n\n\ +(fn make_wide ((value i64)) -> WideReading\n (WideReading.Value value))\n\n\ +(fn make_label ((value string)) -> LabelReading\n (LabelReading.Value value))\n\n\ +(fn wide_code ((reading WideReading)) -> i64\n (match reading\n ((WideReading.Missing)\n 0i64)\n ((WideReading.Value payload)\n payload)))\n\n\ +(fn label_text ((reading LabelReading)) -> string\n (match reading\n ((LabelReading.Missing)\n \"\")\n ((LabelReading.Value payload)\n payload)))\n\n\ +(fn same_wide ((left WideReading) (right WideReading)) -> bool\n (= left right))\n\n\ +(fn same_label ((left LabelReading) (right LabelReading)) -> bool\n (= left right))\n", + )], + "(module main)\n\n\ +(import readings (WideReading LabelReading make_wide make_label wide_code label_text same_wide same_label))\n\n\ +(fn pass_wide ((reading WideReading)) -> WideReading\n reading)\n\n\ +(fn pass_label ((reading LabelReading)) -> LabelReading\n reading)\n\n\ +(fn main () -> i32\n (if (= (wide_code (pass_wide (make_wide 41i64))) 41i64)\n 41\n 0))\n\n\ +(test \"project direct scalar enum import wide match\"\n (= (wide_code (pass_wide (make_wide 41i64))) 41i64))\n\n\ +(test \"project direct scalar enum import wide equality\"\n (same_wide (pass_wide (WideReading.Value 41i64)) (make_wide 41i64)))\n\n\ +(test \"project direct scalar enum import string match\"\n (= (label_text (pass_label (make_label \"hi\"))) \"hi\"))\n\n\ +(test \"project direct scalar enum import string equality\"\n (same_label (pass_label (LabelReading.Value \"hi\")) (make_label \"hi\")))\n", + ); + + let check = run_glagol(["check".as_ref(), project.as_os_str()]); + assert_success_stdout("project direct scalar enum import check", check, ""); + + let test = run_glagol(["test".as_ref(), project.as_os_str()]); + assert_success_stdout( + "project direct scalar enum import test", + test, + "test \"project direct scalar enum import wide match\" ... ok\n\ +test \"project direct scalar enum import wide equality\" ... ok\n\ +test \"project direct scalar enum import string match\" ... ok\n\ +test \"project direct scalar enum import string equality\" ... ok\n\ +4 test(s) passed\n", + ); +} + +#[test] +fn std_layout_local_math_project_uses_only_explicit_local_imports() { + let project = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/projects/std-layout-local-math"); + + let check = run_glagol(["check".as_ref(), project.as_os_str()]); + assert_success_stdout("std layout local math check", check, ""); + + let test = run_glagol(["test".as_ref(), project.as_os_str()]); + assert_success_stdout( + "std layout local math test", + test, + "test \"local math i32 helpers\" ... ok\n\ +test \"local math i64 helpers\" ... ok\n\ +test \"local math f64 helpers\" ... ok\n\ +test \"explicit local math import i32 helpers\" ... ok\n\ +test \"explicit local math import i64 helpers\" ... ok\n\ +test \"explicit local math import f64 helpers\" ... ok\n\ +test \"explicit local math import all helpers\" ... ok\n\ +7 test(s) passed\n", + ); +} + +#[test] +fn std_layout_local_result_project_uses_only_explicit_local_imports() { + let project = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/projects/std-layout-local-result"); + + let check = run_glagol(["check".as_ref(), project.as_os_str()]); + assert_success_stdout("std layout local result check", check, ""); + + let test = run_glagol(["test".as_ref(), project.as_os_str()]); + assert_success_stdout( + "std layout local result test", + test, + "test \"explicit local result i32 wrappers\" ... ok\n\ +test \"explicit local result err wrappers\" ... ok\n\ +test \"explicit local result unwrap_or i32\" ... ok\n\ +test \"explicit local result ok_or_none i32 ok\" ... ok\n\ +test \"explicit local result ok_or_none i32 none\" ... ok\n\ +test \"explicit local result u32 wrappers\" ... ok\n\ +test \"explicit local result unwrap_or u32\" ... ok\n\ +test \"explicit local result ok_or_none u32 ok\" ... ok\n\ +test \"explicit local result ok_or_none u32 none\" ... ok\n\ +test \"explicit local result unwrap_or i64\" ... ok\n\ +test \"explicit local result ok_or_none i64 ok\" ... ok\n\ +test \"explicit local result ok_or_none i64 none\" ... ok\n\ +test \"explicit local result u64 wrappers\" ... ok\n\ +test \"explicit local result unwrap_or u64\" ... ok\n\ +test \"explicit local result ok_or_none u64 ok\" ... ok\n\ +test \"explicit local result ok_or_none u64 none\" ... ok\n\ +test \"explicit local result unwrap_or string\" ... ok\n\ +test \"explicit local result ok_or_none string ok\" ... ok\n\ +test \"explicit local result ok_or_none string none\" ... ok\n\ +test \"explicit local result unwrap_or f64\" ... ok\n\ +test \"explicit local result ok_or_none f64 ok\" ... ok\n\ +test \"explicit local result ok_or_none f64 none\" ... ok\n\ +test \"explicit local result bool helpers\" ... ok\n\ +test \"explicit local result ok_or_none bool ok\" ... ok\n\ +test \"explicit local result ok_or_none bool none\" ... ok\n\ +test \"explicit local result helpers all\" ... ok\n\ +26 test(s) passed\n", + ); +} + +#[test] +fn test_project_runs_tests_in_topological_module_order() { + let project = write_project( + "test-order", + &[("a", "(module a (export value))\n\n(fn value () -> i32\n 1)\n\n(test \"provider first\"\n (= (value) 1))\n")], + "(module main)\n\n(import a (value))\n\n(fn main () -> i32\n (value))\n\n(test \"consumer second\"\n (= (value) 1))\n", + ); + + let output = run_glagol(["test".as_ref(), project.as_os_str()]); + + assert_success_stdout( + "project test", + output, + "test \"provider first\" ... ok\ntest \"consumer second\" ... ok\n2 test(s) passed\n", + ); +} + +#[test] +fn test_project_filter_preserves_order_and_counts_skips() { + let project = write_project( + "test-filter", + &[("a", "(module a (export value))\n\n(fn value () -> i32\n 1)\n\n(test \"provider first\"\n (= (value) 1))\n")], + "(module main)\n\n(import a (value))\n\n(fn main () -> i32\n (value))\n\n(test \"consumer second\"\n (= (value) 1))\n", + ); + + let output = run_glagol([ + "test".as_ref(), + project.as_os_str(), + "--filter".as_ref(), + "consumer".as_ref(), + ]); + + assert_success_stdout( + "project filtered test", + output, + "test \"provider first\" ... skipped\n\ +test \"consumer second\" ... ok\n\ +1 test(s) passed (total_discovered 2, selected 1, passed 1, failed 0, skipped 1, filter \"consumer\")\n", + ); +} + +#[test] +fn manifest_records_project_fields_modules_and_import_edges() { + let project = write_project( + "manifest", + &[( + "math", + "(module math (export add_one))\n\n(fn add_one ((value i32)) -> i32\n (+ value 1))\n", + )], + "(module main)\n\n(import math (add_one))\n\n(fn main () -> i32\n (add_one 41))\n", + ); + let manifest = unique_path("artifact-manifest"); + + let output = run_glagol([ + "check".as_ref(), + "--manifest".as_ref(), + manifest.as_os_str(), + project.as_os_str(), + ]); + + assert_success("project manifest check", &output); + let artifact = fs::read_to_string(&manifest).expect("read artifact manifest"); + assert_project_block_snapshot( + "success check project manifest", + &artifact, + &project, + r#" (project + (project_manifest "$PROJECT/slovo.toml") + (project_root "$PROJECT") + (source_root "src") + (project_name "manifest") + (entry_module "main") + (modules + (module + (name "math") + (path "$PROJECT/src/math.slo") + (imports) + ) + (module + (name "main") + (path "$PROJECT/src/main.slo") + (imports + (import "math") + ) + ) + ) + (import_edges + (import_edge + (from "main") + (to "math") + ) + ) + (diagnostics_count 0) + (diagnostic_artifacts) + (build_outputs) + )"#, + ); +} + +#[test] +fn workspace_local_packages_fixture_checks_tests_and_records_graph_manifest() { + let workspace = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/workspaces/exp-5-local"); + let manifest = unique_path("workspace-artifact-manifest"); + + let check = run_glagol([ + "check".as_ref(), + "--manifest".as_ref(), + manifest.as_os_str(), + workspace.as_os_str(), + ]); + + assert_success("workspace check", &check); + let artifact = fs::read_to_string(&manifest).expect("read workspace artifact manifest"); + assert_project_block_snapshot( + "workspace check manifest", + &artifact, + &workspace, + r#" (project + (project_manifest "$PROJECT/slovo.toml") + (project_root "$PROJECT") + (source_root "") + (project_name "exp-5-local") + (entry_module "main") + (workspace + (workspace_root "$PROJECT") + (workspace_manifest "$PROJECT/slovo.toml") + (members + (member "packages/app") + (member "packages/mathlib") + ) + (packages + (package + (name "mathlib") + (version "0.1.0") + (root "$PROJECT/packages/mathlib") + (manifest "$PROJECT/packages/mathlib/slovo.toml") + (source_root "src") + (entry "main") + (test_count 1) + (modules + (module + (name "math") + (path "$PROJECT/packages/mathlib/src/math.slo") + (imports) + ) + ) + ) + (package + (name "app") + (version "0.1.0") + (root "$PROJECT/packages/app") + (manifest "$PROJECT/packages/app/slovo.toml") + (source_root "src") + (entry "main") + (test_count 1) + (modules + (module + (name "main") + (path "$PROJECT/packages/app/src/main.slo") + (imports + (import "mathlib.math") + ) + ) + ) + ) + ) + (package_dependency_edges + (package_dependency + (from "app") + (to "mathlib") + (kind "local-path") + (path "packages/mathlib") + ) + ) + (selected_build_entry_package) + ) + (modules + (module + (name "mathlib.math") + (path "$PROJECT/packages/mathlib/src/math.slo") + (imports) + ) + (module + (name "app.main") + (path "$PROJECT/packages/app/src/main.slo") + (imports + (import "mathlib.math") + ) + ) + ) + (import_edges + (import_edge + (from "app.main") + (to "mathlib.math") + ) + ) + (diagnostics_count 0) + (diagnostic_artifacts) + (build_outputs) + )"#, + ); + + let test = run_glagol(["test".as_ref(), workspace.as_os_str()]); + assert_success_stdout( + "workspace test", + test, + "test \"mathlib add one\" ... ok\ntest \"package import add one\" ... ok\n2 test(s) passed\n", + ); +} + +#[test] +fn workspace_imports_exported_enum_values_across_packages() { + let workspace = write_workspace( + "workspace-enum-imports", + "[workspace]\nmembers = [\"packages/app\", \"packages/readings\"]\n", + &[ + WorkspacePackageSpec { + member: "packages/readings", + manifest: "[package]\nname = \"readings\"\nversion = \"0.1.0\"\n", + modules: &[( + "readings", + "(module readings (export Reading make score))\n\n\ +(enum Reading\n Missing\n (Value i32))\n\n\ +(fn make ((value i32)) -> Reading\n (Reading.Value value))\n\n\ +(fn score ((reading Reading)) -> i32\n (match reading\n ((Reading.Missing)\n 0)\n ((Reading.Value payload)\n payload)))\n", + )], + }, + WorkspacePackageSpec { + member: "packages/app", + manifest: "[package]\nname = \"app\"\nversion = \"0.1.0\"\n\n[dependencies]\nreadings = { path = \"../readings\" }\n", + modules: &[( + "main", + "(module main)\n\n\ +(import readings.readings (Reading make score))\n\n\ +(fn main () -> i32\n (let reading Reading (make 41))\n (score reading))\n\n\ +(test \"workspace enum import\"\n (= (Reading.Value 41) (make 41)))\n", + )], + }, + ], + ); + + let check = run_glagol(["check".as_ref(), workspace.as_os_str()]); + assert_success_stdout("workspace enum import check", check, ""); + + let test = run_glagol(["test".as_ref(), workspace.as_os_str()]); + assert_success_stdout( + "workspace enum import test", + test, + "test \"workspace enum import\" ... ok\n1 test(s) passed\n", + ); +} + +#[test] +fn workspace_imports_exported_direct_i64_enum_values_across_packages() { + let workspace = write_workspace( + "workspace-direct-i64-enum-imports", + "[workspace]\nmembers = [\"packages/app\", \"packages/readings\"]\n", + &[ + WorkspacePackageSpec { + member: "packages/readings", + manifest: "[package]\nname = \"readings\"\nversion = \"0.1.0\"\n", + modules: &[( + "readings", + "(module readings (export WideReading make score))\n\n\ +(enum WideReading\n Missing\n (Value i64))\n\n\ +(fn make ((value i64)) -> WideReading\n (WideReading.Value value))\n\n\ +(fn score ((reading WideReading)) -> i64\n (match reading\n ((WideReading.Missing)\n 0i64)\n ((WideReading.Value payload)\n payload)))\n", + )], + }, + WorkspacePackageSpec { + member: "packages/app", + manifest: "[package]\nname = \"app\"\nversion = \"0.1.0\"\n\n[dependencies]\nreadings = { path = \"../readings\" }\n", + modules: &[( + "main", + "(module main)\n\n\ +(import readings.readings (WideReading make score))\n\n\ +(fn main () -> i32\n (if (= (score (make 4294967296i64)) 4294967296i64) 0 1))\n\n\ +(test \"workspace i64 enum import\"\n (= (WideReading.Value 4294967296i64) (make 4294967296i64)))\n", + )], + }, + ], + ); + + let check = run_glagol(["check".as_ref(), workspace.as_os_str()]); + assert_success_stdout("workspace i64 enum import check", check, ""); + + let test = run_glagol(["test".as_ref(), workspace.as_os_str()]); + assert_success_stdout( + "workspace i64 enum import test", + test, + "test \"workspace i64 enum import\" ... ok\n1 test(s) passed\n", + ); +} + +#[test] +fn workspace_build_smoke_uses_single_entry_package_when_host_toolchain_is_available() { + let workspace = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/workspaces/exp-5-local"); + let binary = unique_path("workspace-build-bin"); + + let output = run_glagol([ + "build".as_ref(), + "-o".as_ref(), + binary.as_os_str(), + workspace.as_os_str(), + ]); + + if output.status.code() == Some(3) { + assert_stderr_contains("workspace build toolchain", &output, "ToolchainUnavailable"); + return; + } + + assert_success("workspace build", &output); + let run = Command::new(&binary) + .output() + .expect("run workspace build output"); + assert_success("workspace build binary", &run); + assert_eq!( + String::from_utf8_lossy(&run.stdout), + "42\n", + "workspace build binary stdout mismatch" + ); +} + +#[test] +fn workspace_build_requires_one_entry_package() { + let workspace = write_workspace( + "workspace-ambiguous-build-entry", + "[workspace]\nmembers = [\"packages/app\", \"packages/tool\"]\n", + &[ + WorkspacePackageSpec { + member: "packages/app", + manifest: "[package]\nname = \"app\"\nversion = \"0.1.0\"\n", + modules: &[("main", "(module main)\n\n(fn main () -> i32\n 0)\n")], + }, + WorkspacePackageSpec { + member: "packages/tool", + manifest: "[package]\nname = \"tool\"\nversion = \"0.1.0\"\n", + modules: &[("main", "(module main)\n\n(fn main () -> i32\n 0)\n")], + }, + ], + ); + let binary = unique_path("workspace-ambiguous-build-bin"); + + let output = run_glagol([ + "build".as_ref(), + "-o".as_ref(), + binary.as_os_str(), + workspace.as_os_str(), + ]); + + assert_exit_code("workspace ambiguous entry", &output, 1); + assert_stderr_contains( + "workspace ambiguous entry", + &output, + "WorkspaceBuildAmbiguousEntryPackage", + ); +} + +#[test] +fn workspace_package_boundaries_are_diagnostics() { + let missing = write_workspace( + "workspace-missing-dependency", + "[workspace]\nmembers = [\"packages/app\"]\n", + &[WorkspacePackageSpec { + member: "packages/app", + manifest: "[package]\nname = \"app\"\nversion = \"0.1.0\"\n\n[dependencies]\nmathlib = { path = \"../mathlib\" }\n", + modules: &[("main", "(module main)\n\n(fn main () -> i32\n 0)\n")], + }], + ); + let missing_output = run_glagol(["check".as_ref(), missing.as_os_str()]); + assert_exit_code("missing package dependency", &missing_output, 1); + assert_stderr_contains( + "missing package dependency", + &missing_output, + "MissingPackageDependency", + ); + + let duplicate = write_workspace( + "workspace-duplicate-package", + "[workspace]\nmembers = [\"packages/app\", \"packages/dup\"]\n", + &[ + WorkspacePackageSpec { + member: "packages/app", + manifest: "[package]\nname = \"app\"\nversion = \"0.1.0\"\n", + modules: &[("main", "(module main)\n\n(fn main () -> i32\n 0)\n")], + }, + WorkspacePackageSpec { + member: "packages/dup", + manifest: "[package]\nname = \"app\"\nversion = \"0.1.0\"\n", + modules: &[("main", "(module main)\n\n(fn main () -> i32\n 0)\n")], + }, + ], + ); + let duplicate_output = run_glagol(["check".as_ref(), duplicate.as_os_str()]); + assert_exit_code("duplicate package name", &duplicate_output, 1); + assert_stderr_contains( + "duplicate package name", + &duplicate_output, + "DuplicatePackageName", + ); + + let cycle = write_workspace( + "workspace-package-cycle", + "[workspace]\nmembers = [\"packages/app\", \"packages/mathlib\"]\n", + &[ + WorkspacePackageSpec { + member: "packages/app", + manifest: "[package]\nname = \"app\"\nversion = \"0.1.0\"\n\n[dependencies]\nmathlib = { path = \"../mathlib\" }\n", + modules: &[("main", "(module main)\n\n(fn main () -> i32\n 0)\n")], + }, + WorkspacePackageSpec { + member: "packages/mathlib", + manifest: "[package]\nname = \"mathlib\"\nversion = \"0.1.0\"\n\n[dependencies]\napp = { path = \"../app\" }\n", + modules: &[("math", "(module math)\n\n(fn value () -> i32\n 1)\n")], + }, + ], + ); + let cycle_output = run_glagol(["check".as_ref(), cycle.as_os_str()]); + assert_exit_code("package dependency cycle", &cycle_output, 1); + assert_stderr_contains( + "package dependency cycle", + &cycle_output, + "PackageDependencyCycle", + ); + + let private = write_workspace( + "workspace-private-visibility", + "[workspace]\nmembers = [\"packages/app\", \"packages/mathlib\"]\n", + &[ + WorkspacePackageSpec { + member: "packages/app", + manifest: "[package]\nname = \"app\"\nversion = \"0.1.0\"\n\n[dependencies]\nmathlib = { path = \"../mathlib\" }\n", + modules: &[( + "main", + "(module main)\n\n(import mathlib.math (add_one))\n\n(fn main () -> i32\n (add_one 1))\n", + )], + }, + WorkspacePackageSpec { + member: "packages/mathlib", + manifest: "[package]\nname = \"mathlib\"\nversion = \"0.1.0\"\n", + modules: &[( + "math", + "(module math)\n\n(fn add_one ((value i32)) -> i32\n (+ value 1))\n", + )], + }, + ], + ); + let private_output = run_glagol(["check".as_ref(), private.as_os_str()]); + assert_exit_code("private package visibility", &private_output, 1); + assert_stderr_contains("private package visibility", &private_output, "Visibility"); + + let invalid = write_workspace( + "workspace-invalid-package", + "[workspace]\nmembers = [\"packages/app\"]\n", + &[WorkspacePackageSpec { + member: "packages/app", + manifest: "[package]\nname = \"App\"\nversion = \"0.1\"\n", + modules: &[("main", "(module main)\n\n(fn main () -> i32\n 0)\n")], + }], + ); + let invalid_output = run_glagol(["check".as_ref(), invalid.as_os_str()]); + assert_exit_code("invalid package metadata", &invalid_output, 1); + assert_stderr_contains( + "invalid package name", + &invalid_output, + "InvalidPackageName", + ); + assert_stderr_contains( + "invalid package version", + &invalid_output, + "InvalidPackageVersion", + ); + + let escape = write_workspace( + "workspace-path-escape", + "[workspace]\nmembers = [\"../outside\"]\n", + &[], + ); + let escape_output = run_glagol(["check".as_ref(), escape.as_os_str()]); + assert_exit_code("workspace member path escape", &escape_output, 1); + assert_stderr_contains( + "workspace member path escape", + &escape_output, + "WorkspaceMemberPathEscape", + ); +} + +#[test] +fn single_file_input_keeps_v1_2_behavior_even_next_to_manifest() { + let project = write_project( + "single-file", + &[( + "math", + "(module math (export add_one))\n\n(fn add_one ((value i32)) -> i32\n (+ value 1))\n", + )], + "(module main)\n\n(import math (add_one))\n\n(fn main () -> i32\n (add_one 41))\n", + ); + let single = project.join("src/main.slo"); + + let output = run_glagol(["check".as_ref(), single.as_os_str()]); + + assert_exit_code("single file project source", &output, 1); + assert_stderr_contains("single file remains v1.2", &output, "UnknownTopLevelForm"); +} + +#[test] +fn legacy_run_tests_flag_does_not_enter_project_mode() { + let project = write_project( + "legacy-run-tests", + &[], + "(module main)\n\n(test \"ok\"\n true)\n", + ); + + let output = run_glagol(["--run-tests".as_ref(), project.as_os_str()]); + + assert_exit_code("legacy --run-tests project", &output, 1); + assert_stderr_contains("legacy --run-tests project", &output, "InputReadFailed"); +} + +#[test] +fn project_check_and_test_do_not_require_entry_main_function() { + let project = write_project( + "check-test-no-main", + &[], + "(module main)\n\n(test \"ok\"\n true)\n", + ); + let check = run_glagol(["check".as_ref(), project.as_os_str()]); + let test = run_glagol(["test".as_ref(), project.as_os_str()]); + + assert_success_stdout("check without entry main", check, ""); + assert_success_stdout( + "test without entry main", + test, + "test \"ok\" ... ok\n1 test(s) passed\n", + ); +} + +#[test] +fn diagnostic_uses_source_file_for_failed_project_test() { + let project = write_project( + "source-span", + &[], + "(module main)\n\n(fn main () -> i32\n 0)\n\n(test \"fails\"\n false)\n", + ); + let output = run_glagol(["test".as_ref(), project.as_os_str()]); + let main_file = project.join("src/main.slo").display().to_string(); + + assert_exit_code("failed project test", &output, 1); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + stderr.contains("TestFailed") && stderr.contains(&main_file), + "diagnostic did not reference source file `{}`:\n{}", + main_file, + stderr + ); +} + +#[test] +fn failure_manifest_records_project_block_and_diagnostics_artifact() { + let project = write_project( + "failure-manifest", + &[], + "(module main)\n\n(import math (add_one))\n\n(fn main () -> i32\n (add_one 41))\n", + ); + let manifest = unique_path("failure-artifact-manifest"); + + let output = run_glagol([ + "check".as_ref(), + "--manifest".as_ref(), + manifest.as_os_str(), + project.as_os_str(), + ]); + + assert_exit_code("failure project manifest check", &output, 1); + let artifact = fs::read_to_string(&manifest).expect("read failure artifact manifest"); + assert_project_block_snapshot( + "failure check project manifest", + &artifact, + &project, + r#" (project + (project_manifest "$PROJECT/slovo.toml") + (project_root "$PROJECT") + (source_root "src") + (project_name "failure-manifest") + (entry_module "main") + (modules + (module + (name "main") + (path "$PROJECT/src/main.slo") + (imports + (import "math") + ) + ) + ) + (import_edges + (import_edge + (from "main") + (to "math") + ) + ) + (diagnostics_count 1) + (diagnostic_artifacts + (artifact + (kind diagnostics) + (stream stderr) + ) + ) + (build_outputs) + )"#, + ); +} + +#[test] +fn missing_import_module_source_root_and_manifest_are_diagnostics() { + let project = write_project( + "missing-import", + &[], + "(module main)\n\n(import math (add_one))\n\n(fn main () -> i32\n (add_one 41))\n", + ); + let missing_import = run_glagol(["check".as_ref(), project.as_os_str()]); + assert_exit_code("missing import", &missing_import, 1); + assert_stderr_contains("missing import", &missing_import, "MissingImport"); + + let missing_root = write_project_with_manifest( + "missing-root", + "[project]\nname = \"missing-root\"\nsource_root = \"missing\"\nentry = \"main\"\n", + &[], + "", + ); + let missing_root_output = run_glagol(["check".as_ref(), missing_root.as_os_str()]); + assert_exit_code("missing source root", &missing_root_output, 1); + assert_stderr_contains( + "missing source root", + &missing_root_output, + "ProjectSourceRootMissing", + ); + + let bad_manifest = unique_path("bad-manifest"); + fs::create_dir_all(&bad_manifest).expect("create bad manifest project"); + fs::write( + bad_manifest.join("slovo.toml"), + "[project]\nentry = \"main\"\n", + ) + .expect("write bad manifest"); + let bad_manifest_output = run_glagol(["check".as_ref(), bad_manifest.as_os_str()]); + assert_exit_code("bad manifest", &bad_manifest_output, 1); + assert_stderr_contains( + "bad manifest", + &bad_manifest_output, + "ProjectManifestInvalid", + ); +} + +#[test] +fn invalid_manifest_boundaries_are_diagnostics() { + let cases = [ + ( + "missing-project-section", + "name = \"missing-section\"\n", + "project manifest is missing `[project]` section", + ), + ( + "invalid-name", + "[project]\nname = \"Bad_Name\"\n", + "project name must be lowercase ASCII", + ), + ( + "invalid-entry", + "[project]\nname = \"invalid-entry\"\nentry = \"../main\"\n", + "project entry must be a flat module identifier", + ), + ( + "source-root-parent", + "[project]\nname = \"source-root-parent\"\nsource_root = \"../src\"\n", + "project source_root must be a relative path", + ), + ( + "source-root-absolute", + "[project]\nname = \"source-root-absolute\"\nsource_root = \"/tmp\"\n", + "project source_root must be a relative path", + ), + ( + "generated-source", + "[project]\nname = \"generated-source\"\nsource_root = \"generated-source\"\n", + "project source_root must be a relative path", + ), + ]; + + for (name, manifest, expected) in cases { + let project = write_project_with_manifest(name, manifest, &[], ""); + let output = run_glagol(["check".as_ref(), project.as_os_str()]); + assert_exit_code(name, &output, 1); + assert_stderr_contains(name, &output, "ProjectManifestInvalid"); + assert_stderr_contains(name, &output, expected); + } +} + +#[cfg(unix)] +#[test] +fn symlink_source_root_and_module_file_escapes_are_diagnostics() { + use std::os::unix::fs::symlink; + + let root_escape = unique_path("symlink-root-escape"); + let outside_root = unique_path("symlink-root-outside"); + fs::create_dir_all(&root_escape).expect("create symlink project"); + fs::create_dir_all(&outside_root).expect("create symlink outside root"); + fs::write( + root_escape.join("slovo.toml"), + "[project]\nname = \"symlink-root-escape\"\nsource_root = \"src\"\nentry = \"main\"\n", + ) + .expect("write symlink manifest"); + symlink(&outside_root, root_escape.join("src")).expect("symlink source root"); + let root_output = run_glagol(["check".as_ref(), root_escape.as_os_str()]); + assert_exit_code("symlink source root escape", &root_output, 1); + assert_stderr_contains( + "symlink source root escape", + &root_output, + "ProjectManifestInvalid", + ); + assert_stderr_contains( + "symlink source root escape", + &root_output, + "source_root must not escape", + ); + + let module_escape = unique_path("symlink-module-escape"); + let outside_module = unique_path("symlink-module-outside"); + fs::create_dir_all(module_escape.join("src")).expect("create module escape src"); + fs::create_dir_all(&outside_module).expect("create module outside dir"); + fs::write( + module_escape.join("slovo.toml"), + "[project]\nname = \"symlink-module-escape\"\nsource_root = \"src\"\nentry = \"main\"\n", + ) + .expect("write module escape manifest"); + let outside_main = outside_module.join("main.slo"); + fs::write(&outside_main, "(module main)\n\n(fn main () -> i32\n 0)\n") + .expect("write outside module"); + symlink(outside_main, module_escape.join("src/main.slo")).expect("symlink module file"); + let module_output = run_glagol(["check".as_ref(), module_escape.as_os_str()]); + assert_exit_code("symlink module escape", &module_output, 1); + assert_stderr_contains( + "symlink module escape", + &module_output, + "ProjectManifestInvalid", + ); + assert_stderr_contains( + "symlink module escape", + &module_output, + "escapes the source root", + ); +} + +#[test] +fn duplicate_visibility_ambiguous_and_import_cycle_are_diagnostics() { + let duplicate = write_project( + "duplicate", + &[("math", "(module math (export add_one))\n\n(fn add_one ((value i32)) -> i32\n (+ value 1))\n")], + "(module main)\n\n(import math (add_one))\n\n(fn add_one ((value i32)) -> i32\n value)\n\n(fn main () -> i32\n (add_one 41))\n", + ); + let duplicate_output = run_glagol(["check".as_ref(), duplicate.as_os_str()]); + assert_exit_code("duplicate", &duplicate_output, 1); + assert_stderr_contains("duplicate", &duplicate_output, "DuplicateName"); + + let visibility = write_project( + "visibility", + &[("math", "(module math)\n\n(fn hidden () -> i32\n 1)\n")], + "(module main)\n\n(import math (hidden))\n\n(fn main () -> i32\n (hidden))\n", + ); + let visibility_output = run_glagol(["check".as_ref(), visibility.as_os_str()]); + assert_exit_code("visibility", &visibility_output, 1); + assert_stderr_contains("visibility", &visibility_output, "Visibility"); + + let ambiguous = write_project( + "ambiguous", + &[ + ("left", "(module left (export value))\n\n(fn value () -> i32\n 1)\n"), + ("right", "(module right (export value))\n\n(fn value () -> i32\n 2)\n"), + ], + "(module main)\n\n(import left (value))\n(import right (value))\n\n(fn main () -> i32\n (value))\n", + ); + let ambiguous_output = run_glagol(["check".as_ref(), ambiguous.as_os_str()]); + assert_exit_code("ambiguous", &ambiguous_output, 1); + assert_stderr_contains("ambiguous", &ambiguous_output, "AmbiguousName"); + + let cycle = write_project( + "cycle", + &[ + ( + "a", + "(module a (export fa))\n\n(import b (fb))\n\n(fn fa () -> i32\n (fb))\n", + ), + ( + "b", + "(module b (export fb))\n\n(import a (fa))\n\n(fn fb () -> i32\n (fa))\n", + ), + ], + "(module main)\n\n(import a (fa))\n\n(fn main () -> i32\n (fa))\n", + ); + let cycle_output = run_glagol(["check".as_ref(), cycle.as_os_str()]); + assert_exit_code("cycle", &cycle_output, 1); + assert_stderr_contains("cycle", &cycle_output, "ImportCycle"); + assert_stderr_contains( + "cycle related edge", + &cycle_output, + "module `a` imports `b`", + ); +} + +#[test] +fn enum_import_visibility_and_duplicate_cases_are_diagnostics() { + let private = write_project( + "enum-private-import", + &[( + "readings", + "(module readings)\n\n(enum Reading Missing (Value i32))\n", + )], + "(module main)\n\n(import readings (Reading))\n", + ); + let private_output = run_glagol(["check".as_ref(), private.as_os_str()]); + assert_exit_code("private enum import", &private_output, 1); + assert_stderr_contains("private enum import", &private_output, "Visibility"); + assert_stderr_contains( + "private enum related span", + &private_output, + "private declaration", + ); + + let missing = write_project( + "enum-missing-import", + &[( + "readings", + "(module readings)\n\n(fn main () -> i32\n 0)\n", + )], + "(module main)\n\n(import readings (Reading))\n", + ); + let missing_output = run_glagol(["check".as_ref(), missing.as_os_str()]); + assert_exit_code("missing enum import", &missing_output, 1); + assert_stderr_contains("missing enum import", &missing_output, "MissingImport"); + assert_stderr_contains( + "missing enum import message", + &missing_output, + "function, struct, or enum", + ); + + let duplicate = write_project( + "enum-duplicate-import", + &[( + "readings", + "(module readings (export Reading))\n\n(enum Reading Missing)\n", + )], + "(module main)\n\n(import readings (Reading))\n\n(enum Reading Local)\n", + ); + let duplicate_output = run_glagol(["check".as_ref(), duplicate.as_os_str()]); + assert_exit_code("duplicate enum import", &duplicate_output, 1); + assert_stderr_contains("duplicate enum import", &duplicate_output, "DuplicateName"); + + let invalid_export = write_project( + "enum-invalid-export", + &[("readings", "(module readings (export Reading))\n")], + "(module main)\n", + ); + let invalid_export_output = run_glagol(["check".as_ref(), invalid_export.as_os_str()]); + assert_exit_code("invalid enum export", &invalid_export_output, 1); + assert_stderr_contains("invalid enum export", &invalid_export_output, "Visibility"); + assert_stderr_contains( + "invalid enum export message", + &invalid_export_output, + "local function, struct, or enum", + ); +} + +#[test] +fn project_diagnostic_families_have_json_golden_coverage() { + let duplicate = write_project( + "json-duplicate", + &[( + "math", + "(module math (export add_one))\n\n(fn add_one ((value i32)) -> i32\n (+ value 1))\n", + )], + "(module main)\n\n(import math (add_one))\n\n(fn add_one ((value i32)) -> i32\n value)\n", + ); + assert_json_diagnostic_code("duplicate json", &duplicate, "DuplicateName"); + + let missing = write_project( + "json-missing", + &[], + "(module main)\n\n(import math (add_one))\n", + ); + assert_json_diagnostic_code("missing json", &missing, "MissingImport"); + + let visibility = write_project( + "json-visibility", + &[("math", "(module math)\n\n(fn hidden () -> i32\n 1)\n")], + "(module main)\n\n(import math (hidden))\n", + ); + assert_json_diagnostic_code("visibility json", &visibility, "Visibility"); + + let ambiguous = write_project( + "json-ambiguous", + &[ + ( + "left", + "(module left (export value))\n\n(fn value () -> i32\n 1)\n", + ), + ( + "right", + "(module right (export value))\n\n(fn value () -> i32\n 2)\n", + ), + ], + "(module main)\n\n(import left (value))\n(import right (value))\n", + ); + assert_json_diagnostic_code("ambiguous json", &ambiguous, "AmbiguousName"); + + let cycle = write_project( + "json-cycle", + &[ + ( + "a", + "(module a (export fa))\n\n(import b (fb))\n\n(fn fa () -> i32\n (fb))\n", + ), + ( + "b", + "(module b (export fb))\n\n(import a (fa))\n\n(fn fb () -> i32\n (fa))\n", + ), + ], + "(module main)\n\n(import a (fa))\n", + ); + assert_json_diagnostic_code("cycle json", &cycle, "ImportCycle"); +} + +#[test] +fn cross_file_related_spans_render_file_identity_in_text_sexpr_and_json() { + let visibility = write_project( + "related-visibility", + &[("math", "(module math)\n\n(fn hidden () -> i32\n 1)\n")], + "(module main)\n\n(import math (hidden))\n", + ); + let text = run_glagol(["check".as_ref(), visibility.as_os_str()]); + assert_exit_code("visibility related text", &text, 1); + let stderr = String::from_utf8_lossy(&text.stderr); + let math_file = visibility.join("src/math.slo").display().to_string(); + let main_file = visibility.join("src/main.slo").display().to_string(); + assert!( + stderr.contains(&format!("(file {})", sexpr_string(&main_file))) + && stderr.contains(&format!("(file {})", sexpr_string(&math_file))) + && stderr.contains("private declaration"), + "visibility diagnostic did not carry cross-file related span:\n{}", + stderr + ); + + let json = run_glagol([ + "check".as_ref(), + "--json-diagnostics".as_ref(), + visibility.as_os_str(), + ]); + assert_exit_code("visibility related json", &json, 1); + let stderr = String::from_utf8_lossy(&json.stderr); + assert!( + stderr.contains(&format!("\"file\":{}", json_string(&main_file))) + && stderr.contains(&format!("\"file\":{}", json_string(&math_file))) + && stderr.contains("\"related\"") + && stderr.contains("private declaration"), + "visibility JSON diagnostic did not carry cross-file related span:\n{}", + stderr + ); +} + +#[test] +fn project_build_smoke_uses_entry_main_when_host_toolchain_is_available() { + let project = write_project( + "build", + &[("math", "(module math (export main add_one))\n\n(fn main () -> i32\n 9)\n\n(fn add_one ((value i32)) -> i32\n (+ value 1))\n")], + "(module main)\n\n(import math (add_one))\n\n(fn main () -> i32\n (print_i32 (add_one 41))\n 0)\n", + ); + let binary = unique_path("project-build-bin"); + + let output = run_glagol([ + "build".as_ref(), + "-o".as_ref(), + binary.as_os_str(), + project.as_os_str(), + ]); + + if output.status.code() == Some(3) { + assert_stderr_contains("project build toolchain", &output, "ToolchainUnavailable"); + return; + } + + assert_success("project build", &output); + let run = Command::new(&binary) + .output() + .expect("run project build output"); + assert_success("project build binary", &run); + assert_eq!( + String::from_utf8_lossy(&run.stdout), + "42\n", + "project build binary stdout mismatch" + ); +} + +#[test] +fn project_build_requires_entry_main_contract() { + let missing_main = write_project( + "missing-main", + &[], + "(module main)\n\n(test \"ok\"\n true)\n", + ); + let missing_binary = unique_path("missing-main-bin"); + let missing_output = run_glagol([ + "build".as_ref(), + "-o".as_ref(), + missing_binary.as_os_str(), + missing_main.as_os_str(), + ]); + assert_exit_code("missing entry main", &missing_output, 1); + assert_stderr_contains("missing entry main", &missing_output, "MissingImport"); + + let bad_signature = write_project( + "bad-main-signature", + &[], + "(module main)\n\n(fn main ((value i32)) -> i32\n value)\n", + ); + let bad_binary = unique_path("bad-main-signature-bin"); + let bad_output = run_glagol([ + "build".as_ref(), + "-o".as_ref(), + bad_binary.as_os_str(), + bad_signature.as_os_str(), + ]); + assert_exit_code("bad entry main signature", &bad_output, 1); + assert_stderr_contains("bad entry main signature", &bad_output, "MissingImport"); +} + +#[test] +fn module_mismatch_and_reserved_intrinsics_are_diagnostics() { + let mismatch = write_project_with_manifest( + "module-mismatch", + "[project]\nname = \"module-mismatch\"\nsource_root = \"src\"\nentry = \"main\"\n", + &[("main", "(module wrong)\n\n(fn main () -> i32\n 0)\n")], + "", + ); + let mismatch_output = run_glagol(["check".as_ref(), mismatch.as_os_str()]); + assert_exit_code("module mismatch", &mismatch_output, 1); + assert_stderr_contains("module mismatch", &mismatch_output, "MissingImport"); + + let local_reserved = write_project( + "local-reserved", + &[], + "(module main)\n\n(fn print_i32 () -> i32\n 0)\n", + ); + let local_output = run_glagol(["check".as_ref(), local_reserved.as_os_str()]); + assert_exit_code("local reserved", &local_output, 1); + assert_stderr_contains("local reserved", &local_output, "DuplicateName"); + + let export_reserved = write_project( + "export-reserved", + &[], + "(module main (export string_len))\n\n(fn main () -> i32\n 0)\n", + ); + let export_output = run_glagol(["check".as_ref(), export_reserved.as_os_str()]); + assert_exit_code("export reserved", &export_output, 1); + assert_stderr_contains("export reserved", &export_output, "Visibility"); + + let import_reserved = write_project( + "import-reserved", + &[("math", "(module math)\n\n(fn main () -> i32\n 0)\n")], + "(module main)\n\n(import math (print_i32))\n", + ); + let import_output = run_glagol(["check".as_ref(), import_reserved.as_os_str()]); + assert_exit_code("import reserved", &import_output, 1); + assert_stderr_contains("import reserved", &import_output, "Visibility"); + + let local_promoted_reserved = write_project( + "local-promoted-reserved", + &[], + "(module main)\n\n(fn std.io.print_i32 () -> i32\n 0)\n", + ); + let local_promoted_output = run_glagol(["check".as_ref(), local_promoted_reserved.as_os_str()]); + assert_exit_code("local promoted reserved", &local_promoted_output, 1); + assert_stderr_contains( + "local promoted reserved", + &local_promoted_output, + "DuplicateName", + ); + + let local_private_runtime_reserved = write_project( + "local-private-runtime-reserved", + &[], + "(module main)\n\n(fn __glagol_string_concat ((left string) (right string)) -> string\n \"intercepted\")\n", + ); + let local_private_runtime_output = + run_glagol(["check".as_ref(), local_private_runtime_reserved.as_os_str()]); + assert_exit_code( + "local private runtime reserved", + &local_private_runtime_output, + 1, + ); + assert_stderr_contains( + "local private runtime reserved", + &local_private_runtime_output, + "DuplicateName", + ); + + let struct_promoted_reserved = write_project( + "struct-promoted-reserved", + &[], + "(module main)\n\n(struct std.string.len (value i32))\n\n(fn main () -> i32\n 0)\n", + ); + let struct_promoted_output = + run_glagol(["check".as_ref(), struct_promoted_reserved.as_os_str()]); + assert_exit_code("struct promoted reserved", &struct_promoted_output, 1); + assert_stderr_contains( + "struct promoted reserved", + &struct_promoted_output, + "DuplicateName", + ); + + let export_promoted_reserved = write_project( + "export-promoted-reserved", + &[], + "(module main (export std.string.len))\n\n(fn main () -> i32\n 0)\n", + ); + let export_promoted_output = + run_glagol(["check".as_ref(), export_promoted_reserved.as_os_str()]); + assert_exit_code("export promoted reserved", &export_promoted_output, 1); + assert_stderr_contains( + "export promoted reserved", + &export_promoted_output, + "Visibility", + ); + + let import_promoted_reserved = write_project( + "import-promoted-reserved", + &[("math", "(module math)\n\n(fn main () -> i32\n 0)\n")], + "(module main)\n\n(import math (std.io.print_i32))\n", + ); + let import_promoted_output = + run_glagol(["check".as_ref(), import_promoted_reserved.as_os_str()]); + assert_exit_code("import promoted reserved", &import_promoted_output, 1); + assert_stderr_contains( + "import promoted reserved", + &import_promoted_output, + "Visibility", + ); +} + +#[test] +fn unsafe_operation_heads_are_reserved_in_project_visibility() { + for head in UNSAFE_HEADS { + let project_head = head.replace('_', "-"); + let local_reserved = write_project( + &format!("unsafe-local-reserved-{}", project_head), + &[], + &format!("(module main)\n\n(fn {} () -> i32\n 0)\n", head), + ); + let local_output = run_glagol(["check".as_ref(), local_reserved.as_os_str()]); + assert_exit_code("unsafe local reserved", &local_output, 1); + assert_stderr_contains("unsafe local reserved", &local_output, "DuplicateName"); + + let export_reserved = write_project( + &format!("unsafe-export-reserved-{}", project_head), + &[], + &format!( + "(module main (export {}))\n\n(fn main () -> i32\n 0)\n", + head + ), + ); + let export_output = run_glagol(["check".as_ref(), export_reserved.as_os_str()]); + assert_exit_code("unsafe export reserved", &export_output, 1); + assert_stderr_contains("unsafe export reserved", &export_output, "Visibility"); + + let import_reserved = write_project( + &format!("unsafe-import-reserved-{}", project_head), + &[("math", "(module math)\n\n(fn main () -> i32\n 0)\n")], + &format!("(module main)\n\n(import math ({}))\n", head), + ); + let import_output = run_glagol(["check".as_ref(), import_reserved.as_os_str()]); + assert_exit_code("unsafe import reserved", &import_output, 1); + assert_stderr_contains("unsafe import reserved", &import_output, "Visibility"); + } +} + +fn write_project(name: &str, modules: &[(&str, &str)], main: &str) -> PathBuf { + write_project_with_manifest( + name, + &format!( + "[project]\nname = \"{}\"\nsource_root = \"src\"\nentry = \"main\"\n", + name + ), + modules, + main, + ) +} + +fn write_project_with_manifest( + name: &str, + manifest: &str, + modules: &[(&str, &str)], + main: &str, +) -> PathBuf { + let root = unique_path(name); + let src = root.join("src"); + fs::create_dir_all(&src).expect("create project src"); + fs::write(root.join("slovo.toml"), manifest).expect("write manifest"); + if !main.is_empty() { + fs::write(src.join("main.slo"), main).expect("write main module"); + } + for (module, source) in modules { + fs::write(src.join(format!("{}.slo", module)), source).expect("write module"); + } + root +} + +struct WorkspacePackageSpec<'a> { + member: &'a str, + manifest: &'a str, + modules: &'a [(&'a str, &'a str)], +} + +fn write_workspace( + name: &str, + workspace_manifest: &str, + packages: &[WorkspacePackageSpec<'_>], +) -> PathBuf { + let root = unique_path(name); + fs::create_dir_all(&root).expect("create workspace root"); + fs::write(root.join("slovo.toml"), workspace_manifest).expect("write workspace manifest"); + for package in packages { + let package_root = root.join(package.member); + let src = package_root.join("src"); + fs::create_dir_all(&src).expect("create package src"); + fs::write(package_root.join("slovo.toml"), package.manifest) + .expect("write package manifest"); + for (module, source) in package.modules { + fs::write(src.join(format!("{}.slo", module)), source).expect("write package module"); + } + } + root +} + +fn unique_path(name: &str) -> PathBuf { + let id = NEXT_PROJECT_ID.fetch_add(1, Ordering::SeqCst); + let nanos = SystemTime::now() + .duration_since(UNIX_EPOCH) + .map(|duration| duration.as_nanos()) + .unwrap_or(0); + std::env::temp_dir().join(format!( + "glagol-project-mode-{}-{}-{}-{}", + std::process::id(), + nanos, + id, + name + )) +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .output() + .expect("run glagol") +} + +fn assert_success(context: &str, output: &Output) { + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); +} + +fn assert_success_stdout(context: &str, output: Output, expected: &str) { + assert_success(context, &output); + assert_eq!( + String::from_utf8_lossy(&output.stdout), + expected, + "{} stdout mismatch", + context + ); + assert!( + output.stderr.is_empty(), + "{} wrote stderr:\n{}", + context, + String::from_utf8_lossy(&output.stderr) + ); +} + +fn assert_exit_code(context: &str, output: &Output, expected: i32) { + assert_eq!( + output.status.code(), + Some(expected), + "{} exit code mismatch\nstdout:\n{}\nstderr:\n{}", + context, + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); +} + +fn assert_stderr_contains(context: &str, output: &Output, needle: &str) { + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + stderr.contains(needle), + "{} stderr did not contain `{}`:\n{}", + context, + needle, + stderr + ); +} + +fn assert_json_diagnostic_code(context: &str, project: &Path, code: &str) { + let output = run_glagol([ + "check".as_ref(), + "--json-diagnostics".as_ref(), + project.as_os_str(), + ]); + assert_exit_code(context, &output, 1); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + stderr + .lines() + .any(|line| line.contains(&format!("\"code\":{}", json_string(code)))), + "{} JSON diagnostic did not contain code `{}`:\n{}", + context, + code, + stderr + ); +} + +fn assert_project_block_snapshot(context: &str, manifest: &str, project: &Path, expected: &str) { + let block = project_block(manifest); + let normalized = block.replace(&project.display().to_string(), "$PROJECT"); + assert_eq!( + normalized, expected, + "{} project manifest block mismatch\nfull manifest:\n{}", + context, manifest + ); +} + +fn project_block(manifest: &str) -> &str { + let start = manifest + .find(" (project\n") + .expect("manifest did not contain project block"); + manifest[start..] + .strip_suffix("\n)\n") + .expect("manifest did not end with artifact-manifest close") +} + +fn sexpr_string(value: &str) -> String { + let mut out = String::from("\""); + for ch in value.chars() { + match ch { + '\\' => out.push_str("\\\\"), + '"' => out.push_str("\\\""), + '\n' => out.push_str("\\n"), + '\r' => out.push_str("\\r"), + '\t' => out.push_str("\\t"), + ch => out.push(ch), + } + } + out.push('"'); + out +} + +fn json_string(value: &str) -> String { + let mut out = String::from("\""); + for ch in value.chars() { + match ch { + '\\' => out.push_str("\\\\"), + '"' => out.push_str("\\\""), + '\n' => out.push_str("\\n"), + '\r' => out.push_str("\\r"), + '\t' => out.push_str("\\t"), + '\u{08}' => out.push_str("\\b"), + '\u{0c}' => out.push_str("\\f"), + ch if ch <= '\u{1f}' => out.push_str(&format!("\\u{:04x}", ch as u32)), + ch => out.push(ch), + } + } + out.push('"'); + out +} + +#[allow(dead_code)] +fn _assert_path_exists(path: &Path) { + assert!(path.exists(), "path did not exist: {}", path.display()); +} diff --git a/compiler/tests/promotion_gate.rs b/compiler/tests/promotion_gate.rs new file mode 100644 index 0000000..c645350 --- /dev/null +++ b/compiler/tests/promotion_gate.rs @@ -0,0 +1,10237 @@ +use std::{ + ffi::OsStr, + fs, + path::{Path, PathBuf}, + process::{Command, Output}, +}; + +const DIAGNOSTIC_SNAPSHOTS: &[&str] = &[ + "array-index-out-of-bounds.diag", + "array-index-not-i32.diag", + "array-argument-type-mismatch.diag", + "array-local-initializer-type-mismatch.diag", + "array-return-type-mismatch.diag", + "arity-mismatch.diag", + "cyclic-struct-fields.diag", + "duplicate-local.diag", + "duplicate-match-arm.diag", + "duplicate-struct-constructor-field.diag", + "duplicate-struct-field.diag", + "duplicate-struct.diag", + "empty-array.diag", + "enum-constructor-arity.diag", + "enum-container-values.diag", + "enum-duplicate-variant.diag", + "enum-duplicate.diag", + "enum-empty.diag", + "enum-match-duplicate-arm.diag", + "enum-match-invalid-arm.diag", + "enum-match-non-exhaustive.diag", + "enum-match-payload-binding.diag", + "enum-ordering.diag", + "enum-payload-constructor-arity.diag", + "enum-payload-constructor-type.diag", + "enum-payload-recursive-struct.diag", + "enum-payload-struct-equality.diag", + "enum-payload-mixed-types.diag", + "enum-payload-match-missing-binding.diag", + "enum-payload-unsupported-type.diag", + "enum-printing.diag", + "enum-subject-mismatch.diag", + "enum-struct-fields-container.diag", + "enum-unknown-constructor.diag", + "enum-unknown-variant.diag", + "enum-unqualified-variant-constructor.diag", + "if-branch-type-mismatch.diag", + "if-condition-not-bool.diag", + "i64-literal-out-of-range.diag", + "index-on-non-array.diag", + "integer-out-of-range.diag", + "invalid-i64-literal.diag", + "invalid-leading-plus-i64-literal.diag", + "invalid-sign-only-i64-literal.diag", + "empty-struct.diag", + "empty-while-body.diag", + "local-redeclares-parameter.diag", + "local-shadows-function.diag", + "local-shadows-intrinsic.diag", + "match-arm-type-mismatch.diag", + "match-binding-collision.diag", + "match-subject-type-mismatch.diag", + "malformed-option-unwrap.diag", + "malformed-option-constructor.diag", + "malformed-if.diag", + "malformed-match-pattern.diag", + "malformed-result-err-unwrap.diag", + "malformed-result-ok-unwrap.diag", + "malformed-result-constructor.diag", + "malformed-unsafe-form.diag", + "malformed-while.diag", + "field-access-on-non-struct.diag", + "recursive-struct-field.diag", + "nested-local-declaration.diag", + "nested-while.diag", + "non-exhaustive-match.diag", + "option-constructor-type-mismatch.diag", + "option-unwrap-non-option.diag", + "print-bool-arity.diag", + "print-bool-type.diag", + "result-constructor-type-mismatch.diag", + "result-err-unwrap-non-result.diag", + "result-ok-unwrap-non-result.diag", + "set-immutable-local.diag", + "set-parameter.diag", + "single-file-main-i64-return.diag", + "set-type-mismatch.diag", + "set-unknown-local.diag", + "std-abi-layout-unsupported.diag", + "std-async-spawn-unsupported.diag", + "std-env-get-type.diag", + "std-env-get-result-type.diag", + "std-fs-read-binary-unsupported-alias.diag", + "std-fs-read-text-helper-shadow.diag", + "std-fs-list-dir-unsupported.diag", + "std-fs-write-text-arity.diag", + "std-fs-write-text-result-arity.diag", + "std-fs-write-text-result-type.diag", + "std-io-prompt-unsupported.diag", + "std-io-read-stdin-async-unsupported.diag", + "std-io-read-stdin-binary-unsupported.diag", + "std-io-read-stdin-bytes-unsupported.diag", + "std-io-read-stdin-result-arity.diag", + "std-io-read-stdin-result-bool-context.diag", + "std-io-read-stdin-result-helper-shadow.diag", + "std-io-read-stdin-result-name-shadow.diag", + "std-io-read-stdin-result-type.diag", + "std-io-read-line-unsupported.diag", + "std-io-read-stdin-unsupported.diag", + "std-io-stdin-encoding-unsupported.diag", + "std-io-stdin-lines-unsupported.diag", + "std-io-stdin-stream-unsupported.diag", + "std-io-eprint-arity.diag", + "std-io-eprint-result-context.diag", + "std-net-connect-unsupported.diag", + "std-num-cast-checked-unsupported.diag", + "std-num-cast-unsupported.diag", + "std-num-f64-to-i32-result-arity.diag", + "std-num-f64-to-i32-result-bool-context.diag", + "std-num-f64-to-i32-result-context.diag", + "std-num-f64-to-i32-result-name-shadow.diag", + "std-num-f64-to-i32-result-type.diag", + "std-num-f64-to-i32-unsupported.diag", + "std-num-f64-to-i64-result-arity.diag", + "std-num-f64-to-i64-result-bool-context.diag", + "std-num-f64-to-i64-result-context.diag", + "std-num-f64-to-i64-result-name-shadow.diag", + "std-num-f64-to-i64-result-type.diag", + "std-num-f64-to-i64-unsupported.diag", + "std-num-f64-to-string-arity.diag", + "std-num-f64-to-string-bool-context.diag", + "std-num-f64-to-string-context.diag", + "std-num-f64-to-string-helper-shadow.diag", + "std-num-f64-to-string-name-shadow.diag", + "std-num-f64-to-string-type.diag", + "std-num-i32-to-f64-type.diag", + "std-num-i32-to-i64-arity.diag", + "std-num-i32-to-i64-result-unsupported.diag", + "std-num-i32-to-string-arity.diag", + "std-num-i64-to-i32-result-arity.diag", + "std-num-i64-to-i32-result-type.diag", + "std-num-i64-to-i32-unsupported.diag", + "std-num-i64-to-string-type.diag", + "std-num-to-string-unsupported.diag", + "std-package-load-unsupported.diag", + "std-platform-os-unsupported.diag", + "std-print-bool-arity.diag", + "std-print-bool-result.diag", + "std-parameter-shadows-runtime.diag", + "std-process-arg-result-arity.diag", + "std-process-arg-result-helper-shadow.diag", + "std-process-arg-result-name-shadow.diag", + "std-process-arg-type.diag", + "std-random-bytes-unsupported.diag", + "std-random-crypto-i32-unsupported.diag", + "std-random-float-unsupported.diag", + "std-random-i32-arity.diag", + "std-random-i32-bool-context.diag", + "std-random-i32-helper-shadow.diag", + "std-random-i32-name-shadow.diag", + "std-random-range-unsupported.diag", + "std-random-seed-unsupported.diag", + "std-random-shuffle-unsupported.diag", + "std-random-string-unsupported.diag", + "std-random-uuid-unsupported.diag", + "std-result-map-unsupported.diag", + "std-string-concat-arity.diag", + "std-string-concat-helper-shadow.diag", + "std-string-concat-result-context.diag", + "std-string-concat-type.diag", + "std-string-concat-unsupported-string-container.diag", + "std-string-from-i64-unsupported.diag", + "std-string-index-unsupported.diag", + "std-string-len-type.diag", + "std-string-parse-bool-unsupported.diag", + "std-string-parse-bool-result-arity.diag", + "std-string-parse-bool-result-bool-context.diag", + "std-string-parse-bool-result-context.diag", + "std-string-parse-bool-result-helper-shadow.diag", + "std-string-parse-bool-result-name-shadow.diag", + "std-string-parse-bool-result-type.diag", + "std-string-parse-bytes-unsupported.diag", + "std-string-parse-f64-unsupported.diag", + "std-string-parse-generic-unsupported.diag", + "std-string-parse-i32-base-prefix-unsupported.diag", + "std-string-parse-i32-binary-unsupported.diag", + "std-string-parse-i32-code-unsupported.diag", + "std-string-parse-i32-error-adt-unsupported.diag", + "std-string-parse-i32-hex-unsupported.diag", + "std-string-parse-i32-locale-unsupported.diag", + "std-string-parse-i32-message-unsupported.diag", + "std-string-parse-i32-octal-unsupported.diag", + "std-string-parse-i32-plus-unsupported.diag", + "std-string-parse-i32-radix-unsupported.diag", + "std-string-parse-i32-result-arity.diag", + "std-string-parse-i32-result-bool-context.diag", + "std-string-parse-i32-result-context.diag", + "std-string-parse-i32-result-helper-shadow.diag", + "std-string-parse-i32-result-name-shadow.diag", + "std-string-parse-i32-result-type.diag", + "std-string-parse-i64-result-arity.diag", + "std-string-parse-i64-result-bool-context.diag", + "std-string-parse-i64-result-context.diag", + "std-string-parse-i64-result-helper-shadow.diag", + "std-string-parse-i64-result-name-shadow.diag", + "std-string-parse-i64-result-type.diag", + "std-string-parse-f64-result-arity.diag", + "std-string-parse-f64-result-bool-context.diag", + "std-string-parse-f64-result-context.diag", + "std-string-parse-f64-result-helper-shadow.diag", + "std-string-parse-f64-result-name-shadow.diag", + "std-string-parse-f64-result-type.diag", + "std-string-parse-i32-trim-unsupported.diag", + "std-string-parse-i32-underscore-unsupported.diag", + "std-string-parse-i32-unicode-unsupported.diag", + "std-string-parse-i32-unsupported.diag", + "std-string-parse-i32-whitespace-unsupported.diag", + "std-string-parse-string-unsupported.diag", + "std-string-scan-unsupported.diag", + "std-string-slice-unsupported.diag", + "std-string-tokenize-unsupported.diag", + "std-terminal-clear-unsupported.diag", + "std-terminal-echo-unsupported.diag", + "std-terminal-is-tty-unsupported.diag", + "std-terminal-raw-mode-unsupported.diag", + "std-time-monotonic-arity.diag", + "std-time-monotonic-helper-shadow.diag", + "std-time-monotonic-name-shadow.diag", + "std-time-monotonic-result-context.diag", + "std-time-now-unsupported.diag", + "std-time-sleep-arity.diag", + "std-time-sleep-helper-shadow.diag", + "std-time-sleep-name-shadow.diag", + "std-time-sleep-type.diag", + "std-vec-i32-append-vector-type.diag", + "std-vec-i32-arity.diag", + "std-vec-i32-empty-arity.diag", + "std-vec-i32-helper-shadow.diag", + "std-vec-i32-index-arity.diag", + "std-vec-i32-index-index-type.diag", + "std-vec-i32-index-vector-type.diag", + "std-vec-i32-len-arity.diag", + "std-vec-i32-len-type.diag", + "std-vec-i32-one-sided-equality.diag", + "std-vec-i32-push-alias.diag", + "std-vec-i32-result-context.diag", + "std-vec-i32-type.diag", + "std-vec-i64-append-vector-type.diag", + "std-vec-i64-arity.diag", + "std-vec-i64-empty-arity.diag", + "std-vec-i64-helper-shadow.diag", + "std-vec-i64-index-arity.diag", + "std-vec-i64-index-index-type.diag", + "std-vec-i64-index-vector-type.diag", + "std-vec-i64-len-arity.diag", + "std-vec-i64-len-type.diag", + "std-vec-i64-one-sided-equality.diag", + "std-vec-i64-push-alias.diag", + "std-vec-i64-result-context.diag", + "std-vec-i64-type.diag", + "std-vec-string-append-vector-type.diag", + "std-vec-string-arity.diag", + "std-vec-string-empty-arity.diag", + "std-vec-string-helper-shadow.diag", + "std-vec-string-index-arity.diag", + "std-vec-string-index-index-type.diag", + "std-vec-string-index-vector-type.diag", + "std-vec-string-len-arity.diag", + "std-vec-string-len-type.diag", + "std-vec-string-one-sided-equality.diag", + "std-vec-string-push-alias.diag", + "std-vec-string-result-context.diag", + "std-vec-string-type.diag", + "string-len-arity.diag", + "string-len-type.diag", + "struct-field-order-mismatch.diag", + "struct-missing-field.diag", + "struct-unknown-field.diag", + "test-duplicate-name.diag", + "test-invalid-escaped-name.diag", + "test-invalid-form.diag", + "test-invalid-name.diag", + "test-non-bool.diag", + "type-mismatch.diag", + "unclosed-list.diag", + "unknown-function.diag", + "unknown-struct-local.diag", + "unknown-struct-parameter.diag", + "unknown-struct-return.diag", + "unknown-top-level-form.diag", + "unsafe-parameter-shadows-callable.diag", + "unsafe-required-dealloc.diag", + "unsafe-required-ffi-call.diag", + "unsafe-required-load.diag", + "unsafe-required-operation.diag", + "unsafe-required-ptr-add.diag", + "unsafe-required-reinterpret.diag", + "unsafe-required-store.diag", + "unsafe-required-unchecked-index.diag", + "unsupported-local-type.diag", + "unsupported-match-container.diag", + "unsupported-match-mutation.diag", + "unsupported-match-payload-type.diag", + "unsupported-standard-library-call.diag", + "unsupported-option-result-equality.diag", + "unsupported-result-string-equality.diag", + "option-observation-non-option.diag", + "unsupported-option-result-print.diag", + "unsupported-result-string-print.diag", + "unsupported-option-parameter-payload-type.diag", + "result-observation-non-result.diag", + "unsupported-option-return-payload-type.diag", + "unsupported-option-payload-type.diag", + "unsupported-primitive-struct-field-container.diag", + "unsupported-array-element-type.diag", + "unsupported-array-equality.diag", + "unsupported-array-local-mutation.diag", + "unsupported-array-print.diag", + "unsupported-array-signature-element-type.diag", + "unsupported-float-literal.diag", + "unsupported-generic-vec-type.diag", + "unsupported-signature-type.diag", + "unsupported-result-parameter-payload-type.diag", + "unsupported-result-return-payload-type.diag", + "unsupported-unit-parameter-signature.diag", + "unsupported-unit-return-signature.diag", + "unsupported-result-payload-type.diag", + "unsupported-result-string-bool.diag", + "unsupported-struct-field-type.diag", + "unsupported-struct-field-mutation.diag", + "unsupported-print-unit.diag", + "unsupported-string-array.diag", + "unsupported-string-concatenation.diag", + "unsupported-string-escape.diag", + "unsupported-string-literal.diag", + "unsupported-vec-element-type.diag", + "unsupported-vec-literal.diag", + "unsupported-vec-nesting.diag", + "unsupported-vec-option.diag", + "unsupported-vec-result.diag", + "unsupported-unsafe-dealloc.diag", + "unsupported-unsafe-ffi-call.diag", + "unsupported-unsafe-load.diag", + "unsupported-unsafe-operation.diag", + "unsupported-unsafe-ptr-add.diag", + "unsupported-unsafe-reinterpret.diag", + "unsupported-unsafe-store.diag", + "unsupported-unsafe-unchecked-index.diag", + "while-body-local.diag", + "while-body-non-unit.diag", + "while-condition-not-bool.diag", + "while-final-function.diag", + "while-final-test.diag", + "zero-length-array-type.diag", +]; + +const LOWERING_INSPECTOR_FIXTURES: &[&str] = &[ + "add.checked.lower", + "add.surface.lower", + "array-direct-scalars-value-flow.checked.lower", + "array-direct-scalars-value-flow.surface.lower", + "array-direct-scalars.checked.lower", + "array-direct-scalars.surface.lower", + "array-enum.checked.lower", + "array-enum.surface.lower", + "array-string-value-flow.checked.lower", + "array-string-value-flow.surface.lower", + "array-string.checked.lower", + "array-string.surface.lower", + "array-struct-elements.checked.lower", + "array-struct-elements.surface.lower", + "array-struct-fields.checked.lower", + "array-struct-fields.surface.lower", + "array-value-flow.checked.lower", + "array-value-flow.surface.lower", + "array.checked.lower", + "array.surface.lower", + "checked-i64-to-i32-conversion.checked.lower", + "checked-i64-to-i32-conversion.surface.lower", + "comments.checked.lower", + "comments.surface.lower", + "enum-basic.checked.lower", + "enum-basic.surface.lower", + "enum-payload-direct-scalars.checked.lower", + "enum-payload-direct-scalars.surface.lower", + "enum-payload-structs.checked.lower", + "enum-payload-structs.surface.lower", + "enum-payload-i32.checked.lower", + "enum-payload-i32.surface.lower", + "enum-struct-fields.checked.lower", + "enum-struct-fields.surface.lower", + "f64-numeric-primitive.checked.lower", + "f64-numeric-primitive.surface.lower", + "f64-to-i32-result.checked.lower", + "f64-to-i32-result.surface.lower", + "f64-to-i64-result.checked.lower", + "f64-to-i64-result.surface.lower", + "i64-numeric-primitive.checked.lower", + "i64-numeric-primitive.surface.lower", + "numeric-widening-conversions.checked.lower", + "numeric-widening-conversions.surface.lower", + "numeric-struct-fields.checked.lower", + "numeric-struct-fields.surface.lower", + "formatter-stability-v1.checked.lower", + "formatter-stability-v1.surface.lower", + "host-io.checked.lower", + "host-io-result.checked.lower", + "host-io-result.surface.lower", + "host-io.surface.lower", + "if.checked.lower", + "if.surface.lower", + "f64-to-string.checked.lower", + "f64-to-string.surface.lower", + "integer-to-string.checked.lower", + "integer-to-string.surface.lower", + "composite-locals.checked.lower", + "composite-locals.surface.lower", + "composite-struct-fields.checked.lower", + "composite-struct-fields.surface.lower", + "local-variables.checked.lower", + "local-variables.surface.lower", + "option-result-flow.checked.lower", + "option-result-flow.surface.lower", + "option-result-match.checked.lower", + "option-result-match.surface.lower", + "option-result-payload.checked.lower", + "option-result-payload.surface.lower", + "option-result.checked.lower", + "option-result.surface.lower", + "owned-string-concat.checked.lower", + "owned-string-concat.surface.lower", + "print-bool.checked.lower", + "print-bool.surface.lower", + "primitive-struct-fields.checked.lower", + "primitive-struct-fields.surface.lower", + "random.checked.lower", + "random.surface.lower", + "result-helpers.checked.lower", + "result-helpers.surface.lower", + "string-print.checked.lower", + "string-print.surface.lower", + "standard-runtime.checked.lower", + "standard-runtime.surface.lower", + "standard-io-host-env.checked.lower", + "standard-io-host-env.surface.lower", + "stdin-result.checked.lower", + "stdin-result.surface.lower", + "string-parse-i32-result.checked.lower", + "string-parse-i32-result.surface.lower", + "string-parse-i64-result.checked.lower", + "string-parse-i64-result.surface.lower", + "string-parse-u32-result.checked.lower", + "string-parse-u32-result.surface.lower", + "string-parse-u64-result.checked.lower", + "string-parse-u64-result.surface.lower", + "string-parse-f64-result.checked.lower", + "string-parse-f64-result.surface.lower", + "string-parse-bool-result.checked.lower", + "string-parse-bool-result.surface.lower", + "string-value-flow.checked.lower", + "string-value-flow.surface.lower", + "struct-value-flow.checked.lower", + "struct-value-flow.surface.lower", + "struct.checked.lower", + "struct.surface.lower", + "time-sleep.checked.lower", + "time-sleep.surface.lower", + "top-level-test.checked.lower", + "top-level-test.surface.lower", + "u32-numeric-primitive.checked.lower", + "u32-numeric-primitive.surface.lower", + "u64-numeric-primitive.checked.lower", + "u64-numeric-primitive.surface.lower", + "unsafe.checked.lower", + "unsafe.surface.lower", + "unsigned-integer-to-string.checked.lower", + "unsigned-integer-to-string.surface.lower", + "vec-i32.checked.lower", + "vec-i32.surface.lower", + "while.checked.lower", + "while.surface.lower", +]; + +const LOWERING_INSPECTOR_CASES: &[LoweringInspectorCase] = &[ + LoweringInspectorCase { + source: "examples/add.slo", + surface_snapshot: "add.surface.lower", + checked_snapshot: "add.checked.lower", + }, + LoweringInspectorCase { + source: "tests/top-level-test.slo", + surface_snapshot: "top-level-test.surface.lower", + checked_snapshot: "top-level-test.checked.lower", + }, + LoweringInspectorCase { + source: "tests/comments.slo", + surface_snapshot: "comments.surface.lower", + checked_snapshot: "comments.checked.lower", + }, + LoweringInspectorCase { + source: "examples/enum-basic.slo", + surface_snapshot: "enum-basic.surface.lower", + checked_snapshot: "enum-basic.checked.lower", + }, + LoweringInspectorCase { + source: "examples/enum-payload-i32.slo", + surface_snapshot: "enum-payload-i32.surface.lower", + checked_snapshot: "enum-payload-i32.checked.lower", + }, + LoweringInspectorCase { + source: "examples/enum-payload-direct-scalars.slo", + surface_snapshot: "enum-payload-direct-scalars.surface.lower", + checked_snapshot: "enum-payload-direct-scalars.checked.lower", + }, + LoweringInspectorCase { + source: "examples/enum-payload-structs.slo", + surface_snapshot: "enum-payload-structs.surface.lower", + checked_snapshot: "enum-payload-structs.checked.lower", + }, + LoweringInspectorCase { + source: "examples/enum-struct-fields.slo", + surface_snapshot: "enum-struct-fields.surface.lower", + checked_snapshot: "enum-struct-fields.checked.lower", + }, + LoweringInspectorCase { + source: "examples/primitive-struct-fields.slo", + surface_snapshot: "primitive-struct-fields.surface.lower", + checked_snapshot: "primitive-struct-fields.checked.lower", + }, + LoweringInspectorCase { + source: "tests/f64-numeric-primitive.slo", + surface_snapshot: "f64-numeric-primitive.surface.lower", + checked_snapshot: "f64-numeric-primitive.checked.lower", + }, + LoweringInspectorCase { + source: "tests/i64-numeric-primitive.slo", + surface_snapshot: "i64-numeric-primitive.surface.lower", + checked_snapshot: "i64-numeric-primitive.checked.lower", + }, + LoweringInspectorCase { + source: "tests/u32-numeric-primitive.slo", + surface_snapshot: "u32-numeric-primitive.surface.lower", + checked_snapshot: "u32-numeric-primitive.checked.lower", + }, + LoweringInspectorCase { + source: "tests/u64-numeric-primitive.slo", + surface_snapshot: "u64-numeric-primitive.surface.lower", + checked_snapshot: "u64-numeric-primitive.checked.lower", + }, + LoweringInspectorCase { + source: "tests/integer-to-string.slo", + surface_snapshot: "integer-to-string.surface.lower", + checked_snapshot: "integer-to-string.checked.lower", + }, + LoweringInspectorCase { + source: "tests/unsigned-integer-to-string.slo", + surface_snapshot: "unsigned-integer-to-string.surface.lower", + checked_snapshot: "unsigned-integer-to-string.checked.lower", + }, + LoweringInspectorCase { + source: "tests/f64-to-string.slo", + surface_snapshot: "f64-to-string.surface.lower", + checked_snapshot: "f64-to-string.checked.lower", + }, + LoweringInspectorCase { + source: "tests/f64-to-i32-result.slo", + surface_snapshot: "f64-to-i32-result.surface.lower", + checked_snapshot: "f64-to-i32-result.checked.lower", + }, + LoweringInspectorCase { + source: "tests/f64-to-i64-result.slo", + surface_snapshot: "f64-to-i64-result.surface.lower", + checked_snapshot: "f64-to-i64-result.checked.lower", + }, + LoweringInspectorCase { + source: "tests/numeric-widening-conversions.slo", + surface_snapshot: "numeric-widening-conversions.surface.lower", + checked_snapshot: "numeric-widening-conversions.checked.lower", + }, + LoweringInspectorCase { + source: "examples/numeric-struct-fields.slo", + surface_snapshot: "numeric-struct-fields.surface.lower", + checked_snapshot: "numeric-struct-fields.checked.lower", + }, + LoweringInspectorCase { + source: "tests/checked-i64-to-i32-conversion.slo", + surface_snapshot: "checked-i64-to-i32-conversion.surface.lower", + checked_snapshot: "checked-i64-to-i32-conversion.checked.lower", + }, + LoweringInspectorCase { + source: "tests/formatter-stability-v1.fmt", + surface_snapshot: "formatter-stability-v1.surface.lower", + checked_snapshot: "formatter-stability-v1.checked.lower", + }, + LoweringInspectorCase { + source: "examples/host-io.slo", + surface_snapshot: "host-io.surface.lower", + checked_snapshot: "host-io.checked.lower", + }, + LoweringInspectorCase { + source: "tests/host-io-result.slo", + surface_snapshot: "host-io-result.surface.lower", + checked_snapshot: "host-io-result.checked.lower", + }, + LoweringInspectorCase { + source: "tests/unsafe.slo", + surface_snapshot: "unsafe.surface.lower", + checked_snapshot: "unsafe.checked.lower", + }, + LoweringInspectorCase { + source: "examples/local-variables.slo", + surface_snapshot: "local-variables.surface.lower", + checked_snapshot: "local-variables.checked.lower", + }, + LoweringInspectorCase { + source: "examples/composite-locals.slo", + surface_snapshot: "composite-locals.surface.lower", + checked_snapshot: "composite-locals.checked.lower", + }, + LoweringInspectorCase { + source: "examples/composite-struct-fields.slo", + surface_snapshot: "composite-struct-fields.surface.lower", + checked_snapshot: "composite-struct-fields.checked.lower", + }, + LoweringInspectorCase { + source: "examples/array-struct-fields.slo", + surface_snapshot: "array-struct-fields.surface.lower", + checked_snapshot: "array-struct-fields.checked.lower", + }, + LoweringInspectorCase { + source: "examples/if.slo", + surface_snapshot: "if.surface.lower", + checked_snapshot: "if.checked.lower", + }, + LoweringInspectorCase { + source: "examples/while.slo", + surface_snapshot: "while.surface.lower", + checked_snapshot: "while.checked.lower", + }, + LoweringInspectorCase { + source: "examples/struct.slo", + surface_snapshot: "struct.surface.lower", + checked_snapshot: "struct.checked.lower", + }, + LoweringInspectorCase { + source: "examples/struct-value-flow.slo", + surface_snapshot: "struct-value-flow.surface.lower", + checked_snapshot: "struct-value-flow.checked.lower", + }, + LoweringInspectorCase { + source: "examples/array.slo", + surface_snapshot: "array.surface.lower", + checked_snapshot: "array.checked.lower", + }, + LoweringInspectorCase { + source: "examples/array-direct-scalars.slo", + surface_snapshot: "array-direct-scalars.surface.lower", + checked_snapshot: "array-direct-scalars.checked.lower", + }, + LoweringInspectorCase { + source: "examples/array-direct-scalars-value-flow.slo", + surface_snapshot: "array-direct-scalars-value-flow.surface.lower", + checked_snapshot: "array-direct-scalars-value-flow.checked.lower", + }, + LoweringInspectorCase { + source: "examples/array-enum.slo", + surface_snapshot: "array-enum.surface.lower", + checked_snapshot: "array-enum.checked.lower", + }, + LoweringInspectorCase { + source: "examples/array-string.slo", + surface_snapshot: "array-string.surface.lower", + checked_snapshot: "array-string.checked.lower", + }, + LoweringInspectorCase { + source: "examples/array-string-value-flow.slo", + surface_snapshot: "array-string-value-flow.surface.lower", + checked_snapshot: "array-string-value-flow.checked.lower", + }, + LoweringInspectorCase { + source: "examples/array-value-flow.slo", + surface_snapshot: "array-value-flow.surface.lower", + checked_snapshot: "array-value-flow.checked.lower", + }, + LoweringInspectorCase { + source: "examples/array-struct-elements.slo", + surface_snapshot: "array-struct-elements.surface.lower", + checked_snapshot: "array-struct-elements.checked.lower", + }, + LoweringInspectorCase { + source: "examples/option-result.slo", + surface_snapshot: "option-result.surface.lower", + checked_snapshot: "option-result.checked.lower", + }, + LoweringInspectorCase { + source: "examples/option-result-flow.slo", + surface_snapshot: "option-result-flow.surface.lower", + checked_snapshot: "option-result-flow.checked.lower", + }, + LoweringInspectorCase { + source: "examples/option-result-match.slo", + surface_snapshot: "option-result-match.surface.lower", + checked_snapshot: "option-result-match.checked.lower", + }, + LoweringInspectorCase { + source: "examples/option-result-payload.slo", + surface_snapshot: "option-result-payload.surface.lower", + checked_snapshot: "option-result-payload.checked.lower", + }, + LoweringInspectorCase { + source: "examples/string-print.slo", + surface_snapshot: "string-print.surface.lower", + checked_snapshot: "string-print.checked.lower", + }, + LoweringInspectorCase { + source: "examples/string-value-flow.slo", + surface_snapshot: "string-value-flow.surface.lower", + checked_snapshot: "string-value-flow.checked.lower", + }, + LoweringInspectorCase { + source: "examples/print-bool.slo", + surface_snapshot: "print-bool.surface.lower", + checked_snapshot: "print-bool.checked.lower", + }, + LoweringInspectorCase { + source: "examples/random.slo", + surface_snapshot: "random.surface.lower", + checked_snapshot: "random.checked.lower", + }, + LoweringInspectorCase { + source: "examples/stdin-result.slo", + surface_snapshot: "stdin-result.surface.lower", + checked_snapshot: "stdin-result.checked.lower", + }, + LoweringInspectorCase { + source: "examples/string-parse-i32-result.slo", + surface_snapshot: "string-parse-i32-result.surface.lower", + checked_snapshot: "string-parse-i32-result.checked.lower", + }, + LoweringInspectorCase { + source: "examples/string-parse-i64-result.slo", + surface_snapshot: "string-parse-i64-result.surface.lower", + checked_snapshot: "string-parse-i64-result.checked.lower", + }, + LoweringInspectorCase { + source: "examples/string-parse-u32-result.slo", + surface_snapshot: "string-parse-u32-result.surface.lower", + checked_snapshot: "string-parse-u32-result.checked.lower", + }, + LoweringInspectorCase { + source: "examples/string-parse-u64-result.slo", + surface_snapshot: "string-parse-u64-result.surface.lower", + checked_snapshot: "string-parse-u64-result.checked.lower", + }, + LoweringInspectorCase { + source: "examples/string-parse-f64-result.slo", + surface_snapshot: "string-parse-f64-result.surface.lower", + checked_snapshot: "string-parse-f64-result.checked.lower", + }, + LoweringInspectorCase { + source: "examples/string-parse-bool-result.slo", + surface_snapshot: "string-parse-bool-result.surface.lower", + checked_snapshot: "string-parse-bool-result.checked.lower", + }, + LoweringInspectorCase { + source: "examples/result-helpers.slo", + surface_snapshot: "result-helpers.surface.lower", + checked_snapshot: "result-helpers.checked.lower", + }, + LoweringInspectorCase { + source: "examples/standard-runtime.slo", + surface_snapshot: "standard-runtime.surface.lower", + checked_snapshot: "standard-runtime.checked.lower", + }, + LoweringInspectorCase { + source: "examples/time-sleep.slo", + surface_snapshot: "time-sleep.surface.lower", + checked_snapshot: "time-sleep.checked.lower", + }, + LoweringInspectorCase { + source: "examples/owned-string-concat.slo", + surface_snapshot: "owned-string-concat.surface.lower", + checked_snapshot: "owned-string-concat.checked.lower", + }, + LoweringInspectorCase { + source: "tests/standard-io-host-env.slo", + surface_snapshot: "standard-io-host-env.surface.lower", + checked_snapshot: "standard-io-host-env.checked.lower", + }, + LoweringInspectorCase { + source: "examples/vec-i32.slo", + surface_snapshot: "vec-i32.surface.lower", + checked_snapshot: "vec-i32.checked.lower", + }, +]; + +const GLAGOL_TEST_FIXTURES: &[&str] = &[ + "array-direct-scalars-value-flow.slo", + "array-direct-scalars.slo", + "array-enum.slo", + "array-string-value-flow.slo", + "array-string.slo", + "array-struct-elements.slo", + "array-struct-fields.slo", + "array-value-flow.slo", + "array.slo", + "boolean-logic.slo", + "canonical.fmt", + "checked-i64-to-i32-conversion.slo", + "composite-locals.slo", + "composite-struct-fields.slo", + "comments.slo", + "enum-basic.slo", + "enum-payload-direct-scalars.slo", + "enum-payload-structs.slo", + "enum-payload-i32.slo", + "enum-struct-fields.slo", + "f64-to-i32-result.slo", + "f64-to-i64-result.slo", + "f64-to-string.slo", + "f64-numeric-primitive.slo", + "formatter-stability-v1.fmt", + "host-io.slo", + "host-io-result.slo", + "i64-numeric-primitive.slo", + "if.slo", + "integer-bitwise.slo", + "integer-remainder.slo", + "integer-to-string.slo", + "local-variables.slo", + "numeric-struct-fields.slo", + "numeric-widening-conversions.slo", + "option-result-flow.slo", + "option-result-match.slo", + "option-result-payload.slo", + "option-result.slo", + "owned-string-concat.slo", + "primitive-struct-fields.slo", + "print-bool.slo", + "random.slo", + "result-helpers.slo", + "standard-io-host-env.slo", + "standard-runtime.slo", + "stdin-result.slo", + "string-parse-i32-result.slo", + "string-parse-i64-result.slo", + "string-parse-u32-result.slo", + "string-parse-u64-result.slo", + "string-parse-f64-result.slo", + "string-parse-bool-result.slo", + "string-print.slo", + "string-value-flow.slo", + "struct-value-flow.slo", + "struct.slo", + "time-sleep.slo", + "top-level-test.fmt", + "top-level-test.slo", + "u32-numeric-primitive.slo", + "u64-numeric-primitive.slo", + "unsafe.slo", + "unsigned-integer-to-string.slo", + "vec-i32.slo", + "while.slo", +]; + +const SLOVO_FORMATTER_FIXTURES: &[&str] = &[ + "array-direct-scalars-value-flow.slo", + "array-direct-scalars.slo", + "array-enum.slo", + "array-string-value-flow.slo", + "array-string.slo", + "array-struct-elements.slo", + "array-struct-fields.slo", + "array-value-flow.slo", + "array.slo", + "boolean-logic.slo", + "canonical.slo", + "checked-i64-to-i32-conversion.slo", + "composite-locals.slo", + "composite-struct-fields.slo", + "comments.slo", + "enum-basic.slo", + "enum-payload-direct-scalars.slo", + "enum-payload-structs.slo", + "enum-payload-i32.slo", + "enum-struct-fields.slo", + "f64-to-i32-result.slo", + "f64-to-i64-result.slo", + "f64-to-string.slo", + "f64-numeric-primitive.slo", + "host-io.slo", + "host-io-result.slo", + "i64-numeric-primitive.slo", + "if.slo", + "integer-bitwise.slo", + "integer-remainder.slo", + "integer-to-string.slo", + "local-variables.slo", + "numeric-struct-fields.slo", + "numeric-widening-conversions.slo", + "option-result-flow.slo", + "option-result-match.slo", + "option-result-payload.slo", + "option-result.slo", + "owned-string-concat.slo", + "primitive-struct-fields.slo", + "print-bool.slo", + "random.slo", + "result-helpers.slo", + "standard-runtime.slo", + "std-source-layout-alpha.slo", + "stdin-result.slo", + "string-parse-i32-result.slo", + "string-parse-i64-result.slo", + "string-parse-u32-result.slo", + "string-parse-u64-result.slo", + "string-parse-f64-result.slo", + "string-parse-bool-result.slo", + "string-print.slo", + "string-value-flow.slo", + "struct-value-flow.slo", + "struct.slo", + "time-sleep.slo", + "top-level-test.slo", + "u32-numeric-primitive.slo", + "u64-numeric-primitive.slo", + "unsafe.slo", + "unsigned-integer-to-string.slo", + "vec-bool.slo", + "vec-f64.slo", + "vec-i32.slo", + "vec-i64.slo", + "vec-string.slo", + "while.slo", +]; + +const SLOVO_V0_SUPPORTED_COMPATIBILITY_FIXTURES: &[&str] = &[ + "add.slo", + "array.slo", + "if.slo", + "local-variables.slo", + "option-result.slo", + "struct.slo", + "top-level-test.slo", + "unsafe.slo", + "while.slo", +]; + +const SLOVO_V0_FORMATTER_COMPATIBILITY_FIXTURES: &[&str] = &[ + "array.slo", + "canonical.slo", + "if.slo", + "local-variables.slo", + "option-result.slo", + "struct.slo", + "top-level-test.slo", + "unsafe.slo", + "while.slo", +]; + +const STANDARD_MATH_SOURCE_HELPERS_ALPHA: &[&str] = &[ + "abs_i32", + "neg_i32", + "rem_i32", + "bit_and_i32", + "bit_or_i32", + "bit_xor_i32", + "is_even_i32", + "is_odd_i32", + "min_i32", + "max_i32", + "clamp_i32", + "square_i32", + "cube_i32", + "is_zero_i32", + "is_positive_i32", + "is_negative_i32", + "in_range_i32", + "abs_i64", + "neg_i64", + "rem_i64", + "bit_and_i64", + "bit_or_i64", + "bit_xor_i64", + "is_even_i64", + "is_odd_i64", + "min_i64", + "max_i64", + "clamp_i64", + "square_i64", + "cube_i64", + "is_zero_i64", + "is_positive_i64", + "is_negative_i64", + "in_range_i64", + "abs_f64", + "neg_f64", + "min_f64", + "max_f64", + "clamp_f64", + "square_f64", + "cube_f64", + "is_zero_f64", + "is_positive_f64", + "is_negative_f64", + "in_range_f64", +]; + +const STANDARD_OPTION_SOURCE_HELPERS_ALPHA: &[&str] = &[ + "some_i32", + "none_i32", + "is_some_i32", + "is_none_i32", + "unwrap_some_i32", + "unwrap_or_i32", + "some_u32", + "none_u32", + "is_some_u32", + "is_none_u32", + "unwrap_some_u32", + "unwrap_or_u32", + "some_i64", + "none_i64", + "is_some_i64", + "is_none_i64", + "unwrap_some_i64", + "unwrap_or_i64", + "some_u64", + "none_u64", + "is_some_u64", + "is_none_u64", + "unwrap_some_u64", + "unwrap_or_u64", + "some_f64", + "none_f64", + "is_some_f64", + "is_none_f64", + "unwrap_some_f64", + "unwrap_or_f64", + "some_bool", + "none_bool", + "is_some_bool", + "is_none_bool", + "unwrap_some_bool", + "unwrap_or_bool", + "some_string", + "none_string", + "is_some_string", + "is_none_string", + "unwrap_some_string", + "unwrap_or_string", +]; + +const STANDARD_OPTION_RESULT_BRIDGE_HELPERS_ALPHA: &[&str] = &[ + "some_or_err_i32", + "some_or_err_u32", + "some_or_err_i64", + "some_or_err_u64", + "some_or_err_f64", + "some_or_err_bool", + "some_or_err_string", +]; + +const STANDARD_TIME_SOURCE_FACADE_ALPHA: &[&str] = &["monotonic_ms", "sleep_ms_zero"]; + +const STANDARD_RANDOM_SOURCE_FACADE_ALPHA: &[&str] = &["random_i32", "random_i32_non_negative"]; + +const STANDARD_ENV_SOURCE_FACADE_ALPHA: &[&str] = &[ + "get", + "get_result", + "get_option", + "has", + "get_or", + "get_i32_result", + "get_i32_option", + "get_i32_or_zero", + "get_i32_or", + "get_u32_result", + "get_u32_option", + "get_u32_or_zero", + "get_u32_or", + "get_i64_result", + "get_i64_option", + "get_i64_or_zero", + "get_i64_or", + "get_u64_result", + "get_u64_option", + "get_u64_or_zero", + "get_u64_or", + "get_f64_result", + "get_f64_option", + "get_f64_or_zero", + "get_f64_or", + "get_bool_result", + "get_bool_option", + "get_bool_or_false", + "get_bool_or", +]; + +const STANDARD_FS_SOURCE_FACADE_ALPHA: &[&str] = &[ + "read_text", + "read_text_result", + "read_text_option", + "write_text_status", + "write_text_result", + "read_text_or", + "write_text_ok", + "read_i32_result", + "read_i32_option", + "read_i32_or_zero", + "read_i32_or", + "read_u32_result", + "read_u32_option", + "read_u32_or_zero", + "read_u32_or", + "read_i64_result", + "read_i64_option", + "read_i64_or_zero", + "read_i64_or", + "read_u64_result", + "read_u64_option", + "read_u64_or_zero", + "read_u64_or", + "read_f64_result", + "read_f64_option", + "read_f64_or_zero", + "read_f64_or", + "read_bool_result", + "read_bool_option", + "read_bool_or_false", + "read_bool_or", +]; + +const STANDARD_PROCESS_SOURCE_FACADE_ALPHA: &[&str] = &[ + "argc", + "arg", + "arg_result", + "arg_option", + "has_arg", + "arg_or", + "arg_or_empty", + "arg_i32_result", + "arg_i32_option", + "arg_i32_or_zero", + "arg_i32_or", + "arg_u32_result", + "arg_u32_option", + "arg_u32_or_zero", + "arg_u32_or", + "arg_i64_result", + "arg_i64_option", + "arg_i64_or_zero", + "arg_i64_or", + "arg_u64_result", + "arg_u64_option", + "arg_u64_or_zero", + "arg_u64_or", + "arg_f64_result", + "arg_f64_option", + "arg_f64_or_zero", + "arg_f64_or", + "arg_bool_result", + "arg_bool_option", + "arg_bool_or_false", + "arg_bool_or", +]; + +const STANDARD_STRING_SOURCE_FACADE_ALPHA: &[&str] = &[ + "len", + "concat", + "parse_i32_result", + "parse_i32_option", + "parse_u32_result", + "parse_u32_option", + "parse_i64_result", + "parse_i64_option", + "parse_u64_result", + "parse_u64_option", + "parse_f64_result", + "parse_f64_option", + "parse_bool_result", + "parse_bool_option", + "parse_i32_or_zero", + "parse_u32_or_zero", + "parse_i64_or_zero", + "parse_u64_or_zero", + "parse_f64_or_zero", + "parse_bool_or_false", + "parse_i32_or", + "parse_u32_or", + "parse_i64_or", + "parse_u64_or", + "parse_f64_or", + "parse_bool_or", +]; + +const STANDARD_NUM_SOURCE_FACADE_ALPHA: &[&str] = &[ + "i32_to_i64", + "i32_to_f64", + "i64_to_f64", + "i64_to_i32_result", + "f64_to_i32_result", + "f64_to_i64_result", + "i32_to_string", + "u32_to_string", + "i64_to_string", + "u64_to_string", + "f64_to_string", + "i64_to_i32_or", + "f64_to_i32_or", + "f64_to_i64_or", +]; + +const STANDARD_IO_SOURCE_FACADE_ALPHA: &[&str] = &[ + "print_i32_zero", + "print_i64_zero", + "print_u32_zero", + "print_u64_zero", + "print_f64_zero", + "print_string_zero", + "print_bool_zero", + "print_i32_value", + "print_i64_value", + "print_u32_value", + "print_u64_value", + "print_f64_value", + "print_string_value", + "print_bool_value", + "read_stdin_result", + "read_stdin_option", + "read_stdin_or", + "read_stdin_i32_result", + "read_stdin_i32_option", + "read_stdin_i32_or_zero", + "read_stdin_i32_or", + "read_stdin_u32_result", + "read_stdin_u32_option", + "read_stdin_u32_or_zero", + "read_stdin_u32_or", + "read_stdin_i64_result", + "read_stdin_i64_option", + "read_stdin_i64_or_zero", + "read_stdin_i64_or", + "read_stdin_u64_result", + "read_stdin_u64_option", + "read_stdin_u64_or_zero", + "read_stdin_u64_or", + "read_stdin_f64_result", + "read_stdin_f64_option", + "read_stdin_f64_or_zero", + "read_stdin_f64_or", + "read_stdin_bool_result", + "read_stdin_bool_option", + "read_stdin_bool_or_false", + "read_stdin_bool_or", +]; + +const STANDARD_CLI_SOURCE_FACADE_ALPHA: &[&str] = &[ + "arg_text_result", + "arg_text_option", + "arg_i32_result", + "arg_i32_option", + "arg_i32_or_zero", + "arg_i32_or", + "arg_u32_result", + "arg_u32_option", + "arg_u32_or_zero", + "arg_u32_or", + "arg_i64_result", + "arg_i64_option", + "arg_i64_or_zero", + "arg_i64_or", + "arg_u64_result", + "arg_u64_option", + "arg_u64_or_zero", + "arg_u64_or", + "arg_f64_result", + "arg_f64_option", + "arg_f64_or_zero", + "arg_f64_or", + "arg_bool_result", + "arg_bool_option", + "arg_bool_or_false", + "arg_bool_or", +]; + +const STANDARD_STRING_PARSE_RESULT_HELPERS_ALPHA: &[&str] = &[ + "parse_i32_result", + "parse_u32_result", + "parse_i64_result", + "parse_u64_result", + "parse_f64_result", + "parse_bool_result", +]; + +const STANDARD_CLI_LOCAL_SOURCE_FALLBACK_EXPECTED_OUTPUT: &str = concat!( + "test \"explicit local cli arg text missing\" ... ok\n", + "test \"explicit local cli arg text option\" ... ok\n", + "test \"explicit local cli arg i32 missing\" ... ok\n", + "test \"explicit local cli arg i32 fallback missing\" ... ok\n", + "test \"explicit local cli arg u32 missing\" ... ok\n", + "test \"explicit local cli arg u32 fallback missing\" ... ok\n", + "test \"explicit local cli arg i64 missing\" ... ok\n", + "test \"explicit local cli arg i64 fallback missing\" ... ok\n", + "test \"explicit local cli arg u64 missing\" ... ok\n", + "test \"explicit local cli arg u64 fallback missing\" ... ok\n", + "test \"explicit local cli arg f64 missing\" ... ok\n", + "test \"explicit local cli arg f64 fallback missing\" ... ok\n", + "test \"explicit local cli arg bool missing\" ... ok\n", + "test \"explicit local cli arg bool fallback missing\" ... ok\n", + "test \"explicit local cli typed option\" ... ok\n", + "test \"explicit local cli typed custom fallback\" ... ok\n", + "test \"explicit local cli facade all\" ... ok\n", + "17 test(s) passed\n", +); + +const STANDARD_VEC_I32_SOURCE_FACADE_ALPHA: &[&str] = &[ + "empty", + "append", + "len", + "at", + "singleton", + "append2", + "append3", + "pair", + "triple", + "repeat", + "range", + "range_from_zero", + "is_empty", + "index_or", + "first_or", + "last_or", + "index_option", + "first_option", + "last_option", + "index_of_option", + "last_index_of_option", + "count_of", + "contains", + "sum", + "concat", + "take", + "starts_with", + "without_prefix", + "ends_with", + "without_suffix", + "drop", + "reverse", + "subvec", + "insert_at", + "insert_range", + "replace_at", + "replace_range", + "remove_at", + "remove_range", +]; + +const STANDARD_VEC_I64_SOURCE_FACADE_ALPHA: &[&str] = &[ + "empty", + "append", + "len", + "at", + "singleton", + "append2", + "append3", + "pair", + "triple", + "is_empty", + "index_or", + "first_or", + "last_or", + "index_option", + "first_option", + "last_option", + "index_of_option", + "last_index_of_option", + "contains", + "sum", + "concat", + "take", + "drop", + "reverse", + "subvec", + "insert_at", + "insert_range", + "replace_at", + "replace_range", + "remove_at", + "remove_range", +]; + +const STANDARD_VEC_F64_SOURCE_FACADE_ALPHA: &[&str] = &[ + "empty", + "append", + "len", + "at", + "singleton", + "append2", + "append3", + "pair", + "triple", + "is_empty", + "index_or", + "first_or", + "last_or", + "index_option", + "first_option", + "last_option", + "index_of_option", + "last_index_of_option", + "contains", + "sum", + "concat", + "take", + "drop", + "reverse", + "subvec", + "insert_at", + "insert_range", + "replace_at", + "replace_range", + "remove_at", + "remove_range", +]; + +const STANDARD_VEC_BOOL_SOURCE_FACADE_ALPHA: &[&str] = &[ + "empty", + "append", + "len", + "at", + "singleton", + "append2", + "append3", + "pair", + "triple", + "is_empty", + "index_or", + "first_or", + "last_or", + "index_option", + "first_option", + "last_option", + "index_of_option", + "last_index_of_option", + "contains", + "count_of", + "concat", + "take", + "drop", + "reverse", + "subvec", + "insert_at", + "insert_range", + "replace_at", + "replace_range", + "remove_at", + "remove_range", +]; + +const STANDARD_VEC_STRING_SOURCE_FACADE_ALPHA: &[&str] = &[ + "empty", + "append", + "len", + "at", + "singleton", + "append2", + "append3", + "pair", + "triple", + "is_empty", + "index_or", + "first_or", + "last_or", + "index_option", + "first_option", + "last_option", + "index_of_option", + "last_index_of_option", + "contains", + "count_of", + "concat", + "take", + "drop", + "reverse", + "subvec", + "insert_at", + "insert_range", + "replace_at", + "replace_range", + "remove_at", + "remove_range", +]; + +const STANDARD_ENV_MISSING_NAME: &str = "GLAGOL_STD_LAYOUT_LOCAL_ENV_ALPHA_UNLIKELY_MISSING"; +const STANDARD_ENV_PRESENT_NAME: &str = "GLAGOL_STD_LAYOUT_LOCAL_ENV_ALPHA_PRESENT"; +const STANDARD_ENV_PRESENT_VALUE: &str = "glagol-std-layout-local-env-alpha-value"; +const STANDARD_ENV_PRESENT_I32_NAME: &str = "GLAGOL_STD_LAYOUT_LOCAL_ENV_ALPHA_PRESENT_I32"; +const STANDARD_ENV_PRESENT_I32_VALUE: &str = "42"; +const STANDARD_ENV_PRESENT_U32_NAME: &str = "GLAGOL_STD_LAYOUT_LOCAL_ENV_ALPHA_PRESENT_U32"; +const STANDARD_ENV_PRESENT_U32_VALUE: &str = "42"; +const STANDARD_ENV_PRESENT_I64_NAME: &str = "GLAGOL_STD_LAYOUT_LOCAL_ENV_ALPHA_PRESENT_I64"; +const STANDARD_ENV_PRESENT_I64_VALUE: &str = "42000000000"; +const STANDARD_ENV_PRESENT_U64_NAME: &str = "GLAGOL_STD_LAYOUT_LOCAL_ENV_ALPHA_PRESENT_U64"; +const STANDARD_ENV_PRESENT_U64_VALUE: &str = "4294967296"; +const STANDARD_ENV_PRESENT_F64_NAME: &str = "GLAGOL_STD_LAYOUT_LOCAL_ENV_ALPHA_PRESENT_F64"; +const STANDARD_ENV_PRESENT_F64_VALUE: &str = "42.5"; +const STANDARD_ENV_PRESENT_BOOL_NAME: &str = "GLAGOL_STD_LAYOUT_LOCAL_ENV_ALPHA_PRESENT_BOOL"; +const STANDARD_ENV_PRESENT_BOOL_VALUE: &str = "true"; +const STANDARD_ENV_INVALID_NAME: &str = "GLAGOL_STD_LAYOUT_LOCAL_ENV_ALPHA_INVALID"; +const STANDARD_ENV_INVALID_VALUE: &str = "bad"; +const STANDARD_IMPORT_ENV_MISSING_NAME: &str = "GLAGOL_STD_IMPORT_ENV_ALPHA_UNLIKELY_MISSING"; +const STANDARD_IMPORT_ENV_PRESENT_NAME: &str = "GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT"; +const STANDARD_IMPORT_ENV_PRESENT_VALUE: &str = "glagol-std-import-env-alpha-value"; +const STANDARD_IMPORT_ENV_PRESENT_I32_NAME: &str = "GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT_I32"; +const STANDARD_IMPORT_ENV_PRESENT_I32_VALUE: &str = "42"; +const STANDARD_IMPORT_ENV_PRESENT_U32_NAME: &str = "GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT_U32"; +const STANDARD_IMPORT_ENV_PRESENT_U32_VALUE: &str = "42"; +const STANDARD_IMPORT_ENV_PRESENT_I64_NAME: &str = "GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT_I64"; +const STANDARD_IMPORT_ENV_PRESENT_I64_VALUE: &str = "42000000000"; +const STANDARD_IMPORT_ENV_PRESENT_U64_NAME: &str = "GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT_U64"; +const STANDARD_IMPORT_ENV_PRESENT_U64_VALUE: &str = "4294967296"; +const STANDARD_IMPORT_ENV_PRESENT_F64_NAME: &str = "GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT_F64"; +const STANDARD_IMPORT_ENV_PRESENT_F64_VALUE: &str = "42.5"; +const STANDARD_IMPORT_ENV_PRESENT_BOOL_NAME: &str = "GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT_BOOL"; +const STANDARD_IMPORT_ENV_PRESENT_BOOL_VALUE: &str = "true"; +const STANDARD_IMPORT_ENV_INVALID_NAME: &str = "GLAGOL_STD_IMPORT_ENV_ALPHA_INVALID"; +const STANDARD_IMPORT_ENV_INVALID_VALUE: &str = "bad"; + +const STANDARD_RESULT_SOURCE_HELPERS_ALPHA: &[&str] = &[ + "ok_i32", + "err_i32", + "is_ok_i32", + "is_err_i32", + "unwrap_ok_i32", + "unwrap_err_i32", + "unwrap_or_i32", + "ok_u32", + "err_u32", + "is_ok_u32", + "is_err_u32", + "unwrap_ok_u32", + "unwrap_err_u32", + "unwrap_or_u32", + "ok_i64", + "err_i64", + "is_err_i64", + "unwrap_err_i64", + "unwrap_or_i64", + "ok_u64", + "err_u64", + "is_ok_u64", + "is_err_u64", + "unwrap_ok_u64", + "unwrap_err_u64", + "unwrap_or_u64", + "ok_string", + "err_string", + "is_err_string", + "unwrap_err_string", + "unwrap_or_string", + "ok_f64", + "err_f64", + "is_err_f64", + "unwrap_err_f64", + "unwrap_or_f64", + "ok_bool", + "err_bool", + "is_ok_bool", + "is_err_bool", + "unwrap_ok_bool", + "unwrap_err_bool", + "unwrap_or_bool", +]; + +const STANDARD_RESULT_OPTION_BRIDGE_HELPERS_ALPHA: &[&str] = &[ + "ok_or_none_i32", + "ok_or_none_u32", + "ok_or_none_i64", + "ok_or_none_u64", + "ok_or_none_string", + "ok_or_none_f64", + "ok_or_none_bool", +]; + +const STANDARD_RESULT_SOURCE_SEARCH_ALPHA: &[&str] = &[ + "is_ok_i32", + "is_err_i32", + "unwrap_ok_i32", + "unwrap_err_i32", + "unwrap_or_i32", + "is_ok_u32", + "is_err_u32", + "unwrap_ok_u32", + "unwrap_err_u32", + "unwrap_or_u32", + "is_ok_i64", + "is_err_i64", + "unwrap_ok_i64", + "unwrap_err_i64", + "unwrap_or_i64", + "is_ok_u64", + "is_err_u64", + "unwrap_ok_u64", + "unwrap_err_u64", + "unwrap_or_u64", + "is_ok_string", + "is_err_string", + "unwrap_ok_string", + "unwrap_err_string", + "unwrap_or_string", + "is_ok_f64", + "is_err_f64", + "unwrap_ok_f64", + "unwrap_err_f64", + "unwrap_or_f64", + "is_ok_bool", + "is_err_bool", + "unwrap_ok_bool", + "unwrap_err_bool", + "unwrap_or_bool", +]; + +struct LoweringInspectorCase { + source: &'static str, + surface_snapshot: &'static str, + checked_snapshot: &'static str, +} + +#[test] +fn promotion_gate_artifacts_are_aligned() { + let repo = repo_root(); + let glagol_add = repo.join("examples/add.slo"); + let glagol_array_direct_scalars = repo.join("examples/array-direct-scalars.slo"); + let glagol_array_direct_scalars_value_flow = + repo.join("examples/array-direct-scalars-value-flow.slo"); + let glagol_array_enum = repo.join("examples/array-enum.slo"); + let glagol_array_struct_elements = repo.join("examples/array-struct-elements.slo"); + let glagol_array_struct_fields = repo.join("examples/array-struct-fields.slo"); + let glagol_array_string = repo.join("examples/array-string.slo"); + let glagol_array_string_value_flow = repo.join("examples/array-string-value-flow.slo"); + let glagol_array = repo.join("examples/array.slo"); + let glagol_array_value_flow = repo.join("examples/array-value-flow.slo"); + let glagol_boolean_logic = repo.join("examples/boolean-logic.slo"); + let glagol_checked_i64_to_i32 = repo.join("examples/checked-i64-to-i32-conversion.slo"); + let glagol_enum_basic = repo.join("examples/enum-basic.slo"); + let glagol_enum_payload_direct_scalars = repo.join("examples/enum-payload-direct-scalars.slo"); + let glagol_enum_payload_structs = repo.join("examples/enum-payload-structs.slo"); + let glagol_enum_payload_i32 = repo.join("examples/enum-payload-i32.slo"); + let glagol_enum_struct_field = repo.join("examples/enum-struct-fields.slo"); + let glagol_host_io = repo.join("examples/host-io.slo"); + let glagol_host_io_result = repo.join("examples/host-io-result.slo"); + let glagol_if = repo.join("examples/if.slo"); + let glagol_integer_bitwise = repo.join("examples/integer-bitwise.slo"); + let glagol_integer_remainder = repo.join("examples/integer-remainder.slo"); + let glagol_integer_to_string = repo.join("examples/integer-to-string.slo"); + let glagol_f64_to_string = repo.join("examples/f64-to-string.slo"); + let glagol_f64_to_i32_result = repo.join("examples/f64-to-i32-result.slo"); + let glagol_f64_to_i64_result = repo.join("examples/f64-to-i64-result.slo"); + let glagol_composite = repo.join("examples/composite-locals.slo"); + let glagol_composite_struct_fields = repo.join("examples/composite-struct-fields.slo"); + let glagol_local = repo.join("examples/local-variables.slo"); + let glagol_numeric_struct_fields = repo.join("examples/numeric-struct-fields.slo"); + let glagol_numeric_widening = repo.join("examples/numeric-widening-conversions.slo"); + let glagol_option_result = repo.join("examples/option-result.slo"); + let glagol_option_result_flow = repo.join("examples/option-result-flow.slo"); + let glagol_option_result_match = repo.join("examples/option-result-match.slo"); + let glagol_option_result_payload = repo.join("examples/option-result-payload.slo"); + let glagol_owned_string_concat = repo.join("examples/owned-string-concat.slo"); + let glagol_print_bool = repo.join("examples/print-bool.slo"); + let glagol_primitive_struct_fields = repo.join("examples/primitive-struct-fields.slo"); + let glagol_random = repo.join("examples/random.slo"); + let glagol_stdin_result = repo.join("examples/stdin-result.slo"); + let glagol_string_parse_i32_result = repo.join("examples/string-parse-i32-result.slo"); + let glagol_string_parse_i64_result = repo.join("examples/string-parse-i64-result.slo"); + let glagol_string_parse_f64_result = repo.join("examples/string-parse-f64-result.slo"); + let glagol_string_parse_bool_result = repo.join("examples/string-parse-bool-result.slo"); + let glagol_result_helpers = repo.join("examples/result-helpers.slo"); + let glagol_result_f64_bool_match = repo.join("examples/result-f64-bool-match.slo"); + let glagol_standard_runtime = repo.join("examples/standard-runtime.slo"); + let glagol_time_sleep = repo.join("examples/time-sleep.slo"); + let glagol_string_print = repo.join("examples/string-print.slo"); + let glagol_string_value_flow = repo.join("examples/string-value-flow.slo"); + let glagol_struct = repo.join("examples/struct.slo"); + let glagol_struct_value_flow = repo.join("examples/struct-value-flow.slo"); + let glagol_unsafe = repo.join("examples/unsafe.slo"); + let glagol_vec_i32 = repo.join("examples/vec-i32.slo"); + let _glagol_vec_f64 = repo.join("examples/vec-f64.slo"); + let _glagol_vec_bool = repo.join("examples/vec-bool.slo"); + let _glagol_vec_string = repo.join("examples/vec-string.slo"); + let glagol_while = repo.join("examples/while.slo"); + let glagol_project_enum_imports = repo.join("examples/projects/enum-imports"); + let glagol_project_std_layout_local_math = repo.join("examples/projects/std-layout-local-math"); + let glagol_project_std_layout_local_num = repo.join("examples/projects/std-layout-local-num"); + let glagol_project_std_layout_local_option = + repo.join("examples/projects/std-layout-local-option"); + let glagol_project_std_layout_local_process = + repo.join("examples/projects/std-layout-local-process"); + let glagol_project_std_layout_local_result = + repo.join("examples/projects/std-layout-local-result"); + let glagol_project_std_layout_local_string = + repo.join("examples/projects/std-layout-local-string"); + let glagol_project_std_layout_local_time = repo.join("examples/projects/std-layout-local-time"); + let glagol_project_std_layout_local_random = + repo.join("examples/projects/std-layout-local-random"); + let glagol_project_std_layout_local_env = repo.join("examples/projects/std-layout-local-env"); + let glagol_project_std_layout_local_fs = repo.join("examples/projects/std-layout-local-fs"); + let glagol_project_std_layout_local_io = repo.join("examples/projects/std-layout-local-io"); + let glagol_project_std_layout_local_cli = repo.join("examples/projects/std-layout-local-cli"); + let glagol_project_std_layout_local_vec_i32 = + repo.join("examples/projects/std-layout-local-vec_i32"); + let glagol_project_std_layout_local_vec_i64 = + repo.join("examples/projects/std-layout-local-vec_i64"); + let glagol_project_std_layout_local_vec_f64 = + repo.join("examples/projects/std-layout-local-vec_f64"); + let glagol_project_std_layout_local_vec_bool = + repo.join("examples/projects/std-layout-local-vec_bool"); + let glagol_project_std_layout_local_vec_string = + repo.join("examples/projects/std-layout-local-vec_string"); + let glagol_project_std_import_math = repo.join("examples/projects/std-import-math"); + let glagol_project_std_import_result = repo.join("examples/projects/std-import-result"); + let glagol_project_std_import_option = repo.join("examples/projects/std-import-option"); + let glagol_project_std_import_time = repo.join("examples/projects/std-import-time"); + let glagol_project_std_import_random = repo.join("examples/projects/std-import-random"); + let glagol_project_std_import_env = repo.join("examples/projects/std-import-env"); + let glagol_project_std_import_fs = repo.join("examples/projects/std-import-fs"); + let glagol_project_std_import_process = repo.join("examples/projects/std-import-process"); + let glagol_project_std_import_string = repo.join("examples/projects/std-import-string"); + let glagol_project_std_import_num = repo.join("examples/projects/std-import-num"); + let glagol_project_std_import_io = repo.join("examples/projects/std-import-io"); + let glagol_project_std_import_cli = repo.join("examples/projects/std-import-cli"); + let glagol_project_std_import_vec_i32 = repo.join("examples/projects/std-import-vec_i32"); + let glagol_project_std_import_vec_i64 = repo.join("examples/projects/std-import-vec_i64"); + let glagol_project_std_import_vec_f64 = repo.join("examples/projects/std-import-vec_f64"); + let glagol_project_std_import_vec_bool = repo.join("examples/projects/std-import-vec_bool"); + let glagol_project_std_import_vec_string = repo.join("examples/projects/std-import-vec_string"); + let glagol_workspace_std_import_option = repo.join("examples/workspaces/std-import-option"); + let glagol_benchmark_math_loop = repo.join("benchmarks/math-loop"); + let glagol_benchmark_branch_loop = repo.join("benchmarks/branch-loop"); + let glagol_benchmark_parse_loop = repo.join("benchmarks/parse-loop"); + let glagol_benchmark_array_index_loop = repo.join("benchmarks/array-index-loop"); + let glagol_benchmark_string_eq_loop = repo.join("benchmarks/string-eq-loop"); + let glagol_benchmark_array_struct_field_loop = repo.join("benchmarks/array-struct-field-loop"); + let glagol_benchmark_enum_struct_payload_loop = + repo.join("benchmarks/enum-struct-payload-loop"); + let glagol_benchmark_vec_i32_index_loop = repo.join("benchmarks/vec-i32-index-loop"); + let glagol_benchmark_vec_string_eq_loop = repo.join("benchmarks/vec-string-eq-loop"); + let formatter_canonical = repo.join("tests/canonical.fmt"); + let array_direct_scalars_formatter = repo.join("tests/array-direct-scalars.slo"); + let array_direct_scalars_value_flow_formatter = + repo.join("tests/array-direct-scalars-value-flow.slo"); + let array_enum_formatter = repo.join("tests/array-enum.slo"); + let array_struct_elements_formatter = repo.join("tests/array-struct-elements.slo"); + let array_struct_fields_formatter = repo.join("tests/array-struct-fields.slo"); + let array_string_formatter = repo.join("tests/array-string.slo"); + let array_string_value_flow_formatter = repo.join("tests/array-string-value-flow.slo"); + let array_formatter = repo.join("tests/array.slo"); + let array_value_flow_formatter = repo.join("tests/array-value-flow.slo"); + let boolean_logic_formatter = repo.join("tests/boolean-logic.slo"); + let checked_i64_to_i32_formatter = repo.join("tests/checked-i64-to-i32-conversion.slo"); + let host_io_formatter = repo.join("tests/host-io.slo"); + let host_io_result_formatter = repo.join("tests/host-io-result.slo"); + let if_formatter = repo.join("tests/if.slo"); + let integer_bitwise_formatter = repo.join("tests/integer-bitwise.slo"); + let integer_remainder_formatter = repo.join("tests/integer-remainder.slo"); + let integer_to_string_formatter = repo.join("tests/integer-to-string.slo"); + let f64_to_string_formatter = repo.join("tests/f64-to-string.slo"); + let f64_to_i32_result_formatter = repo.join("tests/f64-to-i32-result.slo"); + let f64_to_i64_result_formatter = repo.join("tests/f64-to-i64-result.slo"); + let composite_formatter = repo.join("tests/composite-locals.slo"); + let composite_struct_fields_formatter = repo.join("tests/composite-struct-fields.slo"); + let local_formatter = repo.join("tests/local-variables.slo"); + let numeric_struct_fields_formatter = repo.join("tests/numeric-struct-fields.slo"); + let numeric_widening_formatter = repo.join("tests/numeric-widening-conversions.slo"); + let option_result_formatter = repo.join("tests/option-result.slo"); + let option_result_flow_formatter = repo.join("tests/option-result-flow.slo"); + let option_result_match_formatter = repo.join("tests/option-result-match.slo"); + let option_result_payload_formatter = repo.join("tests/option-result-payload.slo"); + let owned_string_concat_formatter = repo.join("tests/owned-string-concat.slo"); + let print_bool_formatter = repo.join("tests/print-bool.slo"); + let primitive_struct_fields_formatter = repo.join("tests/primitive-struct-fields.slo"); + let random_formatter = repo.join("tests/random.slo"); + let stdin_result_formatter = repo.join("tests/stdin-result.slo"); + let string_parse_i32_result_formatter = repo.join("tests/string-parse-i32-result.slo"); + let string_parse_i64_result_formatter = repo.join("tests/string-parse-i64-result.slo"); + let string_parse_f64_result_formatter = repo.join("tests/string-parse-f64-result.slo"); + let string_parse_bool_result_formatter = repo.join("tests/string-parse-bool-result.slo"); + let result_helpers_formatter = repo.join("tests/result-helpers.slo"); + let standard_runtime_formatter = repo.join("tests/standard-runtime.slo"); + let time_sleep_formatter = repo.join("tests/time-sleep.slo"); + let string_print_formatter = repo.join("tests/string-print.slo"); + let string_value_flow_formatter = repo.join("tests/string-value-flow.slo"); + let struct_formatter = repo.join("tests/struct.slo"); + let struct_value_flow_formatter = repo.join("tests/struct-value-flow.slo"); + let unsafe_formatter = repo.join("tests/unsafe.slo"); + let vec_i32_formatter = repo.join("tests/vec-i32.slo"); + let while_formatter = repo.join("tests/while.slo"); + let comments_formatter = repo.join("tests/comments.slo"); + let enum_basic_formatter = repo.join("tests/enum-basic.slo"); + let enum_payload_direct_scalars_formatter = repo.join("tests/enum-payload-direct-scalars.slo"); + let enum_payload_structs_formatter = repo.join("tests/enum-payload-structs.slo"); + let enum_payload_i32_formatter = repo.join("tests/enum-payload-i32.slo"); + let enum_struct_field_formatter = repo.join("tests/enum-struct-fields.slo"); + let formatter_stability_v1 = repo.join("tests/formatter-stability-v1.fmt"); + + assert_supported_fixtures(&repo); + assert_glagol_test_fixtures(&repo); + assert_lowering_inspector_fixture_inventory(&repo); + assert_same_supported_body(&glagol_add, &formatter_canonical); + assert_same_supported_body( + &glagol_array_direct_scalars, + &array_direct_scalars_formatter, + ); + assert_same_supported_body( + &glagol_array_direct_scalars_value_flow, + &array_direct_scalars_value_flow_formatter, + ); + assert_same_supported_body(&glagol_array_enum, &array_enum_formatter); + assert_same_supported_body( + &glagol_array_struct_elements, + &array_struct_elements_formatter, + ); + assert_same_supported_body(&glagol_array_struct_fields, &array_struct_fields_formatter); + assert_same_supported_body(&glagol_array_string, &array_string_formatter); + assert_same_supported_body( + &glagol_array_string_value_flow, + &array_string_value_flow_formatter, + ); + assert_same_supported_body(&glagol_array, &array_formatter); + assert_same_supported_body(&glagol_array_value_flow, &array_value_flow_formatter); + assert_same_supported_body(&glagol_boolean_logic, &boolean_logic_formatter); + assert_boolean_logic_tooling_matches_fixture(&glagol_boolean_logic); + assert_same_supported_body(&glagol_checked_i64_to_i32, &checked_i64_to_i32_formatter); + assert_same_supported_body(&glagol_enum_basic, &enum_basic_formatter); + assert_same_supported_body( + &glagol_enum_payload_direct_scalars, + &enum_payload_direct_scalars_formatter, + ); + assert_same_supported_body( + &glagol_enum_payload_structs, + &enum_payload_structs_formatter, + ); + assert_same_supported_body(&glagol_enum_payload_i32, &enum_payload_i32_formatter); + assert_same_supported_body(&glagol_enum_struct_field, &enum_struct_field_formatter); + assert_same_supported_body(&glagol_host_io, &host_io_formatter); + assert_same_supported_body(&glagol_host_io_result, &host_io_result_formatter); + assert_same_supported_body(&glagol_if, &if_formatter); + assert_same_supported_body(&glagol_integer_bitwise, &integer_bitwise_formatter); + assert_integer_bitwise_tooling_matches_fixture(&glagol_integer_bitwise); + assert_same_supported_body(&glagol_integer_remainder, &integer_remainder_formatter); + assert_integer_remainder_tooling_matches_fixture(&glagol_integer_remainder); + assert_same_supported_body(&glagol_integer_to_string, &integer_to_string_formatter); + assert_same_supported_body(&glagol_f64_to_string, &f64_to_string_formatter); + assert_same_supported_body(&glagol_f64_to_i32_result, &f64_to_i32_result_formatter); + assert_same_supported_body(&glagol_f64_to_i64_result, &f64_to_i64_result_formatter); + assert_same_supported_body(&glagol_composite, &composite_formatter); + assert_same_supported_body( + &glagol_composite_struct_fields, + &composite_struct_fields_formatter, + ); + assert_same_supported_body(&glagol_local, &local_formatter); + assert_same_supported_body( + &glagol_numeric_struct_fields, + &numeric_struct_fields_formatter, + ); + assert_same_supported_body(&glagol_numeric_widening, &numeric_widening_formatter); + assert_same_supported_body(&glagol_option_result, &option_result_formatter); + assert_same_supported_body(&glagol_option_result_flow, &option_result_flow_formatter); + assert_same_supported_body(&glagol_option_result_match, &option_result_match_formatter); + assert_same_supported_body( + &glagol_option_result_payload, + &option_result_payload_formatter, + ); + assert_same_supported_body(&glagol_owned_string_concat, &owned_string_concat_formatter); + assert_same_supported_body(&glagol_print_bool, &print_bool_formatter); + assert_same_supported_body( + &glagol_primitive_struct_fields, + &primitive_struct_fields_formatter, + ); + assert_same_supported_body(&glagol_random, &random_formatter); + assert_same_supported_body(&glagol_stdin_result, &stdin_result_formatter); + assert_same_supported_body( + &glagol_string_parse_i32_result, + &string_parse_i32_result_formatter, + ); + assert_same_supported_body( + &glagol_string_parse_i64_result, + &string_parse_i64_result_formatter, + ); + assert_same_supported_body( + &glagol_string_parse_f64_result, + &string_parse_f64_result_formatter, + ); + assert_same_supported_body( + &glagol_string_parse_bool_result, + &string_parse_bool_result_formatter, + ); + assert_same_supported_body(&glagol_result_helpers, &result_helpers_formatter); + assert_same_supported_body(&glagol_standard_runtime, &standard_runtime_formatter); + assert_same_supported_body(&glagol_time_sleep, &time_sleep_formatter); + assert_same_supported_body(&glagol_string_print, &string_print_formatter); + assert_same_supported_body(&glagol_string_value_flow, &string_value_flow_formatter); + assert_same_supported_body(&glagol_struct, &struct_formatter); + assert_same_supported_body(&glagol_struct_value_flow, &struct_value_flow_formatter); + assert_same_supported_body(&glagol_unsafe, &unsafe_formatter); + assert_same_supported_body(&glagol_vec_i32, &vec_i32_formatter); + assert_same_supported_body(&glagol_while, &while_formatter); + assert_formatter_fixture_contract(&formatter_canonical); + + assert_compiler_output_matches_supported_add_shape(&repo, &glagol_add); + assert_tree_printer_matches_fixture(&repo, &glagol_add); + assert_formatter_matches_fixture(&array_direct_scalars_formatter); + assert_formatter_matches_fixture(&array_direct_scalars_value_flow_formatter); + assert_formatter_matches_fixture(&array_enum_formatter); + assert_formatter_matches_fixture(&array_struct_elements_formatter); + assert_formatter_matches_fixture(&array_struct_fields_formatter); + assert_formatter_matches_fixture(&array_string_formatter); + assert_formatter_matches_fixture(&array_string_value_flow_formatter); + assert_formatter_matches_fixture(&formatter_canonical); + assert_formatter_matches_fixture(&boolean_logic_formatter); + assert_formatter_matches_fixture(&comments_formatter); + assert_formatter_matches_fixture(&enum_basic_formatter); + assert_formatter_matches_fixture(&enum_payload_direct_scalars_formatter); + assert_formatter_matches_fixture(&enum_payload_structs_formatter); + assert_formatter_matches_fixture(&enum_payload_i32_formatter); + assert_formatter_matches_fixture(&enum_struct_field_formatter); + assert_formatter_matches_fixture(&formatter_stability_v1); + assert_formatter_matches_fixture(&checked_i64_to_i32_formatter); + assert_formatter_matches_fixture(&host_io_formatter); + assert_formatter_matches_fixture(&integer_bitwise_formatter); + assert_formatter_matches_fixture(&integer_remainder_formatter); + assert_formatter_matches_fixture(&integer_to_string_formatter); + assert_formatter_matches_fixture(&f64_to_string_formatter); + assert_formatter_matches_fixture(&f64_to_i32_result_formatter); + assert_formatter_matches_fixture(&f64_to_i64_result_formatter); + assert_formatter_matches_fixture(&owned_string_concat_formatter); + assert_formatter_matches_fixture(&print_bool_formatter); + assert_formatter_matches_fixture(&primitive_struct_fields_formatter); + assert_formatter_matches_fixture(&random_formatter); + assert_formatter_matches_fixture(&stdin_result_formatter); + assert_formatter_matches_fixture(&string_parse_i32_result_formatter); + assert_formatter_matches_fixture(&string_parse_i64_result_formatter); + assert_formatter_matches_fixture(&string_parse_f64_result_formatter); + assert_formatter_matches_fixture(&string_parse_bool_result_formatter); + assert_formatter_matches_fixture(&numeric_struct_fields_formatter); + assert_formatter_matches_fixture(&numeric_widening_formatter); + assert_formatter_matches_fixture(&result_helpers_formatter); + assert_formatter_matches_fixture(&standard_runtime_formatter); + assert_formatter_matches_fixture(&time_sleep_formatter); + assert_formatter_matches_fixture(&string_print_formatter); + assert_formatter_matches_fixture(&string_value_flow_formatter); + assert_formatter_matches_fixture(&vec_i32_formatter); + assert_lowering_inspector_matrix_matches_fixtures(&repo); + assert_lowering_inspector_matches_fixture( + &repo, + &glagol_add, + "--inspect-lowering=surface", + "add.surface.lower", + ); + assert_lowering_inspector_matches_fixture( + &repo, + &glagol_add, + "--inspect-lowering=checked", + "add.checked.lower", + ); + assert_top_level_test_tooling_matches_fixtures(&repo); + assert_array_direct_scalars_tooling_matches_fixtures( + &glagol_array_direct_scalars, + &array_direct_scalars_formatter, + ); + assert_array_direct_scalars_value_flow_tooling_matches_fixtures( + &glagol_array_direct_scalars_value_flow, + &array_direct_scalars_value_flow_formatter, + ); + assert_array_string_tooling_matches_fixtures(&glagol_array_string, &array_string_formatter); + assert_array_string_value_flow_tooling_matches_fixtures( + &glagol_array_string_value_flow, + &array_string_value_flow_formatter, + ); + assert_array_tooling_matches_fixtures(&glagol_array, &array_formatter); + assert_array_value_flow_tooling_matches_fixtures( + &glagol_array_value_flow, + &array_value_flow_formatter, + ); + assert_if_tooling_matches_fixtures(&glagol_if, &if_formatter); + assert_local_variable_tooling_matches_fixtures(&glagol_local, &local_formatter); + assert_option_result_tooling_matches_fixtures(&glagol_option_result, &option_result_formatter); + assert_option_result_flow_tooling_matches_fixtures( + &glagol_option_result_flow, + &option_result_flow_formatter, + ); + assert_option_result_match_tooling_matches_fixtures( + &glagol_option_result_match, + &option_result_match_formatter, + ); + assert_option_result_payload_tooling_matches_fixtures( + &glagol_option_result_payload, + &option_result_payload_formatter, + ); + assert_enum_struct_field_tooling_matches_fixtures( + &glagol_enum_struct_field, + &enum_struct_field_formatter, + ); + assert_primitive_struct_field_tooling_matches_fixtures( + &glagol_primitive_struct_fields, + &primitive_struct_fields_formatter, + ); + assert_numeric_struct_field_tooling_matches_fixtures( + &glagol_numeric_struct_fields, + &numeric_struct_fields_formatter, + ); + assert_owned_string_concat_tooling_matches_fixtures( + &glagol_owned_string_concat, + &owned_string_concat_formatter, + ); + assert_print_bool_tooling_matches_fixtures(&glagol_print_bool, &print_bool_formatter); + assert_random_tooling_matches_fixtures(&glagol_random, &random_formatter); + assert_stdin_result_tooling_matches_fixtures(&glagol_stdin_result, &stdin_result_formatter); + assert_string_parse_i32_result_tooling_matches_fixtures( + &glagol_string_parse_i32_result, + &string_parse_i32_result_formatter, + ); + assert_string_parse_i64_result_tooling_matches_fixtures( + &glagol_string_parse_i64_result, + &string_parse_i64_result_formatter, + ); + assert_string_parse_f64_result_tooling_matches_fixtures( + &glagol_string_parse_f64_result, + &string_parse_f64_result_formatter, + ); + assert_string_parse_bool_result_tooling_matches_fixtures( + &glagol_string_parse_bool_result, + &string_parse_bool_result_formatter, + ); + assert_result_f64_bool_source_flow_matches_fixture(&glagol_result_f64_bool_match); + assert_f64_to_string_tooling_matches_fixtures(&glagol_f64_to_string, &f64_to_string_formatter); + assert_f64_to_i32_result_tooling_matches_fixtures( + &glagol_f64_to_i32_result, + &f64_to_i32_result_formatter, + ); + assert_f64_to_i64_result_tooling_matches_fixtures( + &glagol_f64_to_i64_result, + &f64_to_i64_result_formatter, + ); + assert_result_helpers_tooling_matches_fixtures( + &glagol_result_helpers, + &result_helpers_formatter, + ); + assert_standard_runtime_tooling_matches_fixtures( + &glagol_standard_runtime, + &standard_runtime_formatter, + ); + assert_time_sleep_tooling_matches_fixtures(&glagol_time_sleep, &time_sleep_formatter); + assert_string_print_tooling_matches_fixtures(&glagol_string_print, &string_print_formatter); + assert_string_value_flow_tooling_matches_fixtures( + &glagol_string_value_flow, + &string_value_flow_formatter, + ); + assert_struct_tooling_matches_fixtures(&glagol_struct, &struct_formatter); + assert_struct_value_flow_tooling_matches_fixtures( + &glagol_struct_value_flow, + &struct_value_flow_formatter, + ); + assert_unsafe_tooling_matches_fixtures(&repo, &glagol_unsafe, &unsafe_formatter); + assert_vec_i32_tooling_matches_fixtures(&glagol_vec_i32, &vec_i32_formatter); + assert_while_tooling_matches_fixtures(&glagol_while, &while_formatter); + assert_project_enum_imports_tooling_matches_fixture(&glagol_project_enum_imports); + assert_project_std_layout_local_math_tooling_matches_fixture( + &glagol_project_std_layout_local_math, + ); + assert_project_std_import_math_tooling_matches_fixture(&glagol_project_std_import_math); + assert_project_std_import_result_tooling_matches_fixture(&glagol_project_std_import_result); + assert_project_std_import_option_tooling_matches_fixture(&glagol_project_std_import_option); + assert_project_std_import_time_tooling_matches_fixture(&glagol_project_std_import_time); + assert_project_std_import_random_tooling_matches_fixture(&glagol_project_std_import_random); + assert_project_std_import_env_tooling_matches_fixture(&glagol_project_std_import_env); + assert_project_std_import_fs_tooling_matches_fixture(&glagol_project_std_import_fs); + assert_project_std_import_process_tooling_matches_fixture(&glagol_project_std_import_process); + assert_project_std_import_string_tooling_matches_fixture(&glagol_project_std_import_string); + assert_project_std_import_num_tooling_matches_fixture(&glagol_project_std_import_num); + assert_project_std_import_io_tooling_matches_fixture(&glagol_project_std_import_io); + assert_project_std_import_cli_tooling_matches_fixture(&glagol_project_std_import_cli); + assert_project_std_import_vec_i32_tooling_matches_fixture(&glagol_project_std_import_vec_i32); + assert_project_std_import_vec_i64_tooling_matches_fixture(&glagol_project_std_import_vec_i64); + assert_project_std_import_vec_f64_tooling_matches_fixture(&glagol_project_std_import_vec_f64); + assert_project_std_import_vec_bool_tooling_matches_fixture(&glagol_project_std_import_vec_bool); + assert_project_std_import_vec_string_tooling_matches_fixture( + &glagol_project_std_import_vec_string, + ); + assert_workspace_std_import_option_tooling_matches_fixture(&glagol_workspace_std_import_option); + assert_project_std_layout_local_num_tooling_matches_fixture( + &glagol_project_std_layout_local_num, + ); + assert_project_std_layout_local_option_tooling_matches_fixture( + &glagol_project_std_layout_local_option, + ); + assert_project_std_layout_local_process_tooling_matches_fixture( + &glagol_project_std_layout_local_process, + ); + assert_project_std_layout_local_result_tooling_matches_fixture( + &glagol_project_std_layout_local_result, + ); + assert_project_std_layout_local_string_tooling_matches_fixture( + &glagol_project_std_layout_local_string, + ); + assert_project_std_layout_local_time_tooling_matches_fixture( + &glagol_project_std_layout_local_time, + ); + assert_project_std_layout_local_random_tooling_matches_fixture( + &glagol_project_std_layout_local_random, + ); + assert_project_std_layout_local_env_tooling_matches_fixture( + &glagol_project_std_layout_local_env, + ); + assert_project_std_layout_local_fs_tooling_matches_fixture(&glagol_project_std_layout_local_fs); + assert_project_std_layout_local_io_tooling_matches_fixture(&glagol_project_std_layout_local_io); + assert_project_std_layout_local_cli_tooling_matches_fixture( + &glagol_project_std_layout_local_cli, + ); + assert_project_std_layout_local_vec_i32_tooling_matches_fixture( + &glagol_project_std_layout_local_vec_i32, + ); + assert_project_std_layout_local_vec_i64_tooling_matches_fixture( + &glagol_project_std_layout_local_vec_i64, + ); + assert_project_std_layout_local_vec_f64_tooling_matches_fixture( + &glagol_project_std_layout_local_vec_f64, + ); + assert_project_std_layout_local_vec_bool_tooling_matches_fixture( + &glagol_project_std_layout_local_vec_bool, + ); + assert_project_std_layout_local_vec_string_tooling_matches_fixture( + &glagol_project_std_layout_local_vec_string, + ); + assert_math_loop_benchmark_scaffold_is_promotable(&glagol_benchmark_math_loop); + assert_named_benchmark_scaffold_is_promotable( + &glagol_benchmark_branch_loop, + "branch-loop", + "branch_loop", + ); + assert_named_benchmark_scaffold_is_promotable( + &glagol_benchmark_parse_loop, + "parse-loop", + "parse_loop", + ); + assert_named_benchmark_scaffold_is_promotable( + &glagol_benchmark_array_index_loop, + "array-index-loop", + "array_index_loop", + ); + assert_named_benchmark_scaffold_is_promotable( + &glagol_benchmark_string_eq_loop, + "string-eq-loop", + "string_eq_loop", + ); + assert_named_benchmark_scaffold_is_promotable( + &glagol_benchmark_array_struct_field_loop, + "array-struct-field-loop", + "array_struct_field_loop", + ); + assert_named_benchmark_scaffold_is_promotable( + &glagol_benchmark_enum_struct_payload_loop, + "enum-struct-payload-loop", + "enum_struct_payload_loop", + ); + assert_named_benchmark_scaffold_is_promotable( + &glagol_benchmark_vec_i32_index_loop, + "vec-i32-index-loop", + "vec_i32_index_loop", + ); + assert_named_benchmark_scaffold_is_promotable( + &glagol_benchmark_vec_string_eq_loop, + "vec-string-eq-loop", + "vec_string_eq_loop", + ); + assert_installed_standard_library_discovery_is_promotable(&repo); + assert_diagnostic_snapshots_are_promotable(&repo); +} + +#[test] +#[ignore = "requires a Slovo monorepo checkout; default glagol tests stay standalone"] +fn hermeticum_slovo_fixture_alignment_is_preserved() { + let repo = repo_root(); + let slovo_supported_add = repo.join("docs/language/examples/supported/add.slo"); + let slovo_supported_array_direct_scalars = + repo.join("docs/language/examples/supported/array-direct-scalars.slo"); + let slovo_supported_array_direct_scalars_value_flow = + repo.join("docs/language/examples/supported/array-direct-scalars-value-flow.slo"); + let slovo_supported_array_enum = repo.join("docs/language/examples/supported/array-enum.slo"); + let slovo_supported_array_string = + repo.join("docs/language/examples/supported/array-string.slo"); + let slovo_supported_array_string_value_flow = + repo.join("docs/language/examples/supported/array-string-value-flow.slo"); + let slovo_supported_array_struct_elements = + repo.join("docs/language/examples/supported/array-struct-elements.slo"); + let slovo_supported_array = repo.join("docs/language/examples/supported/array.slo"); + let slovo_supported_array_value_flow = + repo.join("docs/language/examples/supported/array-value-flow.slo"); + let slovo_supported_boolean_logic = + repo.join("docs/language/examples/supported/boolean-logic.slo"); + let slovo_supported_checked_i64_to_i32 = + repo.join("docs/language/examples/supported/checked-i64-to-i32-conversion.slo"); + let slovo_supported_enum_basic = repo.join("docs/language/examples/supported/enum-basic.slo"); + let slovo_supported_enum_payload_direct_scalars = + repo.join("docs/language/examples/supported/enum-payload-direct-scalars.slo"); + let slovo_supported_enum_payload_structs = + repo.join("docs/language/examples/supported/enum-payload-structs.slo"); + let slovo_supported_enum_payload_i32 = + repo.join("docs/language/examples/supported/enum-payload-i32.slo"); + let slovo_supported_enum_struct_fields = + repo.join("docs/language/examples/supported/enum-struct-fields.slo"); + let slovo_supported_host_io = repo.join("docs/language/examples/supported/host-io.slo"); + let slovo_supported_host_io_result = + repo.join("docs/language/examples/supported/host-io-result.slo"); + let slovo_supported_if = repo.join("docs/language/examples/supported/if.slo"); + let slovo_supported_integer_bitwise = + repo.join("docs/language/examples/supported/integer-bitwise.slo"); + let slovo_supported_integer_remainder = + repo.join("docs/language/examples/supported/integer-remainder.slo"); + let slovo_supported_integer_to_string = + repo.join("docs/language/examples/supported/integer-to-string.slo"); + let slovo_supported_f64_to_string = + repo.join("docs/language/examples/supported/f64-to-string.slo"); + let slovo_supported_f64_to_i32_result = + repo.join("docs/language/examples/supported/f64-to-i32-result.slo"); + let slovo_supported_f64_to_i64_result = + repo.join("docs/language/examples/supported/f64-to-i64-result.slo"); + let slovo_supported_composite = + repo.join("docs/language/examples/supported/composite-locals.slo"); + let slovo_supported_composite_struct_fields = + repo.join("docs/language/examples/supported/composite-struct-fields.slo"); + let slovo_supported_test = repo.join("docs/language/examples/supported/top-level-test.slo"); + let slovo_supported_local = repo.join("docs/language/examples/supported/local-variables.slo"); + let slovo_supported_numeric_struct_fields = + repo.join("docs/language/examples/supported/numeric-struct-fields.slo"); + let slovo_supported_numeric_widening = + repo.join("docs/language/examples/supported/numeric-widening-conversions.slo"); + let slovo_supported_option_result = + repo.join("docs/language/examples/supported/option-result.slo"); + let slovo_supported_option_result_flow = + repo.join("docs/language/examples/supported/option-result-flow.slo"); + let slovo_supported_option_result_match = + repo.join("docs/language/examples/supported/option-result-match.slo"); + let slovo_supported_option_result_payload = + repo.join("docs/language/examples/supported/option-result-payload.slo"); + let slovo_supported_owned_string_concat = + repo.join("docs/language/examples/supported/owned-string-concat.slo"); + let slovo_supported_print_bool = repo.join("docs/language/examples/supported/print-bool.slo"); + let slovo_supported_primitive_struct_fields = + repo.join("docs/language/examples/supported/primitive-struct-fields.slo"); + let slovo_supported_random = repo.join("docs/language/examples/supported/random.slo"); + let slovo_supported_stdin_result = + repo.join("docs/language/examples/supported/stdin-result.slo"); + let slovo_supported_string_parse_i32_result = + repo.join("docs/language/examples/supported/string-parse-i32-result.slo"); + let slovo_supported_string_parse_i64_result = + repo.join("docs/language/examples/supported/string-parse-i64-result.slo"); + let slovo_supported_string_parse_bool_result = + repo.join("docs/language/examples/supported/string-parse-bool-result.slo"); + let slovo_supported_result_helpers = + repo.join("docs/language/examples/supported/result-helpers.slo"); + let slovo_supported_result_f64_bool_match = + repo.join("docs/language/examples/supported/result-f64-bool-match.slo"); + let slovo_supported_standard_runtime = + repo.join("docs/language/examples/supported/standard-runtime.slo"); + let slovo_supported_time_sleep = repo.join("docs/language/examples/supported/time-sleep.slo"); + let slovo_supported_string_print = + repo.join("docs/language/examples/supported/string-print.slo"); + let slovo_supported_string_value_flow = + repo.join("docs/language/examples/supported/string-value-flow.slo"); + let slovo_supported_struct = repo.join("docs/language/examples/supported/struct.slo"); + let slovo_supported_struct_value_flow = + repo.join("docs/language/examples/supported/struct-value-flow.slo"); + let slovo_supported_unsafe = repo.join("docs/language/examples/supported/unsafe.slo"); + let slovo_supported_vec_i32 = repo.join("docs/language/examples/supported/vec-i32.slo"); + let slovo_supported_vec_f64 = repo.join("docs/language/examples/supported/vec-f64.slo"); + let slovo_supported_vec_bool = repo.join("docs/language/examples/supported/vec-bool.slo"); + let slovo_supported_vec_string = repo.join("docs/language/examples/supported/vec-string.slo"); + let slovo_supported_while = repo.join("docs/language/examples/supported/while.slo"); + let slovo_project_enum_imports_manifest = + repo.join("docs/language/examples/projects/enum-imports/slovo.toml"); + let slovo_project_enum_imports_readings = + repo.join("docs/language/examples/projects/enum-imports/src/readings.slo"); + let slovo_project_enum_imports_main = + repo.join("docs/language/examples/projects/enum-imports/src/main.slo"); + let slovo_project_std_import_time_manifest = + repo.join("docs/language/examples/projects/std-import-time/slovo.toml"); + let slovo_project_std_import_time_main = + repo.join("docs/language/examples/projects/std-import-time/src/main.slo"); + let slovo_project_std_import_random_manifest = + repo.join("docs/language/examples/projects/std-import-random/slovo.toml"); + let slovo_project_std_import_random_main = + repo.join("docs/language/examples/projects/std-import-random/src/main.slo"); + let slovo_project_std_import_env_manifest = + repo.join("docs/language/examples/projects/std-import-env/slovo.toml"); + let slovo_project_std_import_env_main = + repo.join("docs/language/examples/projects/std-import-env/src/main.slo"); + let slovo_project_std_import_fs_manifest = + repo.join("docs/language/examples/projects/std-import-fs/slovo.toml"); + let slovo_project_std_import_fs_main = + repo.join("docs/language/examples/projects/std-import-fs/src/main.slo"); + let slovo_project_std_import_process_manifest = + repo.join("docs/language/examples/projects/std-import-process/slovo.toml"); + let slovo_project_std_import_process_main = + repo.join("docs/language/examples/projects/std-import-process/src/main.slo"); + let slovo_project_std_import_string_manifest = + repo.join("docs/language/examples/projects/std-import-string/slovo.toml"); + let slovo_project_std_import_string_main = + repo.join("docs/language/examples/projects/std-import-string/src/main.slo"); + let slovo_project_std_import_num_manifest = + repo.join("docs/language/examples/projects/std-import-num/slovo.toml"); + let slovo_project_std_import_num_main = + repo.join("docs/language/examples/projects/std-import-num/src/main.slo"); + let slovo_project_std_import_io_manifest = + repo.join("docs/language/examples/projects/std-import-io/slovo.toml"); + let slovo_project_std_import_io_main = + repo.join("docs/language/examples/projects/std-import-io/src/main.slo"); + let slovo_project_std_import_cli_manifest = + repo.join("docs/language/examples/projects/std-import-cli/slovo.toml"); + let slovo_project_std_import_cli_main = + repo.join("docs/language/examples/projects/std-import-cli/src/main.slo"); + let slovo_project_std_import_vec_i32_manifest = + repo.join("docs/language/examples/projects/std-import-vec_i32/slovo.toml"); + let slovo_project_std_import_vec_i32_main = + repo.join("docs/language/examples/projects/std-import-vec_i32/src/main.slo"); + let slovo_project_std_import_vec_f64_manifest = + repo.join("docs/language/examples/projects/std-import-vec_f64/slovo.toml"); + let slovo_project_std_import_vec_f64_main = + repo.join("docs/language/examples/projects/std-import-vec_f64/src/main.slo"); + let slovo_project_std_import_vec_bool_manifest = + repo.join("docs/language/examples/projects/std-import-vec_bool/slovo.toml"); + let slovo_project_std_import_vec_bool_main = + repo.join("docs/language/examples/projects/std-import-vec_bool/src/main.slo"); + let slovo_formatter_canonical = repo.join("docs/language/examples/formatter/canonical.slo"); + let slovo_formatter_array_direct_scalars = + repo.join("docs/language/examples/formatter/array-direct-scalars.slo"); + let slovo_formatter_array_direct_scalars_value_flow = + repo.join("docs/language/examples/formatter/array-direct-scalars-value-flow.slo"); + let slovo_formatter_array_enum = repo.join("docs/language/examples/formatter/array-enum.slo"); + let slovo_formatter_array_string = + repo.join("docs/language/examples/formatter/array-string.slo"); + let slovo_formatter_array_string_value_flow = + repo.join("docs/language/examples/formatter/array-string-value-flow.slo"); + let slovo_formatter_array_struct_elements = + repo.join("docs/language/examples/formatter/array-struct-elements.slo"); + let slovo_formatter_array = repo.join("docs/language/examples/formatter/array.slo"); + let slovo_formatter_array_value_flow = + repo.join("docs/language/examples/formatter/array-value-flow.slo"); + let slovo_formatter_boolean_logic = + repo.join("docs/language/examples/formatter/boolean-logic.slo"); + let slovo_formatter_checked_i64_to_i32 = + repo.join("docs/language/examples/formatter/checked-i64-to-i32-conversion.slo"); + let slovo_formatter_comments = repo.join("docs/language/examples/formatter/comments.slo"); + let slovo_formatter_enum_payload_direct_scalars = + repo.join("docs/language/examples/formatter/enum-payload-direct-scalars.slo"); + let slovo_formatter_enum_payload_structs = + repo.join("docs/language/examples/formatter/enum-payload-structs.slo"); + let slovo_formatter_enum_payload_i32 = + repo.join("docs/language/examples/formatter/enum-payload-i32.slo"); + let slovo_formatter_enum_struct_fields = + repo.join("docs/language/examples/formatter/enum-struct-fields.slo"); + let slovo_formatter_host_io = repo.join("docs/language/examples/formatter/host-io.slo"); + let slovo_formatter_host_io_result = + repo.join("docs/language/examples/formatter/host-io-result.slo"); + let slovo_formatter_if = repo.join("docs/language/examples/formatter/if.slo"); + let slovo_formatter_integer_bitwise = + repo.join("docs/language/examples/formatter/integer-bitwise.slo"); + let slovo_formatter_integer_remainder = + repo.join("docs/language/examples/formatter/integer-remainder.slo"); + let slovo_formatter_integer_to_string = + repo.join("docs/language/examples/formatter/integer-to-string.slo"); + let slovo_formatter_f64_to_string = + repo.join("docs/language/examples/formatter/f64-to-string.slo"); + let slovo_formatter_f64_to_i32_result = + repo.join("docs/language/examples/formatter/f64-to-i32-result.slo"); + let slovo_formatter_f64_to_i64_result = + repo.join("docs/language/examples/formatter/f64-to-i64-result.slo"); + let slovo_formatter_composite = + repo.join("docs/language/examples/formatter/composite-locals.slo"); + let slovo_formatter_composite_struct_fields = + repo.join("docs/language/examples/formatter/composite-struct-fields.slo"); + let slovo_formatter_test = repo.join("docs/language/examples/formatter/top-level-test.slo"); + let slovo_formatter_local = repo.join("docs/language/examples/formatter/local-variables.slo"); + let slovo_formatter_numeric_struct_fields = + repo.join("docs/language/examples/formatter/numeric-struct-fields.slo"); + let slovo_formatter_numeric_widening = + repo.join("docs/language/examples/formatter/numeric-widening-conversions.slo"); + let slovo_formatter_option_result = + repo.join("docs/language/examples/formatter/option-result.slo"); + let slovo_formatter_option_result_flow = + repo.join("docs/language/examples/formatter/option-result-flow.slo"); + let slovo_formatter_option_result_match = + repo.join("docs/language/examples/formatter/option-result-match.slo"); + let slovo_formatter_option_result_payload = + repo.join("docs/language/examples/formatter/option-result-payload.slo"); + let slovo_formatter_owned_string_concat = + repo.join("docs/language/examples/formatter/owned-string-concat.slo"); + let slovo_formatter_print_bool = repo.join("docs/language/examples/formatter/print-bool.slo"); + let slovo_formatter_primitive_struct_fields = + repo.join("docs/language/examples/formatter/primitive-struct-fields.slo"); + let slovo_formatter_random = repo.join("docs/language/examples/formatter/random.slo"); + let slovo_formatter_stdin_result = + repo.join("docs/language/examples/formatter/stdin-result.slo"); + let slovo_formatter_string_parse_i32_result = + repo.join("docs/language/examples/formatter/string-parse-i32-result.slo"); + let slovo_formatter_string_parse_i64_result = + repo.join("docs/language/examples/formatter/string-parse-i64-result.slo"); + let slovo_formatter_string_parse_bool_result = + repo.join("docs/language/examples/formatter/string-parse-bool-result.slo"); + let slovo_formatter_result_helpers = + repo.join("docs/language/examples/formatter/result-helpers.slo"); + let slovo_formatter_standard_runtime = + repo.join("docs/language/examples/formatter/standard-runtime.slo"); + let slovo_formatter_time_sleep = repo.join("docs/language/examples/formatter/time-sleep.slo"); + let slovo_formatter_string_print = + repo.join("docs/language/examples/formatter/string-print.slo"); + let slovo_formatter_string_value_flow = + repo.join("docs/language/examples/formatter/string-value-flow.slo"); + let slovo_formatter_struct = repo.join("docs/language/examples/formatter/struct.slo"); + let slovo_formatter_struct_value_flow = + repo.join("docs/language/examples/formatter/struct-value-flow.slo"); + let slovo_formatter_unsafe = repo.join("docs/language/examples/formatter/unsafe.slo"); + let slovo_formatter_vec_i32 = repo.join("docs/language/examples/formatter/vec-i32.slo"); + let slovo_formatter_vec_f64 = repo.join("docs/language/examples/formatter/vec-f64.slo"); + let slovo_formatter_vec_bool = repo.join("docs/language/examples/formatter/vec-bool.slo"); + let slovo_formatter_while = repo.join("docs/language/examples/formatter/while.slo"); + let glagol_add = repo.join("examples/add.slo"); + let glagol_array_direct_scalars = repo.join("examples/array-direct-scalars.slo"); + let glagol_array_direct_scalars_value_flow = + repo.join("examples/array-direct-scalars-value-flow.slo"); + let glagol_array_enum = repo.join("examples/array-enum.slo"); + let glagol_array_string = repo.join("examples/array-string.slo"); + let glagol_array_string_value_flow = repo.join("examples/array-string-value-flow.slo"); + let glagol_array_struct_elements = repo.join("examples/array-struct-elements.slo"); + let glagol_array = repo.join("examples/array.slo"); + let glagol_array_value_flow = repo.join("examples/array-value-flow.slo"); + let glagol_boolean_logic = repo.join("examples/boolean-logic.slo"); + let glagol_checked_i64_to_i32 = repo.join("examples/checked-i64-to-i32-conversion.slo"); + let glagol_enum_basic = repo.join("examples/enum-basic.slo"); + let glagol_enum_payload_direct_scalars = repo.join("examples/enum-payload-direct-scalars.slo"); + let glagol_enum_payload_structs = repo.join("examples/enum-payload-structs.slo"); + let glagol_enum_payload_i32 = repo.join("examples/enum-payload-i32.slo"); + let glagol_enum_struct_fields = repo.join("examples/enum-struct-fields.slo"); + let glagol_host_io = repo.join("examples/host-io.slo"); + let glagol_host_io_result = repo.join("examples/host-io-result.slo"); + let glagol_if = repo.join("examples/if.slo"); + let glagol_integer_bitwise = repo.join("examples/integer-bitwise.slo"); + let glagol_integer_remainder = repo.join("examples/integer-remainder.slo"); + let glagol_integer_to_string = repo.join("examples/integer-to-string.slo"); + let glagol_f64_to_string = repo.join("examples/f64-to-string.slo"); + let glagol_f64_to_i32_result = repo.join("examples/f64-to-i32-result.slo"); + let glagol_f64_to_i64_result = repo.join("examples/f64-to-i64-result.slo"); + let glagol_composite = repo.join("examples/composite-locals.slo"); + let glagol_composite_struct_fields = repo.join("examples/composite-struct-fields.slo"); + let glagol_local = repo.join("examples/local-variables.slo"); + let glagol_numeric_struct_fields = repo.join("examples/numeric-struct-fields.slo"); + let glagol_numeric_widening = repo.join("examples/numeric-widening-conversions.slo"); + let glagol_option_result = repo.join("examples/option-result.slo"); + let glagol_option_result_flow = repo.join("examples/option-result-flow.slo"); + let glagol_option_result_match = repo.join("examples/option-result-match.slo"); + let glagol_option_result_payload = repo.join("examples/option-result-payload.slo"); + let glagol_owned_string_concat = repo.join("examples/owned-string-concat.slo"); + let glagol_print_bool = repo.join("examples/print-bool.slo"); + let glagol_primitive_struct_fields = repo.join("examples/primitive-struct-fields.slo"); + let glagol_random = repo.join("examples/random.slo"); + let glagol_stdin_result = repo.join("examples/stdin-result.slo"); + let glagol_string_parse_i32_result = repo.join("examples/string-parse-i32-result.slo"); + let glagol_string_parse_i64_result = repo.join("examples/string-parse-i64-result.slo"); + let glagol_string_parse_bool_result = repo.join("examples/string-parse-bool-result.slo"); + let glagol_result_helpers = repo.join("examples/result-helpers.slo"); + let glagol_result_f64_bool_match = repo.join("examples/result-f64-bool-match.slo"); + let glagol_standard_runtime = repo.join("examples/standard-runtime.slo"); + let glagol_time_sleep = repo.join("examples/time-sleep.slo"); + let glagol_string_print = repo.join("examples/string-print.slo"); + let glagol_string_value_flow = repo.join("examples/string-value-flow.slo"); + let glagol_struct = repo.join("examples/struct.slo"); + let glagol_struct_value_flow = repo.join("examples/struct-value-flow.slo"); + let glagol_unsafe = repo.join("examples/unsafe.slo"); + let glagol_vec_i32 = repo.join("examples/vec-i32.slo"); + let glagol_vec_f64 = repo.join("examples/vec-f64.slo"); + let glagol_vec_bool = repo.join("examples/vec-bool.slo"); + let glagol_vec_string = repo.join("examples/vec-string.slo"); + let glagol_while = repo.join("examples/while.slo"); + let glagol_project_enum_imports_manifest = + repo.join("examples/projects/enum-imports/slovo.toml"); + let glagol_project_enum_imports_readings = + repo.join("examples/projects/enum-imports/src/readings.slo"); + let glagol_project_enum_imports_main = repo.join("examples/projects/enum-imports/src/main.slo"); + let glagol_project_std_layout_local_math_manifest = + repo.join("examples/projects/std-layout-local-math/slovo.toml"); + let glagol_project_std_layout_local_math_math = + repo.join("examples/projects/std-layout-local-math/src/math.slo"); + let glagol_project_std_layout_local_math_main = + repo.join("examples/projects/std-layout-local-math/src/main.slo"); + let glagol_project_std_layout_local_num_manifest = + repo.join("examples/projects/std-layout-local-num/slovo.toml"); + let glagol_project_std_layout_local_num_num = + repo.join("examples/projects/std-layout-local-num/src/num.slo"); + let glagol_project_std_layout_local_num_main = + repo.join("examples/projects/std-layout-local-num/src/main.slo"); + let glagol_project_std_layout_local_process_manifest = + repo.join("examples/projects/std-layout-local-process/slovo.toml"); + let glagol_project_std_layout_local_process_process = + repo.join("examples/projects/std-layout-local-process/src/process.slo"); + let glagol_project_std_layout_local_process_main = + repo.join("examples/projects/std-layout-local-process/src/main.slo"); + let glagol_project_std_layout_local_result_manifest = + repo.join("examples/projects/std-layout-local-result/slovo.toml"); + let glagol_project_std_layout_local_result_result = + repo.join("examples/projects/std-layout-local-result/src/result.slo"); + let glagol_project_std_layout_local_result_main = + repo.join("examples/projects/std-layout-local-result/src/main.slo"); + let glagol_project_std_layout_local_string_manifest = + repo.join("examples/projects/std-layout-local-string/slovo.toml"); + let glagol_project_std_layout_local_string_string = + repo.join("examples/projects/std-layout-local-string/src/string.slo"); + let glagol_project_std_layout_local_string_main = + repo.join("examples/projects/std-layout-local-string/src/main.slo"); + let glagol_project_std_layout_local_cli_manifest = + repo.join("examples/projects/std-layout-local-cli/slovo.toml"); + let glagol_project_std_layout_local_cli_cli = + repo.join("examples/projects/std-layout-local-cli/src/cli.slo"); + let glagol_project_std_layout_local_cli_process = + repo.join("examples/projects/std-layout-local-cli/src/process.slo"); + let glagol_project_std_layout_local_cli_string = + repo.join("examples/projects/std-layout-local-cli/src/string.slo"); + let glagol_project_std_layout_local_cli_main = + repo.join("examples/projects/std-layout-local-cli/src/main.slo"); + let glagol_project_std_layout_local_vec_i32_manifest = + repo.join("examples/projects/std-layout-local-vec_i32/slovo.toml"); + let glagol_project_std_layout_local_vec_i32_vec_i32 = + repo.join("examples/projects/std-layout-local-vec_i32/src/vec_i32.slo"); + let glagol_project_std_layout_local_vec_i32_main = + repo.join("examples/projects/std-layout-local-vec_i32/src/main.slo"); + let glagol_project_std_import_time_manifest = + repo.join("examples/projects/std-import-time/slovo.toml"); + let glagol_project_std_import_time_main = + repo.join("examples/projects/std-import-time/src/main.slo"); + let glagol_project_std_import_random_manifest = + repo.join("examples/projects/std-import-random/slovo.toml"); + let glagol_project_std_import_random_main = + repo.join("examples/projects/std-import-random/src/main.slo"); + let glagol_project_std_import_env_manifest = + repo.join("examples/projects/std-import-env/slovo.toml"); + let glagol_project_std_import_env_main = + repo.join("examples/projects/std-import-env/src/main.slo"); + let glagol_project_std_import_fs_manifest = + repo.join("examples/projects/std-import-fs/slovo.toml"); + let glagol_project_std_import_fs_main = + repo.join("examples/projects/std-import-fs/src/main.slo"); + let glagol_project_std_import_process_manifest = + repo.join("examples/projects/std-import-process/slovo.toml"); + let glagol_project_std_import_process_main = + repo.join("examples/projects/std-import-process/src/main.slo"); + let glagol_project_std_import_string_manifest = + repo.join("examples/projects/std-import-string/slovo.toml"); + let glagol_project_std_import_string_main = + repo.join("examples/projects/std-import-string/src/main.slo"); + let glagol_project_std_import_num_manifest = + repo.join("examples/projects/std-import-num/slovo.toml"); + let glagol_project_std_import_num_main = + repo.join("examples/projects/std-import-num/src/main.slo"); + let glagol_project_std_import_io_manifest = + repo.join("examples/projects/std-import-io/slovo.toml"); + let glagol_project_std_import_io_main = + repo.join("examples/projects/std-import-io/src/main.slo"); + let glagol_project_std_import_cli_manifest = + repo.join("examples/projects/std-import-cli/slovo.toml"); + let glagol_project_std_import_cli_main = + repo.join("examples/projects/std-import-cli/src/main.slo"); + let glagol_project_std_import_vec_i32_manifest = + repo.join("examples/projects/std-import-vec_i32/slovo.toml"); + let glagol_project_std_import_vec_i32_main = + repo.join("examples/projects/std-import-vec_i32/src/main.slo"); + let glagol_project_std_import_vec_f64_manifest = + repo.join("examples/projects/std-import-vec_f64/slovo.toml"); + let glagol_project_std_import_vec_f64_main = + repo.join("examples/projects/std-import-vec_f64/src/main.slo"); + let glagol_project_std_import_vec_bool_manifest = + repo.join("examples/projects/std-import-vec_bool/slovo.toml"); + let glagol_project_std_import_vec_bool_main = + repo.join("examples/projects/std-import-vec_bool/src/main.slo"); + let glagol_test = repo.join("tests/top-level-test.slo"); + let glagol_formatter_canonical = repo.join("tests/canonical.fmt"); + let glagol_formatter_array_direct_scalars = repo.join("tests/array-direct-scalars.slo"); + let glagol_formatter_array_direct_scalars_value_flow = + repo.join("tests/array-direct-scalars-value-flow.slo"); + let glagol_formatter_array_enum = repo.join("tests/array-enum.slo"); + let glagol_formatter_array_string = repo.join("tests/array-string.slo"); + let glagol_formatter_array_string_value_flow = repo.join("tests/array-string-value-flow.slo"); + let glagol_formatter_array_struct_elements = repo.join("tests/array-struct-elements.slo"); + let glagol_formatter_array = repo.join("tests/array.slo"); + let glagol_formatter_array_value_flow = repo.join("tests/array-value-flow.slo"); + let glagol_formatter_boolean_logic = repo.join("tests/boolean-logic.slo"); + let glagol_formatter_checked_i64_to_i32 = repo.join("tests/checked-i64-to-i32-conversion.slo"); + let glagol_formatter_comments = repo.join("tests/comments.slo"); + let glagol_formatter_enum_payload_direct_scalars = + repo.join("tests/enum-payload-direct-scalars.slo"); + let glagol_formatter_enum_payload_structs = repo.join("tests/enum-payload-structs.slo"); + let glagol_formatter_enum_payload_i32 = repo.join("tests/enum-payload-i32.slo"); + let glagol_formatter_enum_struct_fields = repo.join("tests/enum-struct-fields.slo"); + let glagol_formatter_host_io = repo.join("tests/host-io.slo"); + let glagol_formatter_host_io_result = repo.join("tests/host-io-result.slo"); + let glagol_formatter_if = repo.join("tests/if.slo"); + let glagol_formatter_integer_bitwise = repo.join("tests/integer-bitwise.slo"); + let glagol_formatter_integer_remainder = repo.join("tests/integer-remainder.slo"); + let glagol_formatter_integer_to_string = repo.join("tests/integer-to-string.slo"); + let glagol_formatter_f64_to_string = repo.join("tests/f64-to-string.slo"); + let glagol_formatter_f64_to_i32_result = repo.join("tests/f64-to-i32-result.slo"); + let glagol_formatter_f64_to_i64_result = repo.join("tests/f64-to-i64-result.slo"); + let glagol_formatter_composite = repo.join("tests/composite-locals.slo"); + let glagol_formatter_composite_struct_fields = repo.join("tests/composite-struct-fields.slo"); + let glagol_formatter_test = repo.join("tests/top-level-test.fmt"); + let glagol_formatter_local = repo.join("tests/local-variables.slo"); + let glagol_formatter_numeric_struct_fields = repo.join("tests/numeric-struct-fields.slo"); + let glagol_formatter_numeric_widening = repo.join("tests/numeric-widening-conversions.slo"); + let glagol_formatter_option_result = repo.join("tests/option-result.slo"); + let glagol_formatter_option_result_flow = repo.join("tests/option-result-flow.slo"); + let glagol_formatter_option_result_match = repo.join("tests/option-result-match.slo"); + let glagol_formatter_option_result_payload = repo.join("tests/option-result-payload.slo"); + let glagol_formatter_owned_string_concat = repo.join("tests/owned-string-concat.slo"); + let glagol_formatter_print_bool = repo.join("tests/print-bool.slo"); + let glagol_formatter_primitive_struct_fields = repo.join("tests/primitive-struct-fields.slo"); + let glagol_formatter_random = repo.join("tests/random.slo"); + let glagol_formatter_stdin_result = repo.join("tests/stdin-result.slo"); + let glagol_formatter_string_parse_i32_result = repo.join("tests/string-parse-i32-result.slo"); + let glagol_formatter_string_parse_i64_result = repo.join("tests/string-parse-i64-result.slo"); + let glagol_formatter_string_parse_bool_result = repo.join("tests/string-parse-bool-result.slo"); + let glagol_formatter_result_helpers = repo.join("tests/result-helpers.slo"); + let glagol_formatter_standard_runtime = repo.join("tests/standard-runtime.slo"); + let glagol_formatter_time_sleep = repo.join("tests/time-sleep.slo"); + let glagol_formatter_string_print = repo.join("tests/string-print.slo"); + let glagol_formatter_string_value_flow = repo.join("tests/string-value-flow.slo"); + let glagol_formatter_struct = repo.join("tests/struct.slo"); + let glagol_formatter_struct_value_flow = repo.join("tests/struct-value-flow.slo"); + let glagol_formatter_unsafe = repo.join("tests/unsafe.slo"); + let glagol_formatter_vec_i32 = repo.join("tests/vec-i32.slo"); + let glagol_formatter_while = repo.join("tests/while.slo"); + + assert_slovo_supported_fixtures(&repo); + assert_slovo_formatter_fixtures(&repo); + assert_slovo_v0_compatibility_fixtures(&repo); + assert_source_eq(&glagol_add, &slovo_supported_add); + assert_source_eq( + &glagol_array_direct_scalars, + &slovo_supported_array_direct_scalars, + ); + assert_source_eq( + &glagol_array_direct_scalars_value_flow, + &slovo_supported_array_direct_scalars_value_flow, + ); + assert_source_eq(&glagol_array_enum, &slovo_supported_array_enum); + assert_source_eq(&glagol_array_string, &slovo_supported_array_string); + assert_source_eq( + &glagol_array_string_value_flow, + &slovo_supported_array_string_value_flow, + ); + assert_source_eq( + &glagol_array_struct_elements, + &slovo_supported_array_struct_elements, + ); + assert_source_eq(&glagol_array, &slovo_supported_array); + assert_source_eq(&glagol_array_value_flow, &slovo_supported_array_value_flow); + assert_source_eq(&glagol_boolean_logic, &slovo_supported_boolean_logic); + assert_source_eq( + &glagol_checked_i64_to_i32, + &slovo_supported_checked_i64_to_i32, + ); + assert_source_eq(&glagol_enum_basic, &slovo_supported_enum_basic); + assert_source_eq( + &glagol_enum_payload_direct_scalars, + &slovo_supported_enum_payload_direct_scalars, + ); + assert_source_eq( + &glagol_enum_payload_structs, + &slovo_supported_enum_payload_structs, + ); + assert_source_eq(&glagol_enum_payload_i32, &slovo_supported_enum_payload_i32); + assert_source_eq( + &glagol_enum_struct_fields, + &slovo_supported_enum_struct_fields, + ); + assert_source_eq(&glagol_host_io, &slovo_supported_host_io); + assert_source_eq(&glagol_host_io_result, &slovo_supported_host_io_result); + assert_source_eq(&glagol_if, &slovo_supported_if); + assert_source_eq(&glagol_integer_bitwise, &slovo_supported_integer_bitwise); + assert_source_eq( + &glagol_integer_remainder, + &slovo_supported_integer_remainder, + ); + assert_source_eq( + &glagol_integer_to_string, + &slovo_supported_integer_to_string, + ); + assert_source_eq(&glagol_f64_to_string, &slovo_supported_f64_to_string); + assert_source_eq( + &glagol_f64_to_i32_result, + &slovo_supported_f64_to_i32_result, + ); + assert_source_eq( + &glagol_f64_to_i64_result, + &slovo_supported_f64_to_i64_result, + ); + assert_source_eq(&glagol_composite, &slovo_supported_composite); + assert_source_eq( + &glagol_composite_struct_fields, + &slovo_supported_composite_struct_fields, + ); + assert_source_eq(&glagol_test, &slovo_supported_test); + assert_source_eq(&glagol_local, &slovo_supported_local); + assert_source_eq( + &glagol_numeric_struct_fields, + &slovo_supported_numeric_struct_fields, + ); + assert_source_eq(&glagol_numeric_widening, &slovo_supported_numeric_widening); + assert_source_eq(&glagol_option_result, &slovo_supported_option_result); + assert_source_eq( + &glagol_option_result_flow, + &slovo_supported_option_result_flow, + ); + assert_source_eq( + &glagol_option_result_match, + &slovo_supported_option_result_match, + ); + assert_source_eq( + &glagol_option_result_payload, + &slovo_supported_option_result_payload, + ); + assert_source_eq( + &glagol_owned_string_concat, + &slovo_supported_owned_string_concat, + ); + assert_source_eq(&glagol_print_bool, &slovo_supported_print_bool); + assert_source_eq( + &glagol_primitive_struct_fields, + &slovo_supported_primitive_struct_fields, + ); + assert_source_eq(&glagol_random, &slovo_supported_random); + assert_source_eq(&glagol_stdin_result, &slovo_supported_stdin_result); + assert_source_eq( + &glagol_string_parse_i32_result, + &slovo_supported_string_parse_i32_result, + ); + assert_source_eq( + &glagol_string_parse_i64_result, + &slovo_supported_string_parse_i64_result, + ); + assert_source_eq( + &glagol_string_parse_bool_result, + &slovo_supported_string_parse_bool_result, + ); + assert_source_eq(&glagol_result_helpers, &slovo_supported_result_helpers); + assert_source_eq( + &glagol_result_f64_bool_match, + &slovo_supported_result_f64_bool_match, + ); + assert_source_eq(&glagol_standard_runtime, &slovo_supported_standard_runtime); + assert_source_eq(&glagol_time_sleep, &slovo_supported_time_sleep); + assert_source_eq(&glagol_string_print, &slovo_supported_string_print); + assert_source_eq( + &glagol_string_value_flow, + &slovo_supported_string_value_flow, + ); + assert_source_eq(&glagol_struct, &slovo_supported_struct); + assert_source_eq( + &glagol_struct_value_flow, + &slovo_supported_struct_value_flow, + ); + assert_source_eq(&glagol_unsafe, &slovo_supported_unsafe); + assert_source_eq(&glagol_vec_i32, &slovo_supported_vec_i32); + assert_source_eq(&glagol_vec_f64, &slovo_supported_vec_f64); + assert_source_eq(&glagol_vec_bool, &slovo_supported_vec_bool); + assert_source_eq(&glagol_vec_string, &slovo_supported_vec_string); + assert_source_eq(&glagol_while, &slovo_supported_while); + assert_source_eq( + &glagol_project_enum_imports_manifest, + &slovo_project_enum_imports_manifest, + ); + assert_source_eq( + &glagol_project_enum_imports_readings, + &slovo_project_enum_imports_readings, + ); + assert_source_eq( + &glagol_project_enum_imports_main, + &slovo_project_enum_imports_main, + ); + assert!(glagol_project_std_layout_local_math_manifest.is_file()); + assert!(glagol_project_std_layout_local_math_math.is_file()); + assert!(glagol_project_std_layout_local_math_main.is_file()); + assert!(glagol_project_std_layout_local_num_manifest.is_file()); + assert!(glagol_project_std_layout_local_num_num.is_file()); + assert!(glagol_project_std_layout_local_num_main.is_file()); + assert!(glagol_project_std_layout_local_process_manifest.is_file()); + assert!(glagol_project_std_layout_local_process_process.is_file()); + assert!(glagol_project_std_layout_local_process_main.is_file()); + assert!(glagol_project_std_layout_local_result_manifest.is_file()); + assert!(glagol_project_std_layout_local_result_result.is_file()); + assert!(glagol_project_std_layout_local_result_main.is_file()); + assert!(glagol_project_std_layout_local_string_manifest.is_file()); + assert!(glagol_project_std_layout_local_string_string.is_file()); + assert!(glagol_project_std_layout_local_string_main.is_file()); + assert!(glagol_project_std_layout_local_cli_manifest.is_file()); + assert!(glagol_project_std_layout_local_cli_cli.is_file()); + assert!(glagol_project_std_layout_local_cli_process.is_file()); + assert!(glagol_project_std_layout_local_cli_string.is_file()); + assert!(glagol_project_std_layout_local_cli_main.is_file()); + assert!(glagol_project_std_layout_local_vec_i32_manifest.is_file()); + assert!(glagol_project_std_layout_local_vec_i32_vec_i32.is_file()); + assert!(glagol_project_std_layout_local_vec_i32_main.is_file()); + assert_source_eq( + &glagol_project_std_import_time_manifest, + &slovo_project_std_import_time_manifest, + ); + assert_source_eq( + &glagol_project_std_import_time_main, + &slovo_project_std_import_time_main, + ); + assert_source_eq( + &glagol_project_std_import_random_manifest, + &slovo_project_std_import_random_manifest, + ); + assert_source_eq( + &glagol_project_std_import_random_main, + &slovo_project_std_import_random_main, + ); + assert_source_eq( + &glagol_project_std_import_env_manifest, + &slovo_project_std_import_env_manifest, + ); + assert_source_eq( + &glagol_project_std_import_env_main, + &slovo_project_std_import_env_main, + ); + assert_source_eq( + &glagol_project_std_import_fs_manifest, + &slovo_project_std_import_fs_manifest, + ); + assert_source_eq( + &glagol_project_std_import_fs_main, + &slovo_project_std_import_fs_main, + ); + assert_source_eq( + &glagol_project_std_import_process_manifest, + &slovo_project_std_import_process_manifest, + ); + assert_source_eq( + &glagol_project_std_import_process_main, + &slovo_project_std_import_process_main, + ); + assert_source_eq( + &glagol_project_std_import_string_manifest, + &slovo_project_std_import_string_manifest, + ); + assert_source_eq( + &glagol_project_std_import_string_main, + &slovo_project_std_import_string_main, + ); + assert_source_eq( + &glagol_project_std_import_num_manifest, + &slovo_project_std_import_num_manifest, + ); + assert_source_eq( + &glagol_project_std_import_num_main, + &slovo_project_std_import_num_main, + ); + assert_source_eq( + &glagol_project_std_import_io_manifest, + &slovo_project_std_import_io_manifest, + ); + assert_source_eq( + &glagol_project_std_import_io_main, + &slovo_project_std_import_io_main, + ); + assert_source_eq( + &glagol_project_std_import_cli_manifest, + &slovo_project_std_import_cli_manifest, + ); + assert_source_eq( + &glagol_project_std_import_cli_main, + &slovo_project_std_import_cli_main, + ); + assert_source_eq( + &glagol_project_std_import_vec_i32_manifest, + &slovo_project_std_import_vec_i32_manifest, + ); + assert_source_eq( + &glagol_project_std_import_vec_i32_main, + &slovo_project_std_import_vec_i32_main, + ); + assert_source_eq( + &glagol_project_std_import_vec_f64_manifest, + &slovo_project_std_import_vec_f64_manifest, + ); + assert_source_eq( + &glagol_project_std_import_vec_f64_main, + &slovo_project_std_import_vec_f64_main, + ); + assert_source_eq( + &glagol_project_std_import_vec_bool_manifest, + &slovo_project_std_import_vec_bool_manifest, + ); + assert_source_eq( + &glagol_project_std_import_vec_bool_main, + &slovo_project_std_import_vec_bool_main, + ); + assert_source_eq(&glagol_formatter_canonical, &slovo_formatter_canonical); + assert_source_eq( + &glagol_formatter_array_direct_scalars, + &slovo_formatter_array_direct_scalars, + ); + assert_source_eq( + &glagol_formatter_array_direct_scalars_value_flow, + &slovo_formatter_array_direct_scalars_value_flow, + ); + assert_source_eq(&glagol_formatter_array_enum, &slovo_formatter_array_enum); + assert_source_eq( + &glagol_formatter_array_string, + &slovo_formatter_array_string, + ); + assert_source_eq( + &glagol_formatter_array_string_value_flow, + &slovo_formatter_array_string_value_flow, + ); + assert_source_eq( + &glagol_formatter_array_struct_elements, + &slovo_formatter_array_struct_elements, + ); + assert_source_eq(&glagol_formatter_array, &slovo_formatter_array); + assert_source_eq( + &glagol_formatter_array_value_flow, + &slovo_formatter_array_value_flow, + ); + assert_source_eq( + &glagol_formatter_boolean_logic, + &slovo_formatter_boolean_logic, + ); + assert_source_eq( + &glagol_formatter_checked_i64_to_i32, + &slovo_formatter_checked_i64_to_i32, + ); + assert_source_eq(&glagol_formatter_comments, &slovo_formatter_comments); + assert_source_eq( + &glagol_formatter_enum_payload_direct_scalars, + &slovo_formatter_enum_payload_direct_scalars, + ); + assert_source_eq( + &glagol_formatter_enum_payload_structs, + &slovo_formatter_enum_payload_structs, + ); + assert_source_eq( + &glagol_formatter_enum_payload_i32, + &slovo_formatter_enum_payload_i32, + ); + assert_source_eq( + &glagol_formatter_enum_struct_fields, + &slovo_formatter_enum_struct_fields, + ); + assert_source_eq(&glagol_formatter_host_io, &slovo_formatter_host_io); + assert_source_eq( + &glagol_formatter_host_io_result, + &slovo_formatter_host_io_result, + ); + assert_source_eq(&glagol_formatter_if, &slovo_formatter_if); + assert_source_eq( + &glagol_formatter_integer_bitwise, + &slovo_formatter_integer_bitwise, + ); + assert_source_eq( + &glagol_formatter_integer_remainder, + &slovo_formatter_integer_remainder, + ); + assert_source_eq( + &glagol_formatter_integer_to_string, + &slovo_formatter_integer_to_string, + ); + assert_source_eq( + &glagol_formatter_f64_to_string, + &slovo_formatter_f64_to_string, + ); + assert_source_eq( + &glagol_formatter_f64_to_i32_result, + &slovo_formatter_f64_to_i32_result, + ); + assert_source_eq( + &glagol_formatter_f64_to_i64_result, + &slovo_formatter_f64_to_i64_result, + ); + assert_source_eq(&glagol_formatter_composite, &slovo_formatter_composite); + assert_source_eq( + &glagol_formatter_composite_struct_fields, + &slovo_formatter_composite_struct_fields, + ); + assert_source_eq(&glagol_formatter_test, &slovo_formatter_test); + assert_source_eq(&glagol_formatter_local, &slovo_formatter_local); + assert_source_eq( + &glagol_formatter_numeric_struct_fields, + &slovo_formatter_numeric_struct_fields, + ); + assert_source_eq( + &glagol_formatter_numeric_widening, + &slovo_formatter_numeric_widening, + ); + assert_source_eq( + &glagol_formatter_option_result, + &slovo_formatter_option_result, + ); + assert_source_eq( + &glagol_formatter_option_result_flow, + &slovo_formatter_option_result_flow, + ); + assert_source_eq( + &glagol_formatter_option_result_match, + &slovo_formatter_option_result_match, + ); + assert_source_eq( + &glagol_formatter_option_result_payload, + &slovo_formatter_option_result_payload, + ); + assert_source_eq( + &glagol_formatter_owned_string_concat, + &slovo_formatter_owned_string_concat, + ); + assert_source_eq(&glagol_formatter_print_bool, &slovo_formatter_print_bool); + assert_source_eq( + &glagol_formatter_primitive_struct_fields, + &slovo_formatter_primitive_struct_fields, + ); + assert_source_eq(&glagol_formatter_random, &slovo_formatter_random); + assert_source_eq( + &glagol_formatter_stdin_result, + &slovo_formatter_stdin_result, + ); + assert_source_eq( + &glagol_formatter_string_parse_i32_result, + &slovo_formatter_string_parse_i32_result, + ); + assert_source_eq( + &glagol_formatter_string_parse_i64_result, + &slovo_formatter_string_parse_i64_result, + ); + assert_source_eq( + &glagol_formatter_string_parse_bool_result, + &slovo_formatter_string_parse_bool_result, + ); + assert_source_eq( + &glagol_formatter_result_helpers, + &slovo_formatter_result_helpers, + ); + assert_source_eq( + &glagol_formatter_standard_runtime, + &slovo_formatter_standard_runtime, + ); + assert_source_eq(&glagol_formatter_time_sleep, &slovo_formatter_time_sleep); + assert_source_eq( + &glagol_formatter_string_print, + &slovo_formatter_string_print, + ); + assert_source_eq( + &glagol_formatter_string_value_flow, + &slovo_formatter_string_value_flow, + ); + assert_source_eq(&glagol_formatter_struct, &slovo_formatter_struct); + assert_source_eq( + &glagol_formatter_struct_value_flow, + &slovo_formatter_struct_value_flow, + ); + assert_source_eq(&glagol_formatter_unsafe, &slovo_formatter_unsafe); + assert_source_eq(&glagol_formatter_vec_i32, &slovo_formatter_vec_i32); + assert_source_eq(&glagol_vec_f64, &slovo_formatter_vec_f64); + assert_source_eq(&glagol_vec_bool, &slovo_formatter_vec_bool); + assert_source_eq(&glagol_formatter_while, &slovo_formatter_while); +} + +#[test] +#[ignore = "requires sibling slovo std/ contract files; default glagol tests stay standalone"] +fn hermeticum_slovo_std_source_layout_alpha_is_preserved() { + let repo = repo_root(); + let slovo_std = repo.join("lib/std"); + + assert_slovo_std_source_layout_alpha(&repo, &slovo_std); +} + +fn assert_supported_fixtures(repo: &Path) { + let supported_dir = repo.join("examples"); + let mut files = read_dir_files(&supported_dir); + files.sort(); + + assert_eq!( + files, + vec![ + supported_dir.join("add.slo"), + supported_dir.join("array-direct-scalars-value-flow.slo"), + supported_dir.join("array-direct-scalars.slo"), + supported_dir.join("array-enum.slo"), + supported_dir.join("array-string-value-flow.slo"), + supported_dir.join("array-string.slo"), + supported_dir.join("array-struct-elements.slo"), + supported_dir.join("array-struct-fields.slo"), + supported_dir.join("array-value-flow.slo"), + supported_dir.join("array.slo"), + supported_dir.join("boolean-logic.slo"), + supported_dir.join("checked-i64-to-i32-conversion.slo"), + supported_dir.join("composite-locals.slo"), + supported_dir.join("composite-struct-fields.slo"), + supported_dir.join("enum-basic.slo"), + supported_dir.join("enum-payload-direct-scalars.slo"), + supported_dir.join("enum-payload-i32.slo"), + supported_dir.join("enum-payload-structs.slo"), + supported_dir.join("enum-struct-fields.slo"), + supported_dir.join("f64-numeric-primitive.slo"), + supported_dir.join("f64-to-i32-result.slo"), + supported_dir.join("f64-to-i64-result.slo"), + supported_dir.join("f64-to-string.slo"), + supported_dir.join("host-io-result.slo"), + supported_dir.join("host-io.slo"), + supported_dir.join("i64-numeric-primitive.slo"), + supported_dir.join("if.slo"), + supported_dir.join("integer-bitwise.slo"), + supported_dir.join("integer-remainder.slo"), + supported_dir.join("integer-to-string.slo"), + supported_dir.join("local-variables.slo"), + supported_dir.join("numeric-struct-fields.slo"), + supported_dir.join("numeric-widening-conversions.slo"), + supported_dir.join("option-result-flow.slo"), + supported_dir.join("option-result-match.slo"), + supported_dir.join("option-result-payload.slo"), + supported_dir.join("option-result.slo"), + supported_dir.join("owned-string-concat.slo"), + supported_dir.join("primitive-struct-fields.slo"), + supported_dir.join("print-bool.slo"), + supported_dir.join("random.slo"), + supported_dir.join("result-f64-bool-match.slo"), + supported_dir.join("result-helpers.slo"), + supported_dir.join("standard-runtime.slo"), + supported_dir.join("stdin-result.slo"), + supported_dir.join("string-parse-bool-result.slo"), + supported_dir.join("string-parse-f64-result.slo"), + supported_dir.join("string-parse-i32-result.slo"), + supported_dir.join("string-parse-i64-result.slo"), + supported_dir.join("string-parse-u32-result.slo"), + supported_dir.join("string-parse-u64-result.slo"), + supported_dir.join("string-print.slo"), + supported_dir.join("string-value-flow.slo"), + supported_dir.join("struct-value-flow.slo"), + supported_dir.join("struct.slo"), + supported_dir.join("time-sleep.slo"), + supported_dir.join("u32-numeric-primitive.slo"), + supported_dir.join("u64-numeric-primitive.slo"), + supported_dir.join("unsafe.slo"), + supported_dir.join("unsigned-integer-to-string.slo"), + supported_dir.join("vec-bool.slo"), + supported_dir.join("vec-f64.slo"), + supported_dir.join("vec-i32.slo"), + supported_dir.join("vec-string.slo"), + supported_dir.join("while.slo"), + ], + "promotion gate expects Glagol supported fixtures to match the current supported surface" + ); +} + +fn assert_slovo_supported_fixtures(repo: &Path) { + let supported_dir = repo.join("docs/language/examples/supported"); + let mut files = read_dir_files(&supported_dir); + files.sort(); + + assert_eq!( + files, + vec![ + supported_dir.join("add.slo"), + supported_dir.join("array-direct-scalars-value-flow.slo"), + supported_dir.join("array-direct-scalars.slo"), + supported_dir.join("array-enum.slo"), + supported_dir.join("array-string-value-flow.slo"), + supported_dir.join("array-string.slo"), + supported_dir.join("array-struct-elements.slo"), + supported_dir.join("array-struct-fields.slo"), + supported_dir.join("array-value-flow.slo"), + supported_dir.join("array.slo"), + supported_dir.join("boolean-logic.slo"), + supported_dir.join("checked-i64-to-i32-conversion.slo"), + supported_dir.join("composite-locals.slo"), + supported_dir.join("composite-struct-fields.slo"), + supported_dir.join("enum-basic.slo"), + supported_dir.join("enum-payload-direct-scalars.slo"), + supported_dir.join("enum-payload-i32.slo"), + supported_dir.join("enum-payload-structs.slo"), + supported_dir.join("enum-struct-fields.slo"), + supported_dir.join("f64-numeric-primitive.slo"), + supported_dir.join("f64-to-i32-result.slo"), + supported_dir.join("f64-to-i64-result.slo"), + supported_dir.join("f64-to-string.slo"), + supported_dir.join("host-io-result.slo"), + supported_dir.join("host-io.slo"), + supported_dir.join("i64-numeric-primitive.slo"), + supported_dir.join("if.slo"), + supported_dir.join("integer-bitwise.slo"), + supported_dir.join("integer-remainder.slo"), + supported_dir.join("integer-to-string.slo"), + supported_dir.join("local-variables.slo"), + supported_dir.join("numeric-struct-fields.slo"), + supported_dir.join("numeric-widening-conversions.slo"), + supported_dir.join("option-result-flow.slo"), + supported_dir.join("option-result-match.slo"), + supported_dir.join("option-result-payload.slo"), + supported_dir.join("option-result.slo"), + supported_dir.join("owned-string-concat.slo"), + supported_dir.join("primitive-struct-fields.slo"), + supported_dir.join("print-bool.slo"), + supported_dir.join("random.slo"), + supported_dir.join("result-f64-bool-match.slo"), + supported_dir.join("result-helpers.slo"), + supported_dir.join("standard-runtime.slo"), + supported_dir.join("stdin-result.slo"), + supported_dir.join("string-parse-bool-result.slo"), + supported_dir.join("string-parse-f64-result.slo"), + supported_dir.join("string-parse-i32-result.slo"), + supported_dir.join("string-parse-i64-result.slo"), + supported_dir.join("string-parse-u32-result.slo"), + supported_dir.join("string-parse-u64-result.slo"), + supported_dir.join("string-print.slo"), + supported_dir.join("string-value-flow.slo"), + supported_dir.join("struct-value-flow.slo"), + supported_dir.join("struct.slo"), + supported_dir.join("time-sleep.slo"), + supported_dir.join("top-level-test.slo"), + supported_dir.join("u32-numeric-primitive.slo"), + supported_dir.join("u64-numeric-primitive.slo"), + supported_dir.join("unsafe.slo"), + supported_dir.join("unsigned-integer-to-string.slo"), + supported_dir.join("vec-bool.slo"), + supported_dir.join("vec-f64.slo"), + supported_dir.join("vec-i32.slo"), + supported_dir.join("vec-i64.slo"), + supported_dir.join("vec-string.slo"), + supported_dir.join("while.slo"), + ], + "promotion gate expects Slovo supported fixtures to match the current supported surface" + ); +} + +fn assert_glagol_test_fixtures(repo: &Path) { + let tests_dir = repo.join("tests"); + assert_file_name_inventory( + &tests_dir, + &["slo", "fmt"], + GLAGOL_TEST_FIXTURES, + "promotion gate expects Glagol test/formatter fixtures to be explicitly inventoried", + ); +} + +fn assert_lowering_inspector_fixture_inventory(repo: &Path) { + assert_file_name_inventory( + &repo.join("tests"), + &["lower"], + LOWERING_INSPECTOR_FIXTURES, + "promotion gate expects lowering inspector fixtures to be explicitly inventoried", + ); +} + +fn assert_slovo_formatter_fixtures(repo: &Path) { + let formatter_dir = repo.join("docs/language/examples/formatter"); + assert_file_name_inventory( + &formatter_dir, + &["slo"], + SLOVO_FORMATTER_FIXTURES, + "promotion gate expects Slovo formatter fixtures to be explicitly inventoried", + ); +} + +fn assert_slovo_v0_compatibility_fixtures(repo: &Path) { + assert_file_name_inventory( + &repo.join("docs/language/examples/compat/v0/supported"), + &["slo"], + SLOVO_V0_SUPPORTED_COMPATIBILITY_FIXTURES, + "promotion gate expects frozen Slovo v0 supported compatibility fixtures", + ); + assert_file_name_inventory( + &repo.join("docs/language/examples/compat/v0/formatter"), + &["slo"], + SLOVO_V0_FORMATTER_COMPATIBILITY_FIXTURES, + "promotion gate expects frozen Slovo v0 formatter compatibility fixtures", + ); +} + +fn assert_slovo_std_source_layout_alpha(repo: &Path, std_dir: &Path) { + assert!( + std_dir.is_dir(), + "`{}` must exist for exp-30 std source layout alpha", + std_dir.display() + ); + + let mut files = read_dir_files(std_dir); + files.sort(); + assert_eq!( + files, + vec![ + std_dir.join("README.md"), + std_dir.join("cli.slo"), + std_dir.join("env.slo"), + std_dir.join("fs.slo"), + std_dir.join("io.slo"), + std_dir.join("math.slo"), + std_dir.join("num.slo"), + std_dir.join("option.slo"), + std_dir.join("process.slo"), + std_dir.join("random.slo"), + std_dir.join("result.slo"), + std_dir.join("string.slo"), + std_dir.join("time.slo"), + std_dir.join("vec_bool.slo"), + std_dir.join("vec_f64.slo"), + std_dir.join("vec_i32.slo"), + std_dir.join("vec_i64.slo"), + std_dir.join("vec_string.slo"), + ], + "std source layout alpha expects an exact file inventory" + ); + + let readme = read(&std_dir.join("README.md")); + assert!( + readme.contains("source layout") || readme.contains("Source Layout"), + "std/README.md should describe std as a source-layout contract artifact" + ); + + for file in [ + "io.slo", + "cli.slo", + "env.slo", + "fs.slo", + "math.slo", + "num.slo", + "option.slo", + "process.slo", + "random.slo", + "result.slo", + "string.slo", + "time.slo", + "vec_i32.slo", + ] { + let path = std_dir.join(file); + assert_source_shaped_file(&path); + + // Import-bearing std sources are validated through their project fixtures. + if file != "cli.slo" + && file != "env.slo" + && file != "fs.slo" + && file != "io.slo" + && file != "process.slo" + && file != "string.slo" + && file != "vec_i32.slo" + { + let check = run_glagol([OsStr::new("check"), path.as_os_str()]); + assert_success_stdout(check, "", &format!("std source check for `{}`", file)); + } + + if file != "vec_i32.slo" { + let format = run_glagol([OsStr::new("--format"), path.as_os_str()]); + assert_success_stdout( + format, + &read(&path), + &format!("std source formatter stability for `{}`", file), + ); + } + } + + let local_math = repo.join("examples/projects/std-layout-local-math/src/math.slo"); + let slovo_math = read(&std_dir.join("math.slo")); + let glagol_math = read(&local_math); + assert!( + slovo_math.contains("(module math") && glagol_math.contains("(module math"), + "both Slovo std/math.slo and the Glagol local fixture should use explicit `math` module source shape" + ); + assert!( + !slovo_math.contains("std.") && !glagol_math.contains("std."), + "exp-39 standard math helpers must stay source-authored without compiler-known std runtime calls" + ); + assert_eq!( + normalize_local_math_fixture_helpers(&glagol_math), + slovo_math.trim_end(), + "Glagol local math helper source must match sibling Slovo std/math.slo" + ); + for helper in STANDARD_MATH_SOURCE_HELPERS_ALPHA { + assert!( + slovo_math.contains(&format!("(fn {} ", helper)), + "Slovo std/math.slo is missing exp-39 helper `{}`", + helper + ); + assert!( + glagol_math.contains(&format!("(fn {} ", helper)), + "Glagol local math fixture is missing exp-39 helper `{}`", + helper + ); + } + + let local_option = repo.join("examples/projects/std-layout-local-option/src/option.slo"); + let slovo_option = read(&std_dir.join("option.slo")); + let glagol_option = read(&local_option); + assert!( + slovo_option.contains("(module option") && glagol_option.contains("(module option"), + "both Slovo std/option.slo and the Glagol local fixture should use explicit `option` module source shape" + ); + assert!( + !slovo_option.contains("std.") && !glagol_option.contains("std."), + "exp-36 standard option helpers must stay source-authored without compiler-known std runtime calls" + ); + for helper in STANDARD_OPTION_SOURCE_HELPERS_ALPHA { + assert!( + slovo_option.contains(&format!("(fn {} ", helper)), + "Slovo std/option.slo is missing exp-36 helper `{}`", + helper + ); + assert!( + glagol_option.contains(&format!("(fn {} ", helper)), + "Glagol local option fixture is missing exp-36 helper `{}`", + helper + ); + } + for helper in STANDARD_OPTION_RESULT_BRIDGE_HELPERS_ALPHA { + assert!( + slovo_option.contains(&format!("(fn {} ", helper)), + "Slovo std/option.slo is missing exp-109 bridge helper `{}`", + helper + ); + assert!( + glagol_option.contains(&format!("(fn {} ", helper)), + "Glagol local option fixture is missing exp-109 bridge helper `{}`", + helper + ); + } + + let local_result = repo.join("examples/projects/std-layout-local-result/src/result.slo"); + let slovo_result = read(&std_dir.join("result.slo")); + let glagol_result = read(&local_result); + assert!( + slovo_result.contains("(module result") && glagol_result.contains("(module result"), + "both Slovo std/result.slo and the Glagol local fixture should use explicit `result` module source shape" + ); + assert!( + !slovo_result.contains("std.result.map") + && !slovo_result.contains("std.result.and_then") + && !slovo_result.contains("std.result.unwrap_or") + && !glagol_result.contains("std.result.map") + && !glagol_result.contains("std.result.and_then") + && !glagol_result.contains("std.result.unwrap_or"), + "exp-33 standard result source helpers must not introduce deferred compiler-known result helpers" + ); + for helper in STANDARD_RESULT_SOURCE_SEARCH_ALPHA { + assert!( + slovo_result.contains(&format!("(fn {} ", helper)), + "Slovo std/result.slo is missing exp-33 helper `{}`", + helper + ); + assert!( + glagol_result.contains(&format!("(fn {} ", helper)), + "Glagol local result fixture is missing exp-33 helper `{}`", + helper + ); + } + for helper in STANDARD_RESULT_OPTION_BRIDGE_HELPERS_ALPHA { + assert!( + slovo_result.contains(&format!("(fn {} ", helper)), + "Slovo std/result.slo is missing exp-109 bridge helper `{}`", + helper + ); + assert!( + glagol_result.contains(&format!("(fn {} ", helper)), + "Glagol local result fixture is missing exp-109 bridge helper `{}`", + helper + ); + } + + let slovo_string = read(&std_dir.join("string.slo")); + assert!( + slovo_string.starts_with("(module string (export "), + "Slovo std/string.slo must directly export imported helpers" + ); + assert!( + slovo_string.contains("(fn parse_bool_result ") + && slovo_string.contains("(std.string.parse_bool_result value)") + && slovo_string.contains( + "(import std.result (ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool))", + ), + "Slovo std/string.slo must preserve parse result helpers and the std.result option bridge import" + ); + assert_std_only_contains( + &slovo_string, + &[ + "std.result", + "std.string.parse_bool_result", + "std.string.parse_f64_result", + "std.string.parse_u64_result", + "std.string.parse_i64_result", + "std.string.parse_u32_result", + "std.string.parse_i32_result", + "std.string.concat", + "std.string.len", + ], + "Slovo std/string.slo must not introduce other compiler-known std names", + ); + for helper in STANDARD_STRING_SOURCE_FACADE_ALPHA { + assert!( + slovo_string.contains(&format!("(fn {} ", helper)), + "Slovo std/string.slo is missing exp-48 facade `{}`", + helper + ); + } + + let local_num = repo.join("examples/projects/std-layout-local-num/src/num.slo"); + let slovo_num = read(&std_dir.join("num.slo")); + let glagol_num = read(&local_num); + assert!( + slovo_num.starts_with("(module num (export ") + && glagol_num.starts_with("(module num (export "), + "both Slovo std/num.slo and the Glagol local fixture must directly export imported helpers" + ); + for source in [&slovo_num, &glagol_num] { + assert_std_only_contains( + source, + &[ + "std.num.i32_to_i64", + "std.num.i32_to_f64", + "std.num.i64_to_f64", + "std.num.i64_to_i32_result", + "std.num.f64_to_i32_result", + "std.num.f64_to_i64_result", + "std.num.i32_to_string", + "std.num.u32_to_string", + "std.num.i64_to_string", + "std.num.u64_to_string", + "std.num.f64_to_string", + ], + "exp-64 standard num facade must not introduce other compiler-known std names", + ); + assert!( + !source.contains("saturat") + && !source.contains("round") + && !source.contains("floor") + && !source.contains("ceil") + && !source.contains("generic"), + "exp-64 standard num facade must not claim deferred numeric conversion policies or generic abstractions" + ); + } + for helper in STANDARD_NUM_SOURCE_FACADE_ALPHA { + assert!( + slovo_num.contains(&format!("(fn {} ", helper)), + "Slovo std/num.slo is missing exp-64 facade `{}`", + helper + ); + assert!( + glagol_num.contains(&format!("(fn {} ", helper)), + "Glagol local num fixture is missing exp-64 facade `{}`", + helper + ); + } + + let slovo_io = read(&std_dir.join("io.slo")); + assert!( + slovo_io.starts_with("(module io (export "), + "Slovo std/io.slo must directly export imported helpers" + ); + assert!( + slovo_io.contains( + "(import std.result (ok_or_none_string ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool))", + ) && slovo_io.contains( + "(import std.string (parse_i32_result parse_u32_result parse_i64_result parse_u64_result parse_f64_result parse_bool_result))", + ) && slovo_io.contains("(std.io.read_stdin_result)"), + "Slovo std/io.slo must preserve stdin-result source helpers over std.result and std.string bridge imports" + ); + assert_std_only_contains( + &slovo_io, + &[ + "std.io.print_i32", + "std.io.print_i64", + "std.io.print_u32", + "std.io.print_u64", + "std.io.print_f64", + "std.io.print_string", + "std.io.print_bool", + "std.io.read_stdin_result", + "std.result", + "std.string", + ], + "Slovo std/io.slo must not introduce other compiler-known std names", + ); + for helper in STANDARD_IO_SOURCE_FACADE_ALPHA { + assert!( + slovo_io.contains(&format!("(fn {} ", helper)), + "Slovo std/io.slo is missing exp-49 facade `{}`", + helper + ); + } + + let local_vec_i32 = repo.join("examples/projects/std-layout-local-vec_i32/src/vec_i32.slo"); + let local_vec_i32_option = + repo.join("examples/projects/std-layout-local-vec_i32/src/option.slo"); + let slovo_vec_i32 = read(&std_dir.join("vec_i32.slo")); + let glagol_vec_i32 = read(&local_vec_i32); + let glagol_vec_i32_option = read(&local_vec_i32_option); + assert!( + slovo_vec_i32.starts_with("(module vec_i32 (export ") + && glagol_vec_i32.starts_with("(module vec_i32 (export "), + "both Slovo std/vec_i32.slo and the Glagol local fixture must directly export imported helpers" + ); + assert!( + glagol_vec_i32_option.starts_with("(module option (export "), + "Glagol local vec_i32 option helper source must stay an explicit local module" + ); + assert_eq!( + normalize_local_vec_i32_fixture_helpers(&glagol_vec_i32), + slovo_vec_i32.trim_end(), + "Glagol local vec_i32 helper source must match sibling Slovo std/vec_i32.slo" + ); + for source in [&slovo_vec_i32, &glagol_vec_i32] { + assert_std_only_contains( + source, + &[ + "std.vec.i32.empty", + "std.vec.i32.append", + "std.vec.i32.len", + "std.vec.i32.index", + "std.option", + ], + "exp-93 standard vec_i32 facade must not introduce other compiler-known std names", + ); + assert!( + !source.contains("capacity") + && !source.contains("reserve") + && !source.contains("shrink") + && !source.contains("sort") + && !source.contains("map") + && !source.contains("filter") + && !source.contains("(vec i64)") + && !source.contains("(vec f64)") + && !source.contains("(vec string)") + && !source.contains("(vec bool)") + && !source.contains("(option i64)") + && !source.contains("(option f64)") + && !source.contains("(option string)") + && !source.contains("(option bool)"), + "exp-93 standard vec_i32 facade must stay limited to concrete i32 vec and option helpers" + ); + } + for helper in STANDARD_VEC_I32_SOURCE_FACADE_ALPHA { + assert!( + slovo_vec_i32.contains(&format!("(fn {} ", helper)), + "Slovo std/vec_i32.slo is missing exp-93 facade `{}`", + helper + ); + assert!( + glagol_vec_i32.contains(&format!("(fn {} ", helper)), + "Glagol local vec_i32 fixture is missing exp-93 facade `{}`", + helper + ); + } + + let local_process = repo.join("examples/projects/std-layout-local-process/src/process.slo"); + let local_process_string = + repo.join("examples/projects/std-layout-local-process/src/string.slo"); + let slovo_process = read(&std_dir.join("process.slo")); + let glagol_process = read(&local_process); + let glagol_process_string = read(&local_process_string); + assert!( + slovo_process.starts_with("(module process (export ") + && glagol_process.starts_with("(module process (export "), + "both Slovo std/process.slo and the Glagol local fixture must directly export imported helpers" + ); + assert!( + slovo_process.contains("(std.process.argc)") + && slovo_process.contains("(std.process.arg index)") + && slovo_process.contains("(std.process.arg_result index)") + && slovo_process.contains( + "(import std.result (ok_or_none_string ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool))", + ) + && slovo_process.contains("(std.string.parse_i32_result text)") + && slovo_process.contains("(std.string.parse_u32_result text)") + && slovo_process.contains("(std.string.parse_i64_result text)") + && slovo_process.contains("(std.string.parse_u64_result text)") + && slovo_process.contains("(std.string.parse_f64_result text)") + && slovo_process.contains("(std.string.parse_bool_result text)") + && glagol_process.contains( + "(import result (ok_or_none_string ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool))", + ) + && glagol_process.contains("(import string (parse_i32_result parse_u32_result parse_i64_result parse_u64_result parse_f64_result parse_bool_result))") + && glagol_process.contains("(std.process.argc)") + && glagol_process.contains("(std.process.arg index)") + && glagol_process.contains("(std.process.arg_result index)") + && glagol_process_string.starts_with("(module string (export ") + && glagol_process_string.contains( + "(import result (ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool))", + ), + "exp-67 standard process facade must stay backed by existing promoted process and parse result calls" + ); + assert_std_only_contains( + &slovo_process, + &[ + "std.process.arg_result", + "std.process.argc", + "std.process.arg", + "std.result", + "std.string.parse_i32_result", + "std.string.parse_u32_result", + "std.string.parse_i64_result", + "std.string.parse_u64_result", + "std.string.parse_f64_result", + "std.string.parse_bool_result", + ], + "Slovo std/process.slo must not introduce other compiler-known std names", + ); + assert_std_only_contains( + &glagol_process, + &[ + "std.process.arg_result", + "std.process.argc", + "std.process.arg", + ], + "Glagol local process fixture must not introduce direct compiler-known std names beyond process calls", + ); + assert_std_only_contains( + &glagol_process_string, + &[ + "std.string.len", + "std.string.concat", + "std.string.parse_i32_result", + "std.string.parse_u32_result", + "std.string.parse_i64_result", + "std.string.parse_u64_result", + "std.string.parse_f64_result", + "std.string.parse_bool_result", + ], + "Glagol local process string fixture must not introduce other compiler-known std names", + ); + assert!( + !slovo_process.contains("spawn") + && !slovo_process.contains("exit") + && !slovo_process.contains("cwd") + && !slovo_process.contains("signal") + && !glagol_process.contains("spawn") + && !glagol_process.contains("exit") + && !glagol_process.contains("cwd") + && !glagol_process.contains("signal"), + "exp-67 standard process facade must not claim deferred process APIs" + ); + for helper in STANDARD_PROCESS_SOURCE_FACADE_ALPHA { + assert!( + slovo_process.contains(&format!("(fn {} ", helper)), + "Slovo std/process.slo is missing exp-67 facade `{}`", + helper + ); + assert!( + glagol_process.contains(&format!("(fn {} ", helper)), + "Glagol local process fixture is missing exp-67 facade `{}`", + helper + ); + } + + let local_cli = repo.join("examples/projects/std-layout-local-cli/src/cli.slo"); + let local_cli_process = repo.join("examples/projects/std-layout-local-cli/src/process.slo"); + let local_cli_string = repo.join("examples/projects/std-layout-local-cli/src/string.slo"); + let slovo_cli = read(&std_dir.join("cli.slo")); + let glagol_cli = read(&local_cli); + let glagol_cli_process = read(&local_cli_process); + let glagol_cli_string = read(&local_cli_string); + assert!( + slovo_cli.starts_with("(module cli (export "), + "Slovo std/cli.slo must directly export imported helpers" + ); + assert!( + slovo_cli.contains("(import std.process (arg_result))") + && slovo_cli.contains( + "(import std.result (ok_or_none_string ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool))", + ) + && slovo_cli + .contains("(import std.string (parse_i32_result parse_u32_result parse_i64_result parse_u64_result parse_f64_result parse_bool_result))"), + "standard cli facade must use transitive standard-source imports, including the std.result option bridge" + ); + assert_std_only_contains( + &slovo_cli, + &["std.process", "std.result", "std.string"], + "Slovo std/cli.slo must not use compiler-known std runtime names directly", + ); + assert!( + glagol_cli.starts_with("(module cli (export "), + "Glagol local cli fixture must stay an explicit local module export" + ); + assert!( + glagol_cli.contains("(import process (arg_result))") + && glagol_cli.contains( + "(import result (ok_or_none_string ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool))" + ) + && glagol_cli.contains( + "(import string (parse_i32_result parse_u32_result parse_i64_result parse_u64_result parse_f64_result parse_bool_result))" + ) + && !glagol_cli.contains("std."), + "Glagol local cli fixture must stay a source-authored local import chain" + ); + assert!( + glagol_cli_process.starts_with("(module process (export ") + && glagol_cli_process.contains( + "(import result (ok_or_none_string ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool))", + ) + && glagol_cli_process.contains("(std.process.argc)") + && glagol_cli_process.contains("(std.process.arg index)") + && glagol_cli_process.contains("(std.process.arg_result index)") + && glagol_cli_process.contains("(std.string.parse_i32_result text)") + && glagol_cli_process.contains("(std.string.parse_u32_result text)") + && glagol_cli_process.contains("(std.string.parse_i64_result text)") + && glagol_cli_process.contains("(std.string.parse_u64_result text)") + && glagol_cli_process.contains("(std.string.parse_f64_result text)") + && glagol_cli_process.contains("(std.string.parse_bool_result text)"), + "Glagol local cli process fixture must stay aligned with the local process facade surface" + ); + assert_std_only_contains( + &glagol_cli_process, + &[ + "std.process.arg_result", + "std.process.argc", + "std.process.arg", + "std.string.parse_i32_result", + "std.string.parse_u32_result", + "std.string.parse_i64_result", + "std.string.parse_u64_result", + "std.string.parse_f64_result", + "std.string.parse_bool_result", + ], + "Glagol local cli process fixture must not introduce other compiler-known std names", + ); + assert!( + glagol_cli_string.starts_with("(module string (export ") + && glagol_cli_string.contains( + "(import result (ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool))", + ), + "Glagol local cli string fixture must stay a local parse-result helper module" + ); + assert_std_only_contains( + &glagol_cli_string, + &[ + "std.string.len", + "std.string.concat", + "std.string.parse_i32_result", + "std.string.parse_u32_result", + "std.string.parse_i64_result", + "std.string.parse_u64_result", + "std.string.parse_f64_result", + "std.string.parse_bool_result", + ], + "Glagol local cli string fixture must not introduce other compiler-known std names", + ); + for helper in STANDARD_CLI_SOURCE_FACADE_ALPHA { + assert!( + slovo_cli.contains(&format!("(fn {} ", helper)), + "Slovo std/cli.slo is missing CLI facade `{}`", + helper + ); + assert!( + glagol_cli.contains(&format!("(fn {} ", helper)), + "Glagol local cli fixture is missing exp-78 facade `{}`", + helper + ); + } + for helper in STANDARD_STRING_PARSE_RESULT_HELPERS_ALPHA { + assert!( + glagol_cli_string.contains(&format!("(fn {} ", helper)), + "Glagol local cli string fixture is missing parse helper `{}`", + helper + ); + } + + let local_time = repo.join("examples/projects/std-layout-local-time/src/time.slo"); + let slovo_time = read(&std_dir.join("time.slo")); + let glagol_time = read(&local_time); + assert!( + slovo_time.contains("(module time") && glagol_time.contains("(module time"), + "both Slovo std/time.slo and the Glagol local fixture should use explicit `time` module source shape" + ); + assert!( + slovo_time.contains("(std.time.monotonic_ms)") + && slovo_time.contains("(std.time.sleep_ms 0)") + && glagol_time.contains("(std.time.monotonic_ms)") + && glagol_time.contains("(std.time.sleep_ms 0)"), + "exp-37 standard time facade must stay backed by the existing promoted std.time calls" + ); + for helper in STANDARD_TIME_SOURCE_FACADE_ALPHA { + assert!( + slovo_time.contains(&format!("(fn {} ", helper)), + "Slovo std/time.slo is missing exp-37 facade `{}`", + helper + ); + assert!( + glagol_time.contains(&format!("(fn {} ", helper)), + "Glagol local time fixture is missing exp-37 facade `{}`", + helper + ); + } + assert!( + !slovo_time.contains("std.time.now") + && !glagol_time.contains("std.time.now") + && !slovo_time.contains("calendar") + && !glagol_time.contains("calendar") + && !slovo_time.contains("timezone") + && !glagol_time.contains("timezone") + && !slovo_time.contains("async") + && !glagol_time.contains("async") + && !slovo_time.contains("cancel") + && !glagol_time.contains("cancel") + && !slovo_time.contains("schedule") + && !glagol_time.contains("schedule"), + "exp-37 standard time facade must not claim deferred time APIs or scheduling semantics" + ); + + let local_random = repo.join("examples/projects/std-layout-local-random/src/random.slo"); + let slovo_random = read(&std_dir.join("random.slo")); + let glagol_random = read(&local_random); + assert!( + slovo_random.contains("(module random") && glagol_random.contains("(module random"), + "both Slovo std/random.slo and the Glagol local fixture should use explicit `random` module source shape" + ); + assert!( + slovo_random.contains("(std.random.i32)") && glagol_random.contains("(std.random.i32)"), + "exp-38 standard random facade must stay backed by the existing promoted std.random.i32 call" + ); + assert_std_only_contains( + &slovo_random, + &["std.random.i32"], + "Slovo std/random.slo must not introduce other compiler-known std names", + ); + assert_std_only_contains( + &glagol_random, + &["std.random.i32"], + "Glagol local random fixture must not introduce other compiler-known std names", + ); + assert!( + !slovo_random.contains("seed") + && !glagol_random.contains("seed") + && !slovo_random.contains("range") + && !glagol_random.contains("range") + && !slovo_random.contains("bytes") + && !glagol_random.contains("bytes") + && !slovo_random.contains("float") + && !glagol_random.contains("float") + && !slovo_random.contains("uuid") + && !glagol_random.contains("uuid") + && !slovo_random.contains("crypto") + && !glagol_random.contains("crypto"), + "exp-38 standard random facade must not claim deferred random APIs" + ); + for helper in STANDARD_RANDOM_SOURCE_FACADE_ALPHA { + assert!( + slovo_random.contains(&format!("(fn {} ", helper)), + "Slovo std/random.slo is missing exp-38 facade `{}`", + helper + ); + assert!( + glagol_random.contains(&format!("(fn {} ", helper)), + "Glagol local random fixture is missing exp-38 facade `{}`", + helper + ); + } + + let local_env = repo.join("examples/projects/std-layout-local-env/src/env.slo"); + let local_env_string = repo.join("examples/projects/std-layout-local-env/src/string.slo"); + let slovo_env = read(&std_dir.join("env.slo")); + let glagol_env = read(&local_env); + let glagol_env_string = read(&local_env_string); + assert!( + slovo_env.contains("(module env") && glagol_env.contains("(module env"), + "both Slovo std/env.slo and the Glagol local fixture should use explicit `env` module source shape" + ); + assert!( + slovo_env.contains("(std.env.get name)") + && slovo_env.contains("(std.env.get_result name)") + && slovo_env.contains( + "(import std.result (ok_or_none_string ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool))", + ) + && slovo_env.contains("(std.string.parse_i32_result text)") + && slovo_env.contains("(std.string.parse_u32_result text)") + && slovo_env.contains("(std.string.parse_i64_result text)") + && slovo_env.contains("(std.string.parse_u64_result text)") + && slovo_env.contains("(std.string.parse_f64_result text)") + && slovo_env.contains("(std.string.parse_bool_result text)") + && glagol_env.contains( + "(import result (ok_or_none_string ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool))", + ) + && glagol_env.contains("(import string (parse_i32_result parse_u32_result parse_i64_result parse_u64_result parse_f64_result parse_bool_result))") + && glagol_env.contains("(std.env.get name)") + && glagol_env.contains("(std.env.get_result name)") + && glagol_env_string.starts_with("(module string (export ") + && glagol_env_string.contains( + "(import result (ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool))", + ), + "exp-65 standard env facade must stay backed by the existing promoted env and parse result calls" + ); + assert_std_only_contains( + &slovo_env, + &[ + "std.env.get_result", + "std.env.get", + "std.result", + "std.string.parse_i32_result", + "std.string.parse_u32_result", + "std.string.parse_i64_result", + "std.string.parse_u64_result", + "std.string.parse_f64_result", + "std.string.parse_bool_result", + ], + "Slovo std/env.slo must not introduce other compiler-known std names", + ); + assert_std_only_contains( + &glagol_env, + &["std.env.get_result", "std.env.get"], + "Glagol local env fixture must not introduce direct compiler-known std names beyond env lookup", + ); + assert_std_only_contains( + &glagol_env_string, + &[ + "std.string.len", + "std.string.concat", + "std.string.parse_i32_result", + "std.string.parse_u32_result", + "std.string.parse_i64_result", + "std.string.parse_u64_result", + "std.string.parse_f64_result", + "std.string.parse_bool_result", + ], + "Glagol local env string fixture must not introduce other compiler-known std names", + ); + assert!( + !slovo_env.contains("set") + && !glagol_env.contains("set") + && !slovo_env.contains("unset") + && !glagol_env.contains("unset") + && !slovo_env.contains("enumer") + && !glagol_env.contains("enumer") + && !slovo_env.contains("platform") + && !glagol_env.contains("platform") + && !slovo_env.contains("host_error") + && !glagol_env.contains("host_error") + && !slovo_env.contains("errno") + && !glagol_env.contains("errno"), + "exp-65 standard env facade must not claim deferred env APIs or rich host errors" + ); + for helper in STANDARD_ENV_SOURCE_FACADE_ALPHA { + assert!( + slovo_env.contains(&format!("(fn {} ", helper)), + "Slovo std/env.slo is missing exp-65 facade `{}`", + helper + ); + assert!( + glagol_env.contains(&format!("(fn {} ", helper)), + "Glagol local env fixture is missing exp-65 facade `{}`", + helper + ); + } + + let local_fs = repo.join("examples/projects/std-layout-local-fs/src/fs.slo"); + let local_fs_string = repo.join("examples/projects/std-layout-local-fs/src/string.slo"); + let slovo_fs = read(&std_dir.join("fs.slo")); + let glagol_fs = read(&local_fs); + let glagol_fs_string = read(&local_fs_string); + assert!( + slovo_fs.contains("(module fs") && glagol_fs.contains("(module fs"), + "both Slovo std/fs.slo and the Glagol local fixture should use explicit `fs` module source shape" + ); + assert!( + slovo_fs.contains("(std.fs.read_text path)") + && slovo_fs.contains("(std.fs.read_text_result path)") + && slovo_fs.contains("(std.fs.write_text path text)") + && slovo_fs.contains("(std.fs.write_text_result path text)") + && slovo_fs.contains( + "(import std.result (ok_or_none_string ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool))", + ) + && slovo_fs.contains("(std.string.parse_i32_result text)") + && slovo_fs.contains("(std.string.parse_u32_result text)") + && slovo_fs.contains("(std.string.parse_i64_result text)") + && slovo_fs.contains("(std.string.parse_u64_result text)") + && slovo_fs.contains("(std.string.parse_f64_result text)") + && slovo_fs.contains("(std.string.parse_bool_result text)") + && glagol_fs.contains( + "(import result (ok_or_none_string ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool))", + ) + && glagol_fs.contains("(import string (parse_i32_result parse_u32_result parse_i64_result parse_u64_result parse_f64_result parse_bool_result))") + && glagol_fs.contains("(std.fs.read_text path)") + && glagol_fs.contains("(std.fs.read_text_result path)") + && glagol_fs.contains("(std.fs.write_text path text)") + && glagol_fs.contains("(std.fs.write_text_result path text)") + && glagol_fs_string.starts_with("(module string (export ") + && glagol_fs_string.contains( + "(import result (ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool))", + ), + "exp-66 standard fs facade must stay backed by the existing promoted fs and parse result calls" + ); + assert_std_only_contains( + &slovo_fs, + &[ + "std.fs.read_text_result", + "std.fs.write_text_result", + "std.fs.read_text", + "std.fs.write_text", + "std.result", + "std.string.parse_i32_result", + "std.string.parse_u32_result", + "std.string.parse_i64_result", + "std.string.parse_u64_result", + "std.string.parse_f64_result", + "std.string.parse_bool_result", + ], + "Slovo std/fs.slo must not introduce other compiler-known std names", + ); + assert_std_only_contains( + &glagol_fs, + &[ + "std.fs.read_text_result", + "std.fs.write_text_result", + "std.fs.read_text", + "std.fs.write_text", + ], + "Glagol local fs fixture must not introduce direct compiler-known std names beyond fs text calls", + ); + assert_std_only_contains( + &glagol_fs_string, + &[ + "std.string.len", + "std.string.concat", + "std.string.parse_i32_result", + "std.string.parse_u32_result", + "std.string.parse_i64_result", + "std.string.parse_u64_result", + "std.string.parse_f64_result", + "std.string.parse_bool_result", + ], + "Glagol local fs string fixture must not introduce other compiler-known std names", + ); + for source in [&slovo_fs, &glagol_fs, &glagol_fs_string] { + assert!( + !source.contains("binary") + && !source.contains("list_dir") + && !source.contains("walk") + && !source.contains("stream") + && !source.contains("async") + && !source.contains("host_error"), + "exp-66 standard fs facade must not claim deferred fs APIs or rich host errors" + ); + } + for helper in STANDARD_FS_SOURCE_FACADE_ALPHA { + assert!( + slovo_fs.contains(&format!("(fn {} ", helper)), + "Slovo std/fs.slo is missing exp-66 facade `{}`", + helper + ); + assert!( + glagol_fs.contains(&format!("(fn {} ", helper)), + "Glagol local fs fixture is missing exp-66 facade `{}`", + helper + ); + } +} + +fn assert_source_shaped_file(path: &Path) { + let source = read(path); + assert!( + source.ends_with('\n'), + "`{}` must end with a final newline", + path.display() + ); + assert!( + !source.contains('\t'), + "`{}` must use spaces, not tabs", + path.display() + ); + assert!( + !source + .lines() + .any(|line| line.ends_with(' ') || line.ends_with('\t')), + "`{}` must not contain trailing whitespace", + path.display() + ); +} + +fn assert_source_eq(left: &Path, right: &Path) { + let left_source = read(left); + let right_source = read(right); + + assert_eq!( + left_source, + right_source, + "`{}` and `{}` must stay byte-for-byte aligned", + left.display(), + right.display() + ); +} + +fn assert_same_supported_body(supported: &Path, formatter: &Path) { + let supported_body = strip_leading_comments_and_blank_lines(&read(supported)); + let formatter_body = strip_leading_comments_and_blank_lines(&read(formatter)); + + assert_eq!( + supported_body, formatter_body, + "formatter canonical body must remain the same current supported program" + ); +} + +fn assert_formatter_fixture_contract(path: &Path) { + let source = read(path); + + assert!( + source.starts_with("; status: formatter-canonical\n"), + "`{}` must declare formatter-only status", + path.display() + ); + assert!( + source.ends_with('\n'), + "`{}` must end with a final newline", + path.display() + ); + assert!( + !source + .lines() + .any(|line| line.ends_with(' ') || line.ends_with('\t')), + "`{}` must not contain trailing whitespace", + path.display() + ); + assert!( + !source.contains('\t'), + "`{}` must use spaces, not tabs", + path.display() + ); + + let code = strip_comments(&source); + let forbidden_forms = [ + "if", "let", "var", "set", "while", "struct", "test", "unsafe", "string", "array", "slice", + "option", "result", "ptr", + ]; + + for form in forbidden_forms { + assert!( + !contains_word(&code, form), + "formatter canonical fixture contains a form outside the supported add fixture surface: `{}`", + form + ); + } + assert!( + !code.contains('"'), + "formatter canonical fixture must not contain string literals" + ); +} + +fn assert_compiler_output_matches_supported_add_shape(repo: &Path, fixture: &Path) { + let expected = read(&repo.join("tests/add.expected.ll")); + let output = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "compiler rejected supported add fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stderr.is_empty(), + "compiler wrote stderr for accepted add fixture:\n{}", + stderr + ); + assert!( + !stdout.contains("panicked at") && !stderr.contains("panicked at"), + "compiler panicked for supported add fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + + for required in required_llvm_shapes(&expected) { + assert!( + stdout.contains(required), + "LLVM output did not contain expected shape `{}`\nstdout:\n{}", + required, + stdout + ); + } +} + +fn assert_tree_printer_matches_fixture(repo: &Path, fixture: &Path) { + let expected = read(&repo.join("tests/add.sexpr")); + let output = run_glagol([OsStr::new("--print-tree"), fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "tree printer rejected supported add fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert_eq!(stdout, expected, "tree printer fixture drifted"); + assert!( + stderr.is_empty(), + "tree printer wrote stderr for accepted add fixture:\n{}", + stderr + ); +} + +fn assert_formatter_matches_fixture(fixture: &Path) { + let expected = read(fixture); + let output = run_glagol([OsStr::new("--format"), fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "formatter rejected supported fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert_eq!(stdout, expected, "formatter fixture drifted"); + assert!( + stderr.is_empty(), + "formatter wrote stderr for accepted fixture:\n{}", + stderr + ); +} + +fn assert_lowering_inspector_matches_fixture( + repo: &Path, + fixture: &Path, + mode: &str, + snapshot: &str, +) { + let expected = read(&repo.join("tests").join(snapshot)); + let output = run_glagol([OsStr::new(mode), fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "lowering inspector `{}` rejected supported fixture\nstdout:\n{}\nstderr:\n{}", + mode, + stdout, + stderr + ); + assert_eq!( + stdout, expected, + "lowering inspector `{}` fixture drifted", + mode + ); + assert!( + stderr.is_empty(), + "lowering inspector `{}` wrote stderr for accepted fixture:\n{}", + mode, + stderr + ); +} + +fn assert_lowering_inspector_matrix_matches_fixtures(repo: &Path) { + for case in LOWERING_INSPECTOR_CASES { + let source = repo.join(case.source); + assert_lowering_inspector_matches_fixture( + repo, + &source, + "--inspect-lowering=surface", + case.surface_snapshot, + ); + assert_lowering_inspector_matches_fixture( + repo, + &source, + "--inspect-lowering=checked", + case.checked_snapshot, + ); + } +} + +fn assert_top_level_test_tooling_matches_fixtures(repo: &Path) { + let fixture = repo.join("tests/top-level-test.slo"); + + let expected_tree = read(&repo.join("tests/top-level-test.sexpr")); + let tree = run_glagol([OsStr::new("--print-tree"), fixture.as_os_str()]); + assert_success_stdout(tree, &expected_tree, "top-level test tree fixture"); + + assert_lowering_inspector_matches_fixture( + repo, + &fixture, + "--inspect-lowering=surface", + "top-level-test.surface.lower", + ); + assert_lowering_inspector_matches_fixture( + repo, + &fixture, + "--inspect-lowering=checked", + "top-level-test.checked.lower", + ); + + let checked = run_glagol([OsStr::new("--check-tests"), fixture.as_os_str()]); + assert_success_stdout( + checked, + "test \"add works\" ... checked\n1 test(s) checked\n", + "top-level test checker output", + ); + + let run = run_glagol([OsStr::new("--run-tests"), fixture.as_os_str()]); + assert_success_stdout( + run, + "test \"add works\" ... ok\n1 test(s) passed\n", + "top-level test runner output", + ); + + let compile = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + assert!( + compile.status.success(), + "compiler rejected top-level test fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define i32 @add") && stdout.contains("define i32 @main"), + "compiler output for top-level test fixture lost function emission\nstdout:\n{}", + stdout + ); + assert!( + !stdout.contains("add works"), + "compiler output should not emit top-level test names as LLVM metadata\nstdout:\n{}", + stdout + ); + assert!( + stderr.is_empty(), + "compiler wrote stderr for top-level test fixture:\n{}", + stderr + ); +} + +fn assert_array_tooling_matches_fixtures(example: &Path, formatter_fixture: &Path) { + let compile = run_glagol([example.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + assert!( + compile.status.success(), + "compiler rejected array fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define i32 @immediate_second()") + && stdout.contains("insertvalue [3 x i32]") + && stdout.contains("extractvalue [3 x i32]") + && stdout.contains("%values.addr = alloca [3 x i32]") + && stdout.contains("getelementptr inbounds [3 x i32]") + && stdout.contains("load i32"), + "compiler output for array fixture lost index shape\nstdout:\n{}", + stdout + ); + assert!( + !stdout.contains("immediate array index") && !stdout.contains("array local index"), + "compiler output should not emit top-level test names as LLVM metadata\nstdout:\n{}", + stdout + ); + assert!( + stderr.is_empty(), + "compiler wrote stderr for array fixture:\n{}", + stderr + ); + + let run = run_glagol([OsStr::new("--run-tests"), example.as_os_str()]); + assert_success_stdout( + run, + concat!( + "test \"immediate array index\" ... ok\n", + "test \"array local index\" ... ok\n", + "2 test(s) passed\n", + ), + "array test runner output", + ); + + let expected_format = read(formatter_fixture); + let format = run_glagol([OsStr::new("--format"), formatter_fixture.as_os_str()]); + assert_success_stdout(format, &expected_format, "array formatter output"); +} + +fn assert_array_direct_scalars_tooling_matches_fixtures(example: &Path, formatter_fixture: &Path) { + let compile = run_glagol([example.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + assert!( + compile.status.success(), + "compiler rejected direct-scalar array fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define i32 @i32_second()") + && stdout.contains("define i64 @i64_local_pick()") + && stdout.contains("define double @f64_third()") + && stdout.contains("define i1 @bool_local_pick()") + && stdout.contains("insertvalue [3 x i32]") + && stdout.contains("insertvalue [3 x double]") + && stdout.contains("alloca [3 x i64]") + && stdout.contains("alloca [3 x i1]"), + "compiler output for direct-scalar array fixture lost widened array shape\nstdout:\n{}", + stdout + ); + assert!( + !stdout.contains("i32 direct scalar array index") + && !stdout.contains("bool local direct scalar array index"), + "compiler output should not emit top-level test names as LLVM metadata\nstdout:\n{}", + stdout + ); + assert!( + stderr.is_empty(), + "compiler wrote stderr for direct-scalar array fixture:\n{}", + stderr + ); + + let run = run_glagol([OsStr::new("--run-tests"), example.as_os_str()]); + assert_success_stdout( + run, + concat!( + "test \"i32 direct scalar array index\" ... ok\n", + "test \"i64 local direct scalar array index\" ... ok\n", + "test \"f64 direct scalar array index\" ... ok\n", + "test \"bool local direct scalar array index\" ... ok\n", + "4 test(s) passed\n", + ), + "direct-scalar array test runner output", + ); + + let expected_format = read(formatter_fixture); + let format = run_glagol([OsStr::new("--format"), formatter_fixture.as_os_str()]); + assert_success_stdout( + format, + &expected_format, + "direct-scalar array formatter output", + ); +} + +fn assert_array_string_tooling_matches_fixtures(example: &Path, formatter_fixture: &Path) { + let compile = run_glagol([example.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + assert!( + compile.status.success(), + "compiler rejected string-array fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define ptr @immediate_second()") + && stdout.contains("define ptr @local_pick()") + && stdout.contains("insertvalue [3 x ptr]") + && stdout.contains("alloca [3 x ptr]"), + "compiler output for string-array fixture lost widened array shape\nstdout:\n{}", + stdout + ); + assert!( + !stdout.contains("string immediate array index") + && !stdout.contains("string local array index"), + "compiler output should not emit top-level test names as LLVM metadata\nstdout:\n{}", + stdout + ); + assert!( + stderr.is_empty(), + "compiler wrote stderr for string-array fixture:\n{}", + stderr + ); + + let run = run_glagol([OsStr::new("--run-tests"), example.as_os_str()]); + assert_success_stdout( + run, + concat!( + "test \"string immediate array index\" ... ok\n", + "test \"string local array index\" ... ok\n", + "2 test(s) passed\n", + ), + "string-array test runner output", + ); + + let expected_format = read(formatter_fixture); + let format = run_glagol([OsStr::new("--format"), formatter_fixture.as_os_str()]); + assert_success_stdout(format, &expected_format, "string-array formatter output"); +} + +fn assert_array_value_flow_tooling_matches_fixtures(example: &Path, formatter_fixture: &Path) { + let compile = run_glagol([example.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + assert!( + compile.status.success(), + "compiler rejected array value-flow fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define [3 x i32] @make_values(i32 %base)") + && stdout.contains("define i32 @at([3 x i32] %values, i32 %i)") + && stdout.contains("define i32 @parameter_local_copy([3 x i32] %values, i32 %i)") + && stdout.contains("call [3 x i32] @make_values(i32 20)") + && stdout.contains("call i32 @at([3 x i32]") + && stdout.contains("call void @__glagol_array_bounds_trap()") + && stdout.contains("array.index.trap") + && stdout.contains("getelementptr inbounds [3 x i32]"), + "compiler output for array value-flow fixture lost value-flow or bounds-check shape\nstdout:\n{}", + stdout + ); + assert!( + !stdout.contains("array parameter value flow") + && !stdout.contains("array dynamic index") + && !stdout.contains("array parameter local copy"), + "compiler output should not emit top-level test names as LLVM metadata\nstdout:\n{}", + stdout + ); + assert!( + stderr.is_empty(), + "compiler wrote stderr for array value-flow fixture:\n{}", + stderr + ); + + let run = run_glagol([OsStr::new("--run-tests"), example.as_os_str()]); + assert_success_stdout( + run, + concat!( + "test \"array parameter value flow\" ... ok\n", + "test \"array dynamic index\" ... ok\n", + "test \"array local call value flow\" ... ok\n", + "test \"array parameter local copy\" ... ok\n", + "test \"array return call value flow\" ... ok\n", + "5 test(s) passed\n", + ), + "array value-flow test runner output", + ); + + let expected_format = read(formatter_fixture); + let format = run_glagol([OsStr::new("--format"), formatter_fixture.as_os_str()]); + assert_success_stdout( + format, + &expected_format, + "array value-flow formatter output", + ); +} + +fn assert_array_direct_scalars_value_flow_tooling_matches_fixtures( + example: &Path, + formatter_fixture: &Path, +) { + let compile = run_glagol([example.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + assert!( + compile.status.success(), + "compiler rejected direct-scalar array value-flow fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define [3 x i32] @make_i32_values(i32 %base)") + && stdout.contains("define [3 x i64] @make_i64_values(i64 %base)") + && stdout.contains("define [3 x double] @make_f64_values(double %base)") + && stdout.contains("define [3 x i1] @make_flags(i1 %first)") + && stdout.contains("define i64 @i64_at([3 x i64] %values, i32 %i)") + && stdout.contains("define double @f64_at([3 x double] %values, i32 %i)") + && stdout.contains("define [3 x i1] @echo_flags([3 x i1] %values)") + && stdout.contains("call void @__glagol_array_bounds_trap()") + && stdout.contains("array.index.trap") + && stdout.contains("getelementptr inbounds [3 x i64]") + && stdout.contains("getelementptr inbounds [3 x double]"), + "compiler output for direct-scalar array value-flow fixture lost value-flow or bounds-check shape\nstdout:\n{}", + stdout + ); + assert!( + !stdout.contains("i64 array local call value flow") + && !stdout.contains("bool array dynamic index"), + "compiler output should not emit top-level test names as LLVM metadata\nstdout:\n{}", + stdout + ); + assert!( + stderr.is_empty(), + "compiler wrote stderr for direct-scalar array value-flow fixture:\n{}", + stderr + ); + + let run = run_glagol([OsStr::new("--run-tests"), example.as_os_str()]); + assert_success_stdout( + run, + concat!( + "test \"i32 array parameter value flow\" ... ok\n", + "test \"i64 array local call value flow\" ... ok\n", + "test \"f64 array parameter local copy\" ... ok\n", + "test \"bool array dynamic index\" ... ok\n", + "test \"bool array return call value flow\" ... ok\n", + "5 test(s) passed\n", + ), + "direct-scalar array value-flow test runner output", + ); + + let expected_format = read(formatter_fixture); + let format = run_glagol([OsStr::new("--format"), formatter_fixture.as_os_str()]); + assert_success_stdout( + format, + &expected_format, + "direct-scalar array value-flow formatter output", + ); +} + +fn assert_array_string_value_flow_tooling_matches_fixtures( + example: &Path, + formatter_fixture: &Path, +) { + let compile = run_glagol([example.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + assert!( + compile.status.success(), + "compiler rejected string-array value-flow fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define [3 x ptr] @make_words(ptr %head)") + && stdout.contains("define ptr @at([3 x ptr] %values, i32 %i)") + && stdout.contains("define [3 x ptr] @echo([3 x ptr] %values)") + && stdout.contains("call void @__glagol_array_bounds_trap()") + && stdout.contains("array.index.trap") + && stdout.contains("getelementptr inbounds [3 x ptr]"), + "compiler output for string-array value-flow fixture lost value-flow or bounds-check shape\nstdout:\n{}", + stdout + ); + assert!( + !stdout.contains("string array local call value flow") + && !stdout.contains("string array dynamic index"), + "compiler output should not emit top-level test names as LLVM metadata\nstdout:\n{}", + stdout + ); + assert!( + stderr.is_empty(), + "compiler wrote stderr for string-array value-flow fixture:\n{}", + stderr + ); + + let run = run_glagol([OsStr::new("--run-tests"), example.as_os_str()]); + assert_success_stdout( + run, + concat!( + "test \"string array parameter value flow\" ... ok\n", + "test \"string array dynamic index\" ... ok\n", + "test \"string array local call value flow\" ... ok\n", + "test \"string array parameter local copy\" ... ok\n", + "test \"string array return call value flow\" ... ok\n", + "5 test(s) passed\n", + ), + "string-array value-flow test runner output", + ); + + let expected_format = read(formatter_fixture); + let format = run_glagol([OsStr::new("--format"), formatter_fixture.as_os_str()]); + assert_success_stdout( + format, + &expected_format, + "string-array value-flow formatter output", + ); +} + +fn assert_local_variable_tooling_matches_fixtures(example: &Path, formatter_fixture: &Path) { + let compile = run_glagol([example.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + assert!( + compile.status.success(), + "compiler rejected local-variable fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define i32 @add_local") + && stdout.contains("%total.addr = alloca i32") + && stdout.contains("add i32 %a, 1") + && stdout.contains("store i32") + && stdout.contains("load i32") + && stdout.contains("define i1 @keep_flag(i1 %flag)") + && !stdout.contains("%local_flag.addr = alloca i1") + && stdout.contains("define i1 @flip_flag(i1 %flag)") + && stdout.contains("%current.addr = alloca i1") + && stdout.contains("%count.addr = alloca i64") + && stdout.contains("%amount.addr = alloca double"), + "compiler output for local-variable fixture lost local value/storage shape\nstdout:\n{}", + stdout + ); + assert!( + !stdout.contains("locals work") + && !stdout.contains("bool let locals work") + && !stdout.contains("bool let locals preserve false") + && !stdout.contains("bool var set flips true") + && !stdout.contains("bool var set flips false") + && !stdout.contains("i64 var set works") + && !stdout.contains("f64 var set works"), + "compiler output should not emit top-level test names as LLVM metadata\nstdout:\n{}", + stdout + ); + assert!( + stderr.is_empty(), + "compiler wrote stderr for local-variable fixture:\n{}", + stderr + ); + + let run = run_glagol([OsStr::new("--run-tests"), example.as_os_str()]); + assert_success_stdout( + run, + concat!( + "test \"locals work\" ... ok\n", + "test \"bool let locals work\" ... ok\n", + "test \"bool let locals preserve false\" ... ok\n", + "test \"bool var set flips true\" ... ok\n", + "test \"bool var set flips false\" ... ok\n", + "test \"i64 var set works\" ... ok\n", + "test \"f64 var set works\" ... ok\n", + "7 test(s) passed\n", + ), + "local-variable test runner output", + ); + + let expected_format = read(formatter_fixture); + let format = run_glagol([OsStr::new("--format"), formatter_fixture.as_os_str()]); + assert_success_stdout(format, &expected_format, "local-variable formatter output"); +} + +fn assert_option_result_tooling_matches_fixtures(example: &Path, formatter_fixture: &Path) { + let compile = run_glagol([example.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + assert!( + compile.status.success(), + "compiler rejected option/result fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define { i1, i32 } @maybe_value()") + && stdout.contains("define { i1, i32 } @maybe_empty()") + && stdout.contains("define { i1, i32 } @result_ok()") + && stdout.contains("define { i1, i32 } @result_err()") + && stdout.contains("insertvalue { i1, i32 } undef, i1 1, 0") + && stdout.contains("insertvalue { i1, i32 } undef, i1 0, 0") + && stdout.contains("ret { i1, i32 } %") + && !stdout.contains("define ptr @maybe_value") + && !stdout.contains("define ptr @result_ok"), + "compiler output for option/result fixture lost aggregate shape\nstdout:\n{}", + stdout + ); + assert!( + stderr.is_empty(), + "compiler wrote stderr for option/result fixture:\n{}", + stderr + ); + + let expected_format = read(formatter_fixture); + let format = run_glagol([OsStr::new("--format"), formatter_fixture.as_os_str()]); + assert_success_stdout(format, &expected_format, "option/result formatter output"); +} + +fn assert_option_result_flow_tooling_matches_fixtures(example: &Path, formatter_fixture: &Path) { + let compile = run_glagol([example.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + assert!( + compile.status.success(), + "compiler rejected option/result flow fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define i32 @option_score({ i1, i32 } %value)") + && stdout.contains("define i32 @option_wide_score({ i1, i64 } %value)") + && stdout.contains("define i32 @option_string_score({ i1, ptr } %value)") + && stdout.contains("define i32 @result_success_score({ i1, i32 } %value)") + && stdout.contains("%value.addr = alloca { i1, i32 }") + && stdout.contains("%value.addr = alloca { i1, ptr }") + && stdout.contains("call { i1, i64 } @maybe_wide_value(i64 2147483648)") + && stdout.contains("call { i1, i32 } @maybe_value(i32 42)") + && stdout.contains("call { i1, ptr } @maybe_string_value(ptr @.str.") + && stdout.contains("extractvalue { i1, i32 } %") + && stdout.contains("extractvalue { i1, ptr } %") + && stdout.contains("extractvalue { i1, i64 } %") + && stdout.contains("xor i1 %"), + "compiler output for option/result flow fixture lost value-flow shape\nstdout:\n{}", + stdout + ); + assert!( + stderr.is_empty(), + "compiler wrote stderr for option/result flow fixture:\n{}", + stderr + ); + + let run = run_glagol([OsStr::new("--run-tests"), example.as_os_str()]); + assert_success_stdout( + run, + concat!( + "test \"option local value flow\" ... ok\n", + "test \"option call observation\" ... ok\n", + "test \"option i64 local value flow\" ... ok\n", + "test \"option i64 call observation\" ... ok\n", + "test \"option f64 local value flow\" ... ok\n", + "test \"option f64 call observation\" ... ok\n", + "test \"option bool local value flow\" ... ok\n", + "test \"option bool call observation\" ... ok\n", + "test \"option string local value flow\" ... ok\n", + "test \"option string call observation\" ... ok\n", + "test \"result call observation\" ... ok\n", + "test \"result local value flow\" ... ok\n", + "12 test(s) passed\n", + ), + "option/result flow test runner output", + ); + + let expected_format = read(formatter_fixture); + let format = run_glagol([OsStr::new("--format"), formatter_fixture.as_os_str()]); + assert_success_stdout( + format, + &expected_format, + "option/result flow formatter output", + ); +} + +fn assert_option_result_match_tooling_matches_fixtures(example: &Path, formatter_fixture: &Path) { + let compile = run_glagol([example.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + assert!( + compile.status.success(), + "compiler rejected option/result match fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define i32 @option_value_or({ i1, i32 } %value, i32 %fallback)") + && stdout.contains("define i32 @option_bump_or_zero({ i1, i32 } %value)") + && stdout + .contains("define ptr @option_string_value_or({ i1, ptr } %value, ptr %fallback)") + && stdout.contains( + "define ptr @option_string_selected_or({ i1, ptr } %value, ptr %fallback)" + ) + && stdout.contains("define i32 @result_value_or_code({ i1, i32 } %value)") + && stdout.contains("define i32 @result_score({ i1, i32 } %value)") + && stdout.contains("match.some") + && stdout.contains("match.none") + && stdout.contains("match.ok") + && stdout.contains("match.err") + && stdout.contains(" phi i32 ") + && stdout.contains(" phi ptr "), + "compiler output for option/result match fixture lost branch shape\nstdout:\n{}", + stdout + ); + assert!( + stderr.is_empty(), + "compiler wrote stderr for option/result match fixture:\n{}", + stderr + ); + + let run = run_glagol([OsStr::new("--run-tests"), example.as_os_str()]); + assert_success_stdout( + run, + concat!( + "test \"option match some payload\" ... ok\n", + "test \"option match none fallback\" ... ok\n", + "test \"option match multi expression arm\" ... ok\n", + "test \"option i64 match some payload\" ... ok\n", + "test \"option i64 match none fallback\" ... ok\n", + "test \"option i64 match multi expression arm\" ... ok\n", + "test \"option f64 match some payload\" ... ok\n", + "test \"option f64 match none fallback\" ... ok\n", + "test \"option f64 match multi expression arm\" ... ok\n", + "test \"option bool match some payload\" ... ok\n", + "test \"option bool match none fallback\" ... ok\n", + "test \"option bool match multi expression arm\" ... ok\n", + "test \"option string match some payload\" ... ok\n", + "test \"option string match none fallback\" ... ok\n", + "test \"option string match multi expression arm\" ... ok\n", + "test \"result match ok payload\" ... ok\n", + "test \"result match err payload\" ... ok\n", + "test \"result match computed arm\" ... ok\n", + "18 test(s) passed\n", + ), + "option/result match test runner output", + ); + + let expected_format = read(formatter_fixture); + let format = run_glagol([OsStr::new("--format"), formatter_fixture.as_os_str()]); + assert_success_stdout( + format, + &expected_format, + "option/result match formatter output", + ); +} + +fn assert_option_result_payload_tooling_matches_fixtures(example: &Path, formatter_fixture: &Path) { + let compile = run_glagol([example.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + assert!( + compile.status.success(), + "compiler rejected option/result payload fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define i32 @option_direct_payload()") + && stdout.contains("define i32 @option_param_payload({ i1, i32 } %value)") + && stdout.contains("define i32 @option_local_payload()") + && stdout.contains("define i32 @option_call_payload()") + && stdout.contains("define i32 @option_guarded_payload({ i1, i32 } %value)") + && stdout.contains("define i32 @result_ok_direct_payload()") + && stdout.contains("define i32 @result_err_direct_payload()") + && stdout.contains("define i32 @result_ok_param_payload({ i1, i32 } %value)") + && stdout.contains("define i32 @result_err_param_payload({ i1, i32 } %value)") + && stdout.contains("define i32 @result_ok_local_payload()") + && stdout.contains("define i32 @result_err_call_payload()") + && stdout.contains("call void @__glagol_unwrap_some_trap()") + && stdout.contains("call void @__glagol_unwrap_ok_trap()") + && stdout.contains("call void @__glagol_unwrap_err_trap()") + && stdout.contains("unwrap.trap") + && stdout.contains("if.then") + && stdout.contains(" phi i32 ") + && stdout.contains("extractvalue { i1, i32 } %"), + "compiler output for option/result payload fixture lost payload-access shape\nstdout:\n{}", + stdout + ); + assert!( + stderr.is_empty(), + "compiler wrote stderr for option/result payload fixture:\n{}", + stderr + ); + + let run = run_glagol([OsStr::new("--run-tests"), example.as_os_str()]); + assert_success_stdout( + run, + concat!( + "test \"unwrap some direct constructor\" ... ok\n", + "test \"unwrap some local\" ... ok\n", + "test \"unwrap some call\" ... ok\n", + "test \"unwrap some parameter\" ... ok\n", + "test \"guarded unwrap some present\" ... ok\n", + "test \"guarded unwrap some absent\" ... ok\n", + "test \"unwrap some i64 direct constructor\" ... ok\n", + "test \"unwrap some i64 local\" ... ok\n", + "test \"unwrap some i64 call\" ... ok\n", + "test \"unwrap some i64 parameter\" ... ok\n", + "test \"guarded unwrap some i64 present\" ... ok\n", + "test \"guarded unwrap some i64 absent\" ... ok\n", + "test \"unwrap some f64 direct constructor\" ... ok\n", + "test \"unwrap some f64 local\" ... ok\n", + "test \"unwrap some f64 call\" ... ok\n", + "test \"unwrap some f64 parameter\" ... ok\n", + "test \"guarded unwrap some f64 present\" ... ok\n", + "test \"guarded unwrap some f64 absent\" ... ok\n", + "test \"unwrap some bool direct constructor\" ... ok\n", + "test \"unwrap some bool local\" ... ok\n", + "test \"unwrap some bool call\" ... ok\n", + "test \"unwrap some bool parameter\" ... ok\n", + "test \"guarded unwrap some bool present\" ... ok\n", + "test \"guarded unwrap some bool absent\" ... ok\n", + "test \"unwrap some string direct constructor\" ... ok\n", + "test \"unwrap some string local\" ... ok\n", + "test \"unwrap some string call\" ... ok\n", + "test \"unwrap some string parameter\" ... ok\n", + "test \"guarded unwrap some string present\" ... ok\n", + "test \"guarded unwrap some string absent\" ... ok\n", + "test \"unwrap ok direct constructor\" ... ok\n", + "test \"unwrap err direct constructor\" ... ok\n", + "test \"unwrap ok parameter\" ... ok\n", + "test \"unwrap err parameter\" ... ok\n", + "test \"unwrap ok local\" ... ok\n", + "test \"unwrap err call\" ... ok\n", + "36 test(s) passed\n", + ), + "option/result payload test runner output", + ); + + let expected_format = read(formatter_fixture); + let format = run_glagol([OsStr::new("--format"), formatter_fixture.as_os_str()]); + assert_success_stdout( + format, + &expected_format, + "option/result payload formatter output", + ); +} + +fn assert_enum_struct_field_tooling_matches_fixtures(example: &Path, formatter_fixture: &Path) { + let compile = run_glagol([example.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + assert!( + compile.status.success(), + "compiler rejected enum struct field fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define { i32, { i32, i32 } } @make_tagged") + && stdout.contains("extractvalue { i32, { i32, i32 } }") + && stdout.contains("switch i32") + && stdout.contains("and i1"), + "compiler output for enum struct field fixture lost aggregate/match shape\nstdout:\n{}", + stdout + ); + assert!( + stderr.is_empty(), + "compiler wrote stderr for enum struct field fixture:\n{}", + stderr + ); + + let run = run_glagol([OsStr::new("--run-tests"), example.as_os_str()]); + assert_success_stdout( + run, + concat!( + "test \"enum struct field payloadless equality\" ... ok\n", + "test \"enum struct field unary payload equality\" ... ok\n", + "test \"enum struct field access return\" ... ok\n", + "test \"enum struct field local param return call flow\" ... ok\n", + "test \"enum struct field match missing\" ... ok\n", + "test \"enum struct field status predicate\" ... ok\n", + "6 test(s) passed\n", + ), + "enum struct field test runner output", + ); + + let expected_format = read(formatter_fixture); + let format = run_glagol([OsStr::new("--format"), formatter_fixture.as_os_str()]); + assert_success_stdout( + format, + &expected_format, + "enum struct field formatter output", + ); +} + +fn assert_primitive_struct_field_tooling_matches_fixtures( + example: &Path, + formatter_fixture: &Path, +) { + let compile = run_glagol([example.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + assert!( + compile.status.success(), + "compiler rejected primitive struct field fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define { i32, i1, ptr } @make_record") + && stdout.contains("insertvalue { i32, i1, ptr }") + && stdout.contains("extractvalue { i32, i1, ptr }") + && stdout.contains("call i1 @__glagol_string_eq") + && stdout.contains("call i32 @string_len"), + "compiler output for primitive struct field fixture lost aggregate/string shape\nstdout:\n{}", + stdout + ); + assert!( + stderr.is_empty(), + "compiler wrote stderr for primitive struct field fixture:\n{}", + stderr + ); + + let run = run_glagol([OsStr::new("--run-tests"), example.as_os_str()]); + assert_success_stdout( + run, + concat!( + "test \"primitive struct i32 field access\" ... ok\n", + "test \"primitive struct bool field predicate\" ... ok\n", + "test \"primitive struct bool field false branch\" ... ok\n", + "test \"primitive struct string field equality\" ... ok\n", + "test \"primitive struct string field length\" ... ok\n", + "test \"primitive struct local param return call flow\" ... ok\n", + "test \"primitive struct string field access return\" ... ok\n", + "7 test(s) passed\n", + ), + "primitive struct field test runner output", + ); + + let expected_format = read(formatter_fixture); + let format = run_glagol([OsStr::new("--format"), formatter_fixture.as_os_str()]); + assert_success_stdout( + format, + &expected_format, + "primitive struct field formatter output", + ); +} + +fn assert_numeric_struct_field_tooling_matches_fixtures(example: &Path, formatter_fixture: &Path) { + let compile = run_glagol([example.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + assert!( + compile.status.success(), + "compiler rejected numeric struct field fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define { i64, double } @make_record") + && stdout.contains("insertvalue { i64, double }") + && stdout.contains("extractvalue { i64, double }") + && stdout.contains("add i64") + && stdout.contains("fadd double"), + "compiler output for numeric struct field fixture lost aggregate/numeric shape\nstdout:\n{}", + stdout + ); + assert!( + stderr.is_empty(), + "compiler wrote stderr for numeric struct field fixture:\n{}", + stderr + ); + + let run = run_glagol([OsStr::new("--run-tests"), example.as_os_str()]); + assert_success_stdout( + run, + concat!( + "test \"numeric struct i64 field access\" ... ok\n", + "test \"numeric struct f64 field access\" ... ok\n", + "test \"numeric struct i64 arithmetic after access\" ... ok\n", + "test \"numeric struct f64 arithmetic after access\" ... ok\n", + "test \"numeric struct i64 comparison after access\" ... ok\n", + "test \"numeric struct f64 comparison after access\" ... ok\n", + "test \"numeric struct local param return call flow\" ... ok\n", + "test \"numeric struct i64 format after access\" ... ok\n", + "test \"numeric struct f64 format after access\" ... ok\n", + "9 test(s) passed\n", + ), + "numeric struct field test runner output", + ); + + let expected_format = read(formatter_fixture); + let format = run_glagol([OsStr::new("--format"), formatter_fixture.as_os_str()]); + assert_success_stdout( + format, + &expected_format, + "numeric struct field formatter output", + ); +} + +fn assert_string_print_tooling_matches_fixtures(example: &Path, formatter_fixture: &Path) { + let compile = run_glagol([example.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + + assert!( + compile.status.success(), + "string-print fixture compile failed\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("declare void @print_string(ptr)") + && stdout.contains( + "@.str.0 = private unnamed_addr constant [6 x i8] c\"hello\\00\", align 1", + ) + && stdout.contains( + "@.str.1 = private unnamed_addr constant [22 x i8] c\"line\\0Aquote\\22slash\\5Ctab\\09\\00\", align 1", + ) + && stdout.contains("call void @print_string(ptr @.str.0)") + && stdout.contains("call void @print_string(ptr @.str.1)"), + "string-print fixture LLVM shape drifted\nstdout:\n{}", + stdout + ); + assert!( + stderr.is_empty(), + "string-print fixture compile wrote stderr:\n{}", + stderr + ); + + let expected_format = read(formatter_fixture); + let format = run_glagol([OsStr::new("--format"), formatter_fixture.as_os_str()]); + assert_success_stdout(format, &expected_format, "string-print formatter output"); +} + +fn assert_print_bool_tooling_matches_fixtures(example: &Path, formatter_fixture: &Path) { + let compile = run_glagol([example.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + + assert!( + compile.status.success(), + "print-bool fixture compile failed\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("declare void @print_bool(i1)") + && stdout.contains("declare i1 @__glagol_string_eq(ptr, ptr)") + && stdout.contains("call void @print_bool(i1 1)") + && stdout.contains("call void @print_bool(i1 0)") + && stdout.contains("call i1 @__glagol_string_eq(ptr @.str.0, ptr @.str.0)"), + "print-bool fixture LLVM shape drifted\nstdout:\n{}", + stdout + ); + assert!( + stderr.is_empty(), + "print-bool fixture compile wrote stderr:\n{}", + stderr + ); + + let expected_format = read(formatter_fixture); + let format = run_glagol([OsStr::new("--format"), formatter_fixture.as_os_str()]); + assert_success_stdout(format, &expected_format, "print-bool formatter output"); +} + +fn assert_random_tooling_matches_fixtures(example: &Path, formatter_fixture: &Path) { + let compile = run_glagol([example.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + + assert!( + compile.status.success(), + "random fixture compile failed\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("declare i32 @__glagol_random_i32()") + && stdout.contains("call i32 @__glagol_random_i32()") + && stdout.contains("declare void @print_bool(i1)") + && !stdout.contains("@std.random"), + "random fixture LLVM shape drifted\nstdout:\n{}", + stdout + ); + assert!( + stderr.is_empty(), + "random fixture compile wrote stderr:\n{}", + stderr + ); + + let run = run_glagol([OsStr::new("--run-tests"), example.as_os_str()]); + assert_success_stdout( + run, + concat!( + "test \"random i32 is non-negative\" ... ok\n", + "1 test(s) passed\n", + ), + "random test runner output", + ); + + let expected_format = read(formatter_fixture); + let format = run_glagol([OsStr::new("--format"), formatter_fixture.as_os_str()]); + assert_success_stdout(format, &expected_format, "random formatter output"); +} + +fn assert_stdin_result_tooling_matches_fixtures(example: &Path, formatter_fixture: &Path) { + let compile = run_glagol([example.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + + assert!( + compile.status.success(), + "stdin-result fixture compile failed\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("declare ptr @__glagol_io_read_stdin_result()") + && stdout.contains("call ptr @__glagol_io_read_stdin_result()") + && !stdout.contains("@std.io.read_stdin_result"), + "stdin-result fixture LLVM shape drifted\nstdout:\n{}", + stdout + ); + assert!( + stderr.is_empty(), + "stdin-result fixture compile wrote stderr:\n{}", + stderr + ); + + let run = run_glagol([OsStr::new("--run-tests"), example.as_os_str()]); + assert_success_stdout( + run, + concat!( + "test \"stdin result test runner returns ok\" ... ok\n", + "test \"stdin result payload length matches match\" ... ok\n", + "test \"stdin result match observes ok payload\" ... ok\n", + "3 test(s) passed\n", + ), + "stdin-result test runner output", + ); + + let expected_format = read(formatter_fixture); + let format = run_glagol([OsStr::new("--format"), formatter_fixture.as_os_str()]); + assert_success_stdout(format, &expected_format, "stdin-result formatter output"); +} + +fn assert_string_parse_i32_result_tooling_matches_fixtures( + example: &Path, + formatter_fixture: &Path, +) { + let compile = run_glagol([example.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + + assert!( + compile.status.success(), + "string-parse-i32-result fixture compile failed\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("declare i64 @__glagol_string_parse_i32_result(ptr)") + && stdout.contains("call i64 @__glagol_string_parse_i32_result(ptr ") + && stdout.contains("declare ptr @__glagol_io_read_stdin_result()") + && stdout.contains("call ptr @__glagol_io_read_stdin_result()") + && stdout.contains("define { i1, i32 } @parse_text(ptr %text)") + && stdout.contains("define { i1, i32 } @parse_stdin_text()") + && stdout.contains("lshr i64") + && stdout.contains("trunc i64") + && stdout.contains("insertvalue { i1, i32 }") + && !stdout.contains("@std.string.parse_i32_result"), + "string-parse-i32-result fixture LLVM shape drifted\nstdout:\n{}", + stdout + ); + assert!( + stderr.is_empty(), + "string-parse-i32-result fixture compile wrote stderr:\n{}", + stderr + ); + + let run = run_glagol([OsStr::new("--run-tests"), example.as_os_str()]); + assert_success_stdout( + run, + concat!( + "test \"parse 42 ok\" ... ok\n", + "test \"parse negative 7 ok\" ... ok\n", + "test \"parse empty err\" ... ok\n", + "test \"parse trailing byte err\" ... ok\n", + "test \"parse overflow err\" ... ok\n", + "test \"parse stdin text structurally\" ... ok\n", + "6 test(s) passed\n", + ), + "string-parse-i32-result test runner output", + ); + + let expected_format = read(formatter_fixture); + let format = run_glagol([OsStr::new("--format"), formatter_fixture.as_os_str()]); + assert_success_stdout( + format, + &expected_format, + "string-parse-i32-result formatter output", + ); +} + +fn assert_string_parse_i64_result_tooling_matches_fixtures( + example: &Path, + formatter_fixture: &Path, +) { + let compile = run_glagol([example.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + + assert!( + compile.status.success(), + "string-parse-i64-result fixture compile failed\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("declare i32 @__glagol_string_parse_i64_result(ptr, ptr)") + && stdout.contains("call i32 @__glagol_string_parse_i64_result(ptr ") + && stdout.contains("define { i1, i64, i32 } @parse_i64(ptr %text)") + && stdout.contains("alloca i64") + && stdout.contains("insertvalue { i1, i64, i32 }") + && stdout.contains("extractvalue { i1, i64, i32 }") + && stdout.contains("call ptr @__glagol_num_i64_to_string(i64 ") + && !stdout.contains("@std.string.parse_i64_result"), + "string-parse-i64-result fixture LLVM shape drifted\nstdout:\n{}", + stdout + ); + assert!( + stderr.is_empty(), + "string-parse-i64-result fixture compile wrote stderr:\n{}", + stderr + ); + + let run = run_glagol([OsStr::new("--run-tests"), example.as_os_str()]); + assert_success_stdout( + run, + concat!( + "test \"parse i64 zero ok\" ... ok\n", + "test \"parse i64 negative ok\" ... ok\n", + "test \"parse i64 low ok\" ... ok\n", + "test \"parse i64 high ok\" ... ok\n", + "test \"parse i64 empty err\" ... ok\n", + "test \"parse i64 plus err\" ... ok\n", + "test \"parse i64 above range err\" ... ok\n", + "7 test(s) passed\n", + ), + "string-parse-i64-result test runner output", + ); + + let expected_format = read(formatter_fixture); + let format = run_glagol([OsStr::new("--format"), formatter_fixture.as_os_str()]); + assert_success_stdout( + format, + &expected_format, + "string-parse-i64-result formatter output", + ); +} + +fn assert_string_parse_f64_result_tooling_matches_fixtures( + example: &Path, + formatter_fixture: &Path, +) { + let compile = run_glagol([example.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + + assert!( + compile.status.success(), + "string-parse-f64-result fixture compile failed\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("declare i32 @__glagol_string_parse_f64_result(ptr, ptr)") + && stdout.contains("call i32 @__glagol_string_parse_f64_result(ptr ") + && stdout.contains("define { i1, double, i32 } @parse_f64(ptr %text)") + && stdout.contains("alloca double") + && stdout.contains("insertvalue { i1, double, i32 }") + && stdout.contains("extractvalue { i1, double, i32 }") + && !stdout.contains("@std.string.parse_f64_result"), + "string-parse-f64-result fixture LLVM shape drifted\nstdout:\n{}", + stdout + ); + assert!( + stderr.is_empty(), + "string-parse-f64-result fixture compile wrote stderr:\n{}", + stderr + ); + + let run = run_glagol([OsStr::new("--run-tests"), example.as_os_str()]); + assert_success_stdout( + run, + concat!( + "test \"parse f64 decimal ok\" ... ok\n", + "test \"parse f64 negative decimal ok\" ... ok\n", + "test \"parse f64 text err\" ... ok\n", + "test \"parse f64 nan err\" ... ok\n", + "4 test(s) passed\n", + ), + "string-parse-f64-result test runner output", + ); + + let expected_format = read(formatter_fixture); + let format = run_glagol([OsStr::new("--format"), formatter_fixture.as_os_str()]); + assert_success_stdout( + format, + &expected_format, + "string-parse-f64-result formatter output", + ); +} + +fn assert_result_f64_bool_source_flow_matches_fixture(example: &Path) { + let format = run_glagol([ + OsStr::new("fmt"), + OsStr::new("--check"), + example.as_os_str(), + ]); + assert_success_stdout(format, "", "result f64 bool source flow fmt --check"); + + let check = run_glagol([OsStr::new("check"), example.as_os_str()]); + assert_success_stdout(check, "", "result f64 bool source flow check"); + + let test = run_glagol([OsStr::new("test"), example.as_os_str()]); + assert_success_stdout( + test, + concat!( + "test \"result f64 ok constructor and match\" ... ok\n", + "test \"result f64 err constructor and match\" ... ok\n", + "test \"result bool ok constructor and match\" ... ok\n", + "test \"result bool err constructor and match\" ... ok\n", + "4 test(s) passed\n", + ), + "result f64 bool source flow test", + ); + + let compile = run_glagol([example.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + assert!( + compile.status.success(), + "result f64 bool source flow compile failed\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("insertvalue { i1, double, i32 }") + && stdout.contains("insertvalue { i1, i1, i32 }") + && stdout.contains("extractvalue { i1, double, i32 }") + && stdout.contains("extractvalue { i1, i1, i32 }"), + "result f64 bool source flow LLVM shape drifted\nstdout:\n{}", + stdout + ); +} + +fn assert_integer_remainder_tooling_matches_fixture(example: &Path) { + let format = run_glagol([ + OsStr::new("fmt"), + OsStr::new("--check"), + example.as_os_str(), + ]); + assert_success_stdout(format, "", "integer remainder fmt --check"); + + let check = run_glagol([OsStr::new("check"), example.as_os_str()]); + assert_success_stdout(check, "", "integer remainder check"); + + let test = run_glagol([OsStr::new("test"), example.as_os_str()]); + assert_success_stdout( + test, + concat!( + "test \"i32 remainder\" ... ok\n", + "test \"i32 signed remainder\" ... ok\n", + "test \"i64 remainder\" ... ok\n", + "test \"i64 signed remainder\" ... ok\n", + "test \"integer remainder summary\" ... ok\n", + "5 test(s) passed\n", + ), + "integer remainder test", + ); + + let compile = run_glagol([example.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + assert!( + compile.status.success(), + "integer remainder compile failed\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("srem i32") && stdout.contains("srem i64"), + "integer remainder LLVM shape drifted\nstdout:\n{}", + stdout + ); + assert!( + stderr.is_empty(), + "integer remainder compile wrote stderr:\n{}", + stderr + ); +} + +fn assert_boolean_logic_tooling_matches_fixture(example: &Path) { + let format = run_glagol([ + OsStr::new("fmt"), + OsStr::new("--check"), + example.as_os_str(), + ]); + assert_success_stdout(format, "", "boolean logic fmt --check"); + + let check = run_glagol([OsStr::new("check"), example.as_os_str()]); + assert_success_stdout(check, "", "boolean logic check"); + + let test = run_glagol([OsStr::new("test"), example.as_os_str()]); + assert_success_stdout( + test, + concat!( + "test \"and true\" ... ok\n", + "test \"and false\" ... ok\n", + "test \"or true\" ... ok\n", + "test \"not false\" ... ok\n", + "test \"and short circuit\" ... ok\n", + "test \"or short circuit\" ... ok\n", + "test \"boolean logic summary\" ... ok\n", + "7 test(s) passed\n", + ), + "boolean logic test", + ); + + let compile = run_glagol([example.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + assert!( + compile.status.success(), + "boolean logic compile failed\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define i1 @and_true()") + && stdout.contains("define i1 @and_short_circuits()") + && stdout.contains("br i1"), + "boolean logic LLVM shape drifted\nstdout:\n{}", + stdout + ); + assert!( + stderr.is_empty(), + "boolean logic compile wrote stderr:\n{}", + stderr + ); +} + +fn assert_integer_bitwise_tooling_matches_fixture(example: &Path) { + let format = run_glagol([ + OsStr::new("fmt"), + OsStr::new("--check"), + example.as_os_str(), + ]); + assert_success_stdout(format, "", "integer bitwise fmt --check"); + + let check = run_glagol([OsStr::new("check"), example.as_os_str()]); + assert_success_stdout(check, "", "integer bitwise check"); + + let test = run_glagol([OsStr::new("test"), example.as_os_str()]); + assert_success_stdout( + test, + concat!( + "test \"i32 bit and\" ... ok\n", + "test \"i32 bit or\" ... ok\n", + "test \"i32 bit xor\" ... ok\n", + "test \"i64 bit and\" ... ok\n", + "test \"i64 bit or\" ... ok\n", + "test \"i64 bit xor\" ... ok\n", + "test \"integer bitwise summary\" ... ok\n", + "7 test(s) passed\n", + ), + "integer bitwise test", + ); + + let compile = run_glagol([example.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + assert!( + compile.status.success(), + "integer bitwise compile failed\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("and i32") + && stdout.contains("or i32") + && stdout.contains("xor i32") + && stdout.contains("and i64") + && stdout.contains("or i64") + && stdout.contains("xor i64"), + "integer bitwise LLVM shape drifted\nstdout:\n{}", + stdout + ); + assert!( + stderr.is_empty(), + "integer bitwise compile wrote stderr:\n{}", + stderr + ); +} + +fn assert_string_parse_bool_result_tooling_matches_fixtures( + example: &Path, + formatter_fixture: &Path, +) { + let compile = run_glagol([example.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + + assert!( + compile.status.success(), + "string-parse-bool-result fixture compile failed\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("declare i32 @__glagol_string_parse_bool_result(ptr, ptr)") + && stdout.contains("call i32 @__glagol_string_parse_bool_result(ptr ") + && stdout.contains("define { i1, i1, i32 } @parse_bool(ptr %text)") + && stdout.contains("define i32 @bool_score(ptr %text)") + && stdout.contains("alloca i1") + && stdout.contains("insertvalue { i1, i1, i32 }") + && stdout.contains("extractvalue { i1, i1, i32 }") + && !stdout.contains("@std.string.parse_bool_result"), + "string-parse-bool-result fixture LLVM shape drifted\nstdout:\n{}", + stdout + ); + assert!( + stderr.is_empty(), + "string-parse-bool-result fixture compile wrote stderr:\n{}", + stderr + ); + + let run = run_glagol([OsStr::new("--run-tests"), example.as_os_str()]); + assert_success_stdout( + run, + concat!( + "test \"parse bool true ok\" ... ok\n", + "test \"parse bool false ok\" ... ok\n", + "test \"parse bool uppercase err\" ... ok\n", + "test \"parse bool empty err\" ... ok\n", + "test \"parse bool whitespace err\" ... ok\n", + "test \"parse bool helper flow\" ... ok\n", + "6 test(s) passed\n", + ), + "string-parse-bool-result test runner output", + ); + + let expected_format = read(formatter_fixture); + let format = run_glagol([OsStr::new("--format"), formatter_fixture.as_os_str()]); + assert_success_stdout( + format, + &expected_format, + "string-parse-bool-result formatter output", + ); +} + +fn assert_f64_to_string_tooling_matches_fixtures(example: &Path, formatter_fixture: &Path) { + let compile = run_glagol([example.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + + assert!( + compile.status.success(), + "f64-to-string fixture compile failed\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("declare ptr @__glagol_num_f64_to_string(double)") + && stdout.contains("call ptr @__glagol_num_f64_to_string(double ") + && stdout.contains("call void @print_string(ptr %") + && !stdout.contains("@std.num.f64_to_string"), + "f64-to-string fixture LLVM shape drifted\nstdout:\n{}", + stdout + ); + assert!( + stderr.is_empty(), + "f64-to-string fixture compile wrote stderr:\n{}", + stderr + ); + + let run = run_glagol([OsStr::new("--run-tests"), example.as_os_str()]); + assert_success_stdout( + run, + concat!( + "test \"f64 zero to string\" ... ok\n", + "test \"f64 fractional to string\" ... ok\n", + "test \"f64 negative to string\" ... ok\n", + "test \"f64 whole to string\" ... ok\n", + "test \"f64 negative string length\" ... ok\n", + "test \"f64 whole string length\" ... ok\n", + "6 test(s) passed\n", + ), + "f64-to-string test runner output", + ); + + let expected_format = read(formatter_fixture); + let format = run_glagol([OsStr::new("--format"), formatter_fixture.as_os_str()]); + assert_success_stdout(format, &expected_format, "f64-to-string formatter output"); +} + +fn assert_f64_to_i32_result_tooling_matches_fixtures(example: &Path, formatter_fixture: &Path) { + let compile = run_glagol([example.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + + assert!( + compile.status.success(), + "f64-to-i32-result fixture compile failed\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("fcmp oge double") + && stdout.contains("fcmp ole double") + && stdout.contains("f64.to_i32.integral") + && stdout.contains("f64.to_i32.exponent") + && stdout.contains("f64.to_i32.fraction") + && stdout.contains("bitcast double") + && stdout.contains("lshr i64") + && stdout.contains("fptosi double") + && !stdout.contains("frem double") + && stdout.contains("phi { i1, i32 }") + && !stdout.contains("@std.num.f64_to_i32_result") + && !stdout.contains("__glagol_num_f64_to_i32_result"), + "f64-to-i32-result fixture LLVM shape drifted\nstdout:\n{}", + stdout + ); + let integral_pos = stdout + .find("f64.to_i32.integral") + .expect("integral check block is present"); + let fraction_pos = stdout + .find("f64.to_i32.fraction") + .expect("fraction bit check block is present"); + let fptosi_pos = stdout + .find("fptosi double") + .expect("narrowing conversion is present"); + assert!( + integral_pos < fraction_pos && fraction_pos < fptosi_pos, + "f64-to-i32-result must prove integrality before fptosi\nstdout:\n{}", + stdout + ); + assert!( + stderr.is_empty(), + "f64-to-i32-result fixture compile wrote stderr:\n{}", + stderr + ); + + let run = run_glagol([OsStr::new("--run-tests"), example.as_os_str()]); + assert_success_stdout( + run, + concat!( + "test \"f64 zero narrows to i32\" ... ok\n", + "test \"negative f64 narrows to i32\" ... ok\n", + "test \"fractional f64 returns err\" ... ok\n", + "test \"above i32 range f64 returns err\" ... ok\n", + "4 test(s) passed\n", + ), + "f64-to-i32-result test runner output", + ); + + let expected_format = read(formatter_fixture); + let format = run_glagol([OsStr::new("--format"), formatter_fixture.as_os_str()]); + assert_success_stdout( + format, + &expected_format, + "f64-to-i32-result formatter output", + ); +} + +fn assert_f64_to_i64_result_tooling_matches_fixtures(example: &Path, formatter_fixture: &Path) { + let compile = run_glagol([example.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + + assert!( + compile.status.success(), + "f64-to-i64-result fixture compile failed\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("fcmp oge double") + && stdout.contains("-9223372036854775808.0") + && stdout.contains("fcmp olt double") + && stdout.contains("9223372036854775808.0") + && stdout.contains("f64.to_i64.integral") + && stdout.contains("f64.to_i64.exponent") + && stdout.contains("f64.to_i64.mantissa") + && stdout.contains("f64.to_i64.fraction") + && stdout.contains("bitcast double") + && stdout.contains("lshr i64") + && stdout.contains("fptosi double") + && stdout.contains("to i64") + && !stdout.contains("frem double") + && stdout.contains("phi { i1, i64, i32 }") + && !stdout.contains("@std.num.f64_to_i64_result") + && !stdout.contains("__glagol_num_f64_to_i64_result"), + "f64-to-i64-result fixture LLVM shape drifted\nstdout:\n{}", + stdout + ); + let integral_pos = stdout + .find("f64.to_i64.integral") + .expect("integral check block is present"); + let fraction_pos = stdout + .find("f64.to_i64.fraction") + .expect("fraction bit check block is present"); + let fptosi_pos = stdout + .find("fptosi double") + .expect("narrowing conversion is present"); + assert!( + integral_pos < fraction_pos && fraction_pos < fptosi_pos, + "f64-to-i64-result must prove integrality before fptosi\nstdout:\n{}", + stdout + ); + assert!( + stderr.is_empty(), + "f64-to-i64-result fixture compile wrote stderr:\n{}", + stderr + ); + + let run = run_glagol([OsStr::new("--run-tests"), example.as_os_str()]); + assert_success_stdout( + run, + concat!( + "test \"f64 zero narrows to i64\" ... ok\n", + "test \"negative f64 narrows to i64\" ... ok\n", + "test \"fractional f64 returns err for i64\" ... ok\n", + "test \"above i64 range f64 returns err\" ... ok\n", + "4 test(s) passed\n", + ), + "f64-to-i64-result test runner output", + ); + + let expected_format = read(formatter_fixture); + let format = run_glagol([OsStr::new("--format"), formatter_fixture.as_os_str()]); + assert_success_stdout( + format, + &expected_format, + "f64-to-i64-result formatter output", + ); +} + +fn assert_result_helpers_tooling_matches_fixtures(example: &Path, formatter_fixture: &Path) { + let compile = run_glagol([example.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + + assert!( + compile.status.success(), + "result-helpers fixture compile failed\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define i1 @observe_i32_ok()") + && stdout.contains("define i32 @unwrap_i32_ok()") + && stdout.contains("define i1 @observe_text_ok()") + && stdout.contains("define ptr @unwrap_text_ok()") + && stdout.contains("__glagol_unwrap_ok_trap") + && stdout.contains("__glagol_unwrap_err_trap") + && !stdout.contains("@std.result.is_ok") + && !stdout.contains("@std.result.is_err") + && !stdout.contains("@std.result.unwrap_ok") + && !stdout.contains("@std.result.unwrap_err"), + "result-helpers fixture LLVM shape drifted\nstdout:\n{}", + stdout + ); + assert!( + stderr.is_empty(), + "result-helpers fixture compile wrote stderr:\n{}", + stderr + ); + + let run = run_glagol([OsStr::new("--run-tests"), example.as_os_str()]); + assert_success_stdout( + run, + concat!( + "test \"std result i32 observers\" ... ok\n", + "test \"std result i32 unwraps\" ... ok\n", + "test \"std result string observers\" ... ok\n", + "test \"std result string unwraps\" ... ok\n", + "test \"legacy result helpers still work\" ... ok\n", + "5 test(s) passed\n", + ), + "result-helpers test runner output", + ); + + let expected_format = read(formatter_fixture); + let format = run_glagol([OsStr::new("--format"), formatter_fixture.as_os_str()]); + assert_success_stdout(format, &expected_format, "result-helpers formatter output"); +} + +fn assert_standard_runtime_tooling_matches_fixtures(example: &Path, formatter_fixture: &Path) { + let compile = run_glagol([example.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + + assert!( + compile.status.success(), + "standard-runtime fixture compile failed\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("declare void @print_i32(i32)") + && stdout.contains("declare void @print_string(ptr)") + && stdout.contains("declare void @print_bool(i1)") + && stdout.contains("declare i32 @string_len(ptr)") + && stdout.contains("call void @print_string(ptr %") + && stdout.contains("call void @print_bool(i1 %") + && stdout.contains("call void @print_i32(i32 %") + && stdout.contains("call i32 @string_len(ptr ") + && !stdout.contains("@std."), + "standard-runtime fixture LLVM shape drifted\nstdout:\n{}", + stdout + ); + assert!( + stderr.is_empty(), + "standard-runtime fixture compile wrote stderr:\n{}", + stderr + ); + + let run = run_glagol([OsStr::new("--run-tests"), example.as_os_str()]); + assert_success_stdout( + run, + concat!( + "test \"std string equality\" ... ok\n", + "test \"std string byte length\" ... ok\n", + "2 test(s) passed\n", + ), + "standard-runtime test runner output", + ); + + let expected_format = read(formatter_fixture); + let format = run_glagol([OsStr::new("--format"), formatter_fixture.as_os_str()]); + assert_success_stdout( + format, + &expected_format, + "standard-runtime formatter output", + ); +} + +fn assert_time_sleep_tooling_matches_fixtures(example: &Path, formatter_fixture: &Path) { + let compile = run_glagol([example.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + + assert!( + compile.status.success(), + "time-sleep fixture compile failed\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("declare i32 @__glagol_time_monotonic_ms()") + && stdout.contains("declare void @__glagol_time_sleep_ms(i32)") + && stdout.contains("call i32 @__glagol_time_monotonic_ms()") + && stdout.contains("call void @__glagol_time_sleep_ms(i32 0)") + && !stdout.contains("@std.time."), + "time-sleep fixture LLVM shape drifted\nstdout:\n{}", + stdout + ); + assert!( + stderr.is_empty(), + "time-sleep fixture compile wrote stderr:\n{}", + stderr + ); + + let run = run_glagol([OsStr::new("--run-tests"), example.as_os_str()]); + assert_success_stdout( + run, + concat!( + "test \"monotonic value is self equal\" ... ok\n", + "test \"sleep zero returns\" ... ok\n", + "2 test(s) passed\n", + ), + "time-sleep test runner output", + ); + + let expected_format = read(formatter_fixture); + let format = run_glagol([OsStr::new("--format"), formatter_fixture.as_os_str()]); + assert_success_stdout(format, &expected_format, "time-sleep formatter output"); +} + +fn assert_owned_string_concat_tooling_matches_fixtures(example: &Path, formatter_fixture: &Path) { + let compile = run_glagol([example.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + + assert!( + compile.status.success(), + "owned-string-concat fixture compile failed\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("declare ptr @__glagol_string_concat(ptr, ptr)") + && stdout.contains("define ptr @join(ptr %left, ptr %right)") + && stdout.contains("call ptr @__glagol_string_concat(ptr %left, ptr %right)") + && stdout.contains("call void @print_string(ptr %") + && stdout.contains("call void @print_i32(i32 %") + && stdout.contains("call i32 @string_len(ptr %") + && !stdout.contains("@std."), + "owned-string-concat fixture LLVM shape drifted\nstdout:\n{}", + stdout + ); + assert!( + stderr.is_empty(), + "owned-string-concat fixture compile wrote stderr:\n{}", + stderr + ); + + let run = run_glagol([OsStr::new("--run-tests"), example.as_os_str()]); + assert_success_stdout( + run, + concat!( + "test \"owned string concat equality\" ... ok\n", + "test \"owned string concat length\" ... ok\n", + "2 test(s) passed\n", + ), + "owned-string-concat test runner output", + ); + + let expected_format = read(formatter_fixture); + let format = run_glagol([OsStr::new("--format"), formatter_fixture.as_os_str()]); + assert_success_stdout( + format, + &expected_format, + "owned-string-concat formatter output", + ); +} + +fn assert_string_value_flow_tooling_matches_fixtures(example: &Path, formatter_fixture: &Path) { + let compile = run_glagol([example.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + + assert!( + compile.status.success(), + "string-value-flow fixture compile failed\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define ptr @label()") + && stdout.contains("define ptr @echo(ptr %value)") + && stdout.contains("define ptr @local_label()") + && stdout.contains("define i32 @label_len()") + && stdout.contains("%value.addr = alloca ptr") + && stdout.contains("call i32 @string_len(ptr %") + && stdout.contains("call void @print_string(ptr %"), + "string-value-flow fixture LLVM shape drifted\nstdout:\n{}", + stdout + ); + assert!( + stderr.is_empty(), + "string-value-flow fixture compile wrote stderr:\n{}", + stderr + ); + + let run = run_glagol([OsStr::new("--run-tests"), example.as_os_str()]); + assert_success_stdout( + run, + concat!( + "test \"string literal equality\" ... ok\n", + "test \"string parameter equality\" ... ok\n", + "test \"string call return equality\" ... ok\n", + "test \"string byte length\" ... ok\n", + "4 test(s) passed\n", + ), + "string value-flow test runner output", + ); + + let expected_format = read(formatter_fixture); + let format = run_glagol([OsStr::new("--format"), formatter_fixture.as_os_str()]); + assert_success_stdout( + format, + &expected_format, + "string-value-flow formatter output", + ); +} + +fn assert_if_tooling_matches_fixtures(example: &Path, formatter_fixture: &Path) { + let compile = run_glagol([example.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + assert!( + compile.status.success(), + "compiler rejected if fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define i32 @choose(i32 %value)") + && stdout.contains("if.then") + && stdout.contains("if.else") + && stdout.contains("if.end") + && stdout.contains("br i1") + && stdout.contains(" phi i32 "), + "compiler output for if fixture lost branch shape\nstdout:\n{}", + stdout + ); + assert!( + !stdout.contains("if chooses") && !stdout.contains("if returns bool"), + "compiler output should not emit top-level test names as LLVM metadata\nstdout:\n{}", + stdout + ); + assert!( + stderr.is_empty(), + "compiler wrote stderr for if fixture:\n{}", + stderr + ); + + let run = run_glagol([OsStr::new("--run-tests"), example.as_os_str()]); + assert_success_stdout( + run, + concat!( + "test \"if chooses then\" ... ok\n", + "test \"if chooses else\" ... ok\n", + "test \"if returns bool\" ... ok\n", + "3 test(s) passed\n", + ), + "if test runner output", + ); + + let expected_format = read(formatter_fixture); + let format = run_glagol([OsStr::new("--format"), formatter_fixture.as_os_str()]); + assert_success_stdout(format, &expected_format, "if formatter output"); +} + +fn assert_struct_tooling_matches_fixtures(example: &Path, formatter_fixture: &Path) { + let compile = run_glagol([example.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + assert!( + compile.status.success(), + "compiler rejected struct fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define i32 @point_sum()") + && stdout.contains("insertvalue { i32, i32 }") + && stdout.contains("extractvalue { i32, i32 }") + && stdout.contains("define i32 @main()") + && !stdout.contains("type {"), + "compiler output for struct fixture lost field-read shape\nstdout:\n{}", + stdout + ); + assert!( + !stdout.contains("struct field access") && !stdout.contains("struct field compares"), + "compiler output should not emit top-level test names as LLVM metadata\nstdout:\n{}", + stdout + ); + assert!( + stderr.is_empty(), + "compiler wrote stderr for struct fixture:\n{}", + stderr + ); + + let run = run_glagol([OsStr::new("--run-tests"), example.as_os_str()]); + assert_success_stdout( + run, + concat!( + "test \"struct field access\" ... ok\n", + "test \"struct field compares\" ... ok\n", + "2 test(s) passed\n", + ), + "struct test runner output", + ); + + let expected_format = read(formatter_fixture); + let format = run_glagol([OsStr::new("--format"), formatter_fixture.as_os_str()]); + assert_success_stdout(format, &expected_format, "struct formatter output"); +} + +fn assert_struct_value_flow_tooling_matches_fixtures(example: &Path, formatter_fixture: &Path) { + let compile = run_glagol([example.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + assert!( + compile.status.success(), + "compiler rejected struct value-flow fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define { i32, i32 } @make_point(i32 %x, i32 %y)") + && stdout.contains("define i32 @point_x({ i32, i32 } %p)") + && stdout.contains("define i32 @point_sum({ i32, i32 } %p)") + && stdout.contains("alloca { i32, i32 }") + && stdout.contains("call i32 @point_sum({ i32, i32 }") + && !stdout.contains("type {"), + "compiler output for struct value-flow fixture lost aggregate shape\nstdout:\n{}", + stdout + ); + assert!( + !stdout.contains("struct local value flow") + && !stdout.contains("struct parameter value flow") + && !stdout.contains("stored struct field access"), + "compiler output should not emit top-level test names as LLVM metadata\nstdout:\n{}", + stdout + ); + assert!( + stderr.is_empty(), + "compiler wrote stderr for struct value-flow fixture:\n{}", + stderr + ); + + let run = run_glagol([OsStr::new("--run-tests"), example.as_os_str()]); + assert_success_stdout( + run, + concat!( + "test \"struct local value flow\" ... ok\n", + "test \"struct parameter value flow\" ... ok\n", + "test \"stored struct field access\" ... ok\n", + "3 test(s) passed\n", + ), + "struct value-flow test runner output", + ); + + let expected_format = read(formatter_fixture); + let format = run_glagol([OsStr::new("--format"), formatter_fixture.as_os_str()]); + assert_success_stdout( + format, + &expected_format, + "struct value-flow formatter output", + ); +} + +fn assert_unsafe_tooling_matches_fixtures(repo: &Path, example: &Path, formatter_fixture: &Path) { + let compile = run_glagol([example.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + assert!( + compile.status.success(), + "compiler rejected unsafe fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define i32 @add_one_in_unsafe(i32 %value)") + && stdout.contains("%0 = add i32 %value, 1") + && stdout.contains("ret i32 %0") + && !stdout.contains("%one.addr = alloca i32") + && stdout.contains("define i32 @main()"), + "compiler output for unsafe fixture lost safe block shape\nstdout:\n{}", + stdout + ); + assert!( + !stdout.contains("unsafe block returns final value") + && !stdout.contains("unsafe block can return bool"), + "compiler output should not emit top-level test names as LLVM metadata\nstdout:\n{}", + stdout + ); + assert!( + stderr.is_empty(), + "compiler wrote stderr for unsafe fixture:\n{}", + stderr + ); + + assert_lowering_inspector_matches_fixture( + repo, + formatter_fixture, + "--inspect-lowering=surface", + "unsafe.surface.lower", + ); + assert_lowering_inspector_matches_fixture( + repo, + formatter_fixture, + "--inspect-lowering=checked", + "unsafe.checked.lower", + ); + + let run = run_glagol([OsStr::new("--run-tests"), example.as_os_str()]); + assert_success_stdout( + run, + concat!( + "test \"unsafe block returns final value\" ... ok\n", + "test \"unsafe block can return bool\" ... ok\n", + "2 test(s) passed\n", + ), + "unsafe test runner output", + ); + + let expected_format = read(formatter_fixture); + let format = run_glagol([OsStr::new("--format"), formatter_fixture.as_os_str()]); + assert_success_stdout(format, &expected_format, "unsafe formatter output"); +} + +fn assert_vec_i32_tooling_matches_fixtures(example: &Path, formatter_fixture: &Path) { + let compile = run_glagol([example.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + assert!( + compile.status.success(), + "compiler rejected vec-i32 fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("declare ptr @__glagol_vec_i32_empty()") + && stdout.contains("declare ptr @__glagol_vec_i32_append(ptr, i32)") + && stdout.contains("declare i32 @__glagol_vec_i32_len(ptr)") + && stdout.contains("declare i32 @__glagol_vec_i32_index(ptr, i32)") + && stdout.contains("declare i1 @__glagol_vec_i32_eq(ptr, ptr)") + && stdout.contains("define ptr @empty_values()") + && stdout.contains("define ptr @pair(i32 %base)") + && stdout.contains("define ptr @echo(ptr %values)") + && stdout.contains("define i32 @at(ptr %values, i32 %i)") + && stdout.contains("call ptr @__glagol_vec_i32_append(ptr %") + && stdout.contains("call i32 @__glagol_vec_i32_index(ptr %"), + "compiler output for vec-i32 fixture lost runtime vector shape\nstdout:\n{}", + stdout + ); + assert!( + !stdout.contains("vec i32 empty length") && !stdout.contains("vec i32 equality"), + "compiler output should not emit top-level test names as LLVM metadata\nstdout:\n{}", + stdout + ); + assert!( + stderr.is_empty(), + "compiler wrote stderr for vec-i32 fixture:\n{}", + stderr + ); + + let run = run_glagol([OsStr::new("--run-tests"), example.as_os_str()]); + assert_success_stdout( + run, + concat!( + "test \"vec i32 empty length\" ... ok\n", + "test \"vec i32 append length\" ... ok\n", + "test \"vec i32 index\" ... ok\n", + "test \"vec i32 append is immutable\" ... ok\n", + "test \"vec i32 equality\" ... ok\n", + "5 test(s) passed\n", + ), + "vec-i32 test runner output", + ); + + let expected_format = read(formatter_fixture); + let format = run_glagol([OsStr::new("--format"), formatter_fixture.as_os_str()]); + assert_success_stdout(format, &expected_format, "vec-i32 formatter output"); +} + +fn assert_while_tooling_matches_fixtures(example: &Path, formatter_fixture: &Path) { + let compile = run_glagol([example.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + assert!( + compile.status.success(), + "compiler rejected while fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define i32 @count_to(i32 %limit)") + && stdout.contains("while.cond") + && stdout.contains("while.body") + && stdout.contains("while.end") + && stdout.contains("br i1"), + "compiler output for while fixture lost loop shape\nstdout:\n{}", + stdout + ); + assert!( + !stdout.contains("while counts") && !stdout.contains("while false skips"), + "compiler output should not emit top-level test names as LLVM metadata\nstdout:\n{}", + stdout + ); + assert!( + stderr.is_empty(), + "compiler wrote stderr for while fixture:\n{}", + stderr + ); + + let run = run_glagol([OsStr::new("--run-tests"), example.as_os_str()]); + assert_success_stdout( + run, + concat!( + "test \"while counts\" ... ok\n", + "test \"while false skips\" ... ok\n", + "2 test(s) passed\n", + ), + "while test runner output", + ); + + let expected_format = read(formatter_fixture); + let format = run_glagol([OsStr::new("--format"), formatter_fixture.as_os_str()]); + assert_success_stdout(format, &expected_format, "while formatter output"); +} + +fn assert_project_enum_imports_tooling_matches_fixture(project: &Path) { + assert!(project.join("slovo.toml").is_file()); + assert!(project.join("src/readings.slo").is_file()); + assert!(project.join("src/main.slo").is_file()); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "project enum imports check"); + + let test_cwd = temp_root("std-layout-local-fs"); + let test = run_glagol_in([OsStr::new("test"), project.as_os_str()], &test_cwd); + assert_success_stdout( + test, + concat!( + "test \"local exported enum constructor\" ... ok\n", + "test \"imported enum payload constructor equality\" ... ok\n", + "test \"imported enum payload equality compares payload\" ... ok\n", + "test \"imported enum payloadless equality\" ... ok\n", + "test \"imported enum local return call flow\" ... ok\n", + "test \"imported enum parameter equality\" ... ok\n", + "test \"imported enum match missing\" ... ok\n", + "test \"imported enum match value\" ... ok\n", + "test \"imported enum match offset\" ... ok\n", + "9 test(s) passed\n", + ), + "project enum imports test", + ); +} + +fn assert_project_std_layout_local_math_tooling_matches_fixture(project: &Path) { + assert!(project.join("slovo.toml").is_file()); + assert!(project.join("src/math.slo").is_file()); + assert!(project.join("src/main.slo").is_file()); + assert_standard_math_source_helpers_alpha(project); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std layout local math project check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + test, + concat!( + "test \"local math i32 helpers\" ... ok\n", + "test \"local math i64 helpers\" ... ok\n", + "test \"local math f64 helpers\" ... ok\n", + "test \"explicit local math import i32 helpers\" ... ok\n", + "test \"explicit local math import i64 helpers\" ... ok\n", + "test \"explicit local math import f64 helpers\" ... ok\n", + "test \"explicit local math import all helpers\" ... ok\n", + "7 test(s) passed\n", + ), + "std layout local math project test", + ); +} + +fn assert_project_std_import_math_tooling_matches_fixture(project: &Path) { + assert!(project.join("slovo.toml").is_file()); + assert!(project.join("src/main.slo").is_file()); + assert!( + !project.join("src/math.slo").exists(), + "std import math fixture must use repo-root std/math.slo, not a local copy" + ); + + let main = read(&project.join("src/main.slo")); + let slovo_math = read(&repo_root().join("lib/std/math.slo")); + assert!( + main.starts_with("(module main)\n\n(import std.math ("), + "std import math fixture must use explicit `std.math` import syntax" + ); + assert!( + slovo_math.starts_with("(module math (export "), + "repo-root Slovo std/math.slo must directly export imported helpers" + ); + for helper in STANDARD_MATH_SOURCE_HELPERS_ALPHA { + assert!( + slovo_math.contains(&format!("(fn {} ", helper)), + "Slovo std/math.slo is missing helper `{}`", + helper + ); + assert!( + main.matches(helper).count() >= 2, + "std import math main fixture import/use is missing helper `{}`", + helper + ); + } + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std import math project check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + test, + concat!( + "test \"explicit std math import i32 helpers\" ... ok\n", + "test \"explicit std math import i64 helpers\" ... ok\n", + "test \"explicit std math import f64 helpers\" ... ok\n", + "test \"explicit std math import all helpers\" ... ok\n", + "4 test(s) passed\n", + ), + "std import math project test", + ); +} + +fn assert_project_std_import_result_tooling_matches_fixture(project: &Path) { + assert!(project.join("slovo.toml").is_file()); + assert!(project.join("src/main.slo").is_file()); + assert!( + !project.join("src/result.slo").exists(), + "std import result fixture must use repo-root std/result.slo, not a local copy" + ); + assert!( + !project.join("src/bridge.slo").exists(), + "std import result fixture must not depend on a local bridge shim" + ); + + let main = read(&project.join("src/main.slo")); + let slovo_result = read(&repo_root().join("lib/std/result.slo")); + assert!( + main.starts_with("(module main)\n\n(import std.result ("), + "std import result fixture must use explicit `std.result` import syntax" + ); + assert!( + slovo_result.starts_with("(module result (export "), + "repo-root Slovo std/result.slo must directly export imported helpers" + ); + for helper in STANDARD_RESULT_SOURCE_SEARCH_ALPHA { + assert!( + slovo_result.contains(&format!("(fn {} ", helper)), + "Slovo std/result.slo is missing helper `{}`", + helper + ); + } + for helper in [ + "is_ok_i32", + "is_err_i32", + "unwrap_ok_i32", + "unwrap_err_i32", + "unwrap_or_i32", + "is_ok_u32", + "is_err_u32", + "unwrap_ok_u32", + "unwrap_err_u32", + "unwrap_or_u32", + "is_err_i64", + "unwrap_err_i64", + "unwrap_or_i64", + "is_ok_u64", + "is_err_u64", + "unwrap_ok_u64", + "unwrap_err_u64", + "unwrap_or_u64", + "is_err_string", + "unwrap_err_string", + "is_err_f64", + "unwrap_err_f64", + "unwrap_or_string", + "unwrap_or_f64", + "is_ok_bool", + "is_err_bool", + "unwrap_ok_bool", + "unwrap_err_bool", + "unwrap_or_bool", + ] { + assert!( + main.matches(helper).count() >= 1, + "std import result main fixture import/use is missing helper `{}`", + helper + ); + } + for helper in STANDARD_RESULT_OPTION_BRIDGE_HELPERS_ALPHA { + assert!( + slovo_result.contains(&format!("(fn {} ", helper)), + "Slovo std/result.slo is missing exp-109 bridge helper `{}`", + helper + ); + assert!( + main.contains(helper), + "std import result main fixture import/use is missing exp-109 bridge helper `{}`", + helper + ); + } + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std import result project check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + test, + concat!( + "test \"explicit std result i32 wrappers\" ... ok\n", + "test \"explicit std result err wrappers\" ... ok\n", + "test \"explicit std result unwrap_or i32\" ... ok\n", + "test \"explicit std result ok_or_none i32 ok\" ... ok\n", + "test \"explicit std result ok_or_none i32 none\" ... ok\n", + "test \"explicit std result u32 wrappers\" ... ok\n", + "test \"explicit std result unwrap_or u32\" ... ok\n", + "test \"explicit std result ok_or_none u32 ok\" ... ok\n", + "test \"explicit std result ok_or_none u32 none\" ... ok\n", + "test \"explicit std result unwrap_or i64\" ... ok\n", + "test \"explicit std result ok_or_none i64 ok\" ... ok\n", + "test \"explicit std result ok_or_none i64 none\" ... ok\n", + "test \"explicit std result u64 wrappers\" ... ok\n", + "test \"explicit std result unwrap_or u64\" ... ok\n", + "test \"explicit std result ok_or_none u64 ok\" ... ok\n", + "test \"explicit std result ok_or_none u64 none\" ... ok\n", + "test \"explicit std result unwrap_or string\" ... ok\n", + "test \"explicit std result ok_or_none string ok\" ... ok\n", + "test \"explicit std result ok_or_none string none\" ... ok\n", + "test \"explicit std result unwrap_or f64\" ... ok\n", + "test \"explicit std result ok_or_none f64 ok\" ... ok\n", + "test \"explicit std result ok_or_none f64 none\" ... ok\n", + "test \"explicit std result bool helpers\" ... ok\n", + "test \"explicit std result ok_or_none bool ok\" ... ok\n", + "test \"explicit std result ok_or_none bool none\" ... ok\n", + "test \"explicit std result helpers all\" ... ok\n", + "26 test(s) passed\n", + ), + "std import result project test", + ); +} + +fn assert_project_std_import_option_tooling_matches_fixture(project: &Path) { + assert!(project.join("slovo.toml").is_file()); + assert!(project.join("src/main.slo").is_file()); + assert!( + !project.join("src/option.slo").exists(), + "std import option fixture must use repo-root std/option.slo, not a local copy" + ); + assert!( + !project.join("src/bridge.slo").exists(), + "std import option fixture must not depend on a local bridge shim" + ); + + let main = read(&project.join("src/main.slo")); + let slovo_option = read(&repo_root().join("lib/std/option.slo")); + assert!( + main.starts_with("(module main)\n\n(import std.option ("), + "std import option fixture must use explicit `std.option` import syntax" + ); + assert!( + slovo_option.starts_with("(module option (export "), + "repo-root Slovo std/option.slo must directly export imported helpers" + ); + for helper in STANDARD_OPTION_SOURCE_HELPERS_ALPHA { + assert!( + slovo_option.contains(&format!("(fn {} ", helper)), + "Slovo std/option.slo is missing helper `{}`", + helper + ); + assert!( + main.matches(helper).count() >= 2, + "std import option main fixture import/use is missing helper `{}`", + helper + ); + } + for helper in STANDARD_OPTION_RESULT_BRIDGE_HELPERS_ALPHA { + assert!( + slovo_option.contains(&format!("(fn {} ", helper)), + "Slovo std/option.slo is missing exp-109 bridge helper `{}`", + helper + ); + assert!( + main.contains(helper), + "std import option main fixture import/use is missing exp-109 bridge helper `{}`", + helper + ); + } + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std import option project check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + test, + concat!( + "test \"explicit std option i32 observation\" ... ok\n", + "test \"explicit std option i32 unwrap_some\" ... ok\n", + "test \"explicit std option i32 unwrap_or\" ... ok\n", + "test \"explicit std option i32 some_or_err ok\" ... ok\n", + "test \"explicit std option i32 some_or_err err\" ... ok\n", + "test \"explicit std option u32 observation\" ... ok\n", + "test \"explicit std option u32 unwrap_some\" ... ok\n", + "test \"explicit std option u32 unwrap_or\" ... ok\n", + "test \"explicit std option u32 some_or_err ok\" ... ok\n", + "test \"explicit std option u32 some_or_err err\" ... ok\n", + "test \"explicit std option i64 observation\" ... ok\n", + "test \"explicit std option i64 unwrap_some\" ... ok\n", + "test \"explicit std option i64 unwrap_or\" ... ok\n", + "test \"explicit std option i64 some_or_err ok\" ... ok\n", + "test \"explicit std option i64 some_or_err err\" ... ok\n", + "test \"explicit std option u64 observation\" ... ok\n", + "test \"explicit std option u64 unwrap_some\" ... ok\n", + "test \"explicit std option u64 unwrap_or\" ... ok\n", + "test \"explicit std option u64 some_or_err ok\" ... ok\n", + "test \"explicit std option u64 some_or_err err\" ... ok\n", + "test \"explicit std option f64 observation\" ... ok\n", + "test \"explicit std option f64 unwrap_some\" ... ok\n", + "test \"explicit std option f64 unwrap_or\" ... ok\n", + "test \"explicit std option f64 some_or_err ok\" ... ok\n", + "test \"explicit std option f64 some_or_err err\" ... ok\n", + "test \"explicit std option bool observation\" ... ok\n", + "test \"explicit std option bool unwrap_some\" ... ok\n", + "test \"explicit std option bool unwrap_or\" ... ok\n", + "test \"explicit std option bool some_or_err ok\" ... ok\n", + "test \"explicit std option bool some_or_err err\" ... ok\n", + "test \"explicit std option string observation\" ... ok\n", + "test \"explicit std option string unwrap_some\" ... ok\n", + "test \"explicit std option string unwrap_or\" ... ok\n", + "test \"explicit std option string some_or_err ok\" ... ok\n", + "test \"explicit std option string some_or_err err\" ... ok\n", + "test \"explicit std option helpers all\" ... ok\n", + "36 test(s) passed\n", + ), + "std import option project test", + ); +} + +fn assert_project_std_import_time_tooling_matches_fixture(project: &Path) { + assert_project_std_import_host_facade_tooling_matches_fixture( + project, + "time", + STANDARD_TIME_SOURCE_FACADE_ALPHA, + concat!( + "test \"explicit std time monotonic facade\" ... ok\n", + "test \"explicit std time sleep zero facade\" ... ok\n", + "test \"explicit std time facade all\" ... ok\n", + "3 test(s) passed\n", + ), + ); +} + +fn assert_project_std_import_random_tooling_matches_fixture(project: &Path) { + assert_project_std_import_host_facade_tooling_matches_fixture( + project, + "random", + STANDARD_RANDOM_SOURCE_FACADE_ALPHA, + concat!( + "test \"explicit std random i32 non-negative facade\" ... ok\n", + "test \"explicit std random facade all\" ... ok\n", + "2 test(s) passed\n", + ), + ); +} + +fn assert_project_std_import_env_tooling_matches_fixture(project: &Path) { + assert_project_std_import_host_facade_shape(project, "env", STANDARD_ENV_SOURCE_FACADE_ALPHA); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std import env project check"); + + let test = run_glagol_configured([OsStr::new("test"), project.as_os_str()], |command| { + configure_standard_import_env(command); + }); + assert_success_stdout( + test, + concat!( + "test \"explicit std env get missing facade\" ... ok\n", + "test \"explicit std env get result missing facade\" ... ok\n", + "test \"explicit std env has missing facade\" ... ok\n", + "test \"explicit std env get or missing facade\" ... ok\n", + "test \"explicit std env get option missing facade\" ... ok\n", + "test \"explicit std env has present facade\" ... ok\n", + "test \"explicit std env get or present facade\" ... ok\n", + "test \"explicit std env get option present facade\" ... ok\n", + "test \"explicit std env get i32 result present facade\" ... ok\n", + "test \"explicit std env get i32 or zero invalid facade\" ... ok\n", + "test \"explicit std env get u32 result present facade\" ... ok\n", + "test \"explicit std env get u32 or zero invalid facade\" ... ok\n", + "test \"explicit std env get i64 result present facade\" ... ok\n", + "test \"explicit std env get i64 or zero missing facade\" ... ok\n", + "test \"explicit std env get u64 result present facade\" ... ok\n", + "test \"explicit std env get u64 or zero missing facade\" ... ok\n", + "test \"explicit std env get f64 result present facade\" ... ok\n", + "test \"explicit std env get f64 or zero invalid facade\" ... ok\n", + "test \"explicit std env get bool result present facade\" ... ok\n", + "test \"explicit std env get bool or false invalid facade\" ... ok\n", + "test \"explicit std env typed option facade\" ... ok\n", + "test \"explicit std env typed custom fallback facade\" ... ok\n", + "test \"explicit std env facade all\" ... ok\n", + "23 test(s) passed\n", + ), + "std import env project test", + ); +} + +fn assert_project_std_import_fs_tooling_matches_fixture(project: &Path) { + assert_project_std_import_host_facade_shape(project, "fs", STANDARD_FS_SOURCE_FACADE_ALPHA); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std import fs project check"); + + let test_cwd = temp_root("std-import-fs"); + let test = run_glagol_in([OsStr::new("test"), project.as_os_str()], &test_cwd); + assert_success_stdout( + test, + concat!( + "test \"explicit std fs write text status facade\" ... ok\n", + "test \"explicit std fs read text facade\" ... ok\n", + "test \"explicit std fs write text result facade\" ... ok\n", + "test \"explicit std fs read text result facade\" ... ok\n", + "test \"explicit std fs read text option missing facade\" ... ok\n", + "test \"explicit std fs read text option present facade\" ... ok\n", + "test \"explicit std fs read text or missing facade\" ... ok\n", + "test \"explicit std fs read text or present facade\" ... ok\n", + "test \"explicit std fs write text ok facade\" ... ok\n", + "test \"explicit std fs read i32 result present facade\" ... ok\n", + "test \"explicit std fs read i32 or zero invalid facade\" ... ok\n", + "test \"explicit std fs read u32 result present facade\" ... ok\n", + "test \"explicit std fs read u32 or zero invalid facade\" ... ok\n", + "test \"explicit std fs read i64 result present facade\" ... ok\n", + "test \"explicit std fs read i64 or zero missing facade\" ... ok\n", + "test \"explicit std fs read u64 result present facade\" ... ok\n", + "test \"explicit std fs read u64 or zero missing facade\" ... ok\n", + "test \"explicit std fs read f64 result present facade\" ... ok\n", + "test \"explicit std fs read f64 or zero invalid facade\" ... ok\n", + "test \"explicit std fs read bool result present facade\" ... ok\n", + "test \"explicit std fs read bool or false invalid facade\" ... ok\n", + "test \"explicit std fs typed option facade\" ... ok\n", + "test \"explicit std fs typed custom fallback facade\" ... ok\n", + "test \"explicit std fs facade all\" ... ok\n", + "24 test(s) passed\n", + ), + "std import fs project test", + ); +} + +fn assert_project_std_import_process_tooling_matches_fixture(project: &Path) { + assert_project_std_import_host_facade_shape( + project, + "process", + STANDARD_PROCESS_SOURCE_FACADE_ALPHA, + ); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std import process project check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + test, + concat!( + "test \"explicit std process argc facade\" ... ok\n", + "test \"explicit std process arg result oob facade\" ... ok\n", + "test \"explicit std process arg option facade\" ... ok\n", + "test \"explicit std process has arg facade\" ... ok\n", + "test \"explicit std process arg fallback missing facade\" ... ok\n", + "test \"explicit std process arg fallback present facade\" ... ok\n", + "test \"explicit std process arg i32 missing facade\" ... ok\n", + "test \"explicit std process arg i32 invalid fallback facade\" ... ok\n", + "test \"explicit std process arg u32 missing facade\" ... ok\n", + "test \"explicit std process arg u32 invalid fallback facade\" ... ok\n", + "test \"explicit std process arg i64 missing facade\" ... ok\n", + "test \"explicit std process arg i64 invalid fallback facade\" ... ok\n", + "test \"explicit std process arg u64 missing facade\" ... ok\n", + "test \"explicit std process arg u64 invalid fallback facade\" ... ok\n", + "test \"explicit std process arg f64 missing facade\" ... ok\n", + "test \"explicit std process arg f64 invalid fallback facade\" ... ok\n", + "test \"explicit std process arg bool missing facade\" ... ok\n", + "test \"explicit std process arg bool invalid fallback facade\" ... ok\n", + "test \"explicit std process typed option facade\" ... ok\n", + "test \"explicit std process typed custom fallback facade\" ... ok\n", + "test \"explicit std process facade all\" ... ok\n", + "21 test(s) passed\n", + ), + "std import process project test", + ); +} + +fn assert_project_std_import_string_tooling_matches_fixture(project: &Path) { + assert_project_std_import_core_facade_tooling_matches_fixture( + project, + "string", + STANDARD_STRING_SOURCE_FACADE_ALPHA, + concat!( + "test \"explicit std string len concat\" ... ok\n", + "test \"explicit std string parse result wrappers\" ... ok\n", + "test \"explicit std string parse option wrappers\" ... ok\n", + "test \"explicit std string parse integer fallbacks\" ... ok\n", + "test \"explicit std string parse float bool fallbacks\" ... ok\n", + "test \"explicit std string parse custom fallbacks\" ... ok\n", + "test \"explicit std string helpers all\" ... ok\n", + "7 test(s) passed\n", + ), + ); +} + +fn assert_project_std_import_num_tooling_matches_fixture(project: &Path) { + assert_project_std_import_core_facade_tooling_matches_fixture( + project, + "num", + STANDARD_NUM_SOURCE_FACADE_ALPHA, + concat!( + "test \"explicit std num widening\" ... ok\n", + "test \"explicit std num checked conversions\" ... ok\n", + "test \"explicit std num to string\" ... ok\n", + "test \"explicit std num checked fallbacks\" ... ok\n", + "test \"explicit std num helpers all\" ... ok\n", + "5 test(s) passed\n", + ), + ); +} + +fn assert_project_std_import_io_tooling_matches_fixture(project: &Path) { + assert_project_std_import_host_facade_shape(project, "io", STANDARD_IO_SOURCE_FACADE_ALPHA); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std import io project check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + test, + concat!( + "test \"explicit std io i64 zero facade\" ... ok\n", + "test \"explicit std io u32 zero facade\" ... ok\n", + "test \"explicit std io u64 zero facade\" ... ok\n", + "test \"explicit std io f64 zero facade\" ... ok\n", + "test \"explicit std io value facade\" ... ok\n", + "test \"explicit std io stdin result facade\" ... ok\n", + "test \"explicit std io stdin option facade\" ... ok\n", + "test \"explicit std io stdin text fallback facade\" ... ok\n", + "test \"explicit std io stdin typed result facade\" ... ok\n", + "test \"explicit std io stdin typed option facade\" ... ok\n", + "test \"explicit std io stdin typed fallback facade\" ... ok\n", + "test \"explicit std io helpers all\" ... ok\n", + "12 test(s) passed\n", + ), + "std import io project test", + ); +} + +fn assert_project_std_import_cli_tooling_matches_fixture(project: &Path) { + assert!(project.join("slovo.toml").is_file()); + assert!(project.join("src/main.slo").is_file()); + assert!( + !project.join("src/cli.slo").exists(), + "std import cli fixture must use repo-root std/cli.slo, not a local copy" + ); + + let main = read(&project.join("src/main.slo")); + let slovo_cli = read(&repo_root().join("lib/std/cli.slo")); + assert!( + main.starts_with("(module main)\n\n(import std.cli ("), + "std import cli fixture must use explicit `std.cli` import syntax" + ); + assert!( + slovo_cli.contains("(import std.process (arg_result))") + && slovo_cli + .contains("(import std.result (ok_or_none_string ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool))") + && slovo_cli + .contains("(import std.string (parse_i32_result parse_u32_result parse_i64_result parse_u64_result parse_f64_result parse_bool_result))"), + "std/cli.slo must preserve transitive standard-source imports" + ); + for helper in STANDARD_CLI_SOURCE_FACADE_ALPHA { + assert!( + slovo_cli.contains(&format!("(fn {} ", helper)), + "Slovo std/cli.slo is missing helper `{}`", + helper + ); + assert!( + main.contains(helper), + "std import cli main fixture import/use is missing helper `{}`", + helper + ); + } + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std import cli project check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + test, + concat!( + "test \"explicit std cli arg text missing\" ... ok\n", + "test \"explicit std cli arg text option\" ... ok\n", + "test \"explicit std cli arg i32 missing\" ... ok\n", + "test \"explicit std cli arg i32 fallback missing\" ... ok\n", + "test \"explicit std cli arg u32 missing\" ... ok\n", + "test \"explicit std cli arg u32 fallback missing\" ... ok\n", + "test \"explicit std cli arg i64 missing\" ... ok\n", + "test \"explicit std cli arg i64 fallback missing\" ... ok\n", + "test \"explicit std cli arg u64 missing\" ... ok\n", + "test \"explicit std cli arg u64 fallback missing\" ... ok\n", + "test \"explicit std cli arg f64 missing\" ... ok\n", + "test \"explicit std cli arg f64 fallback missing\" ... ok\n", + "test \"explicit std cli arg bool missing\" ... ok\n", + "test \"explicit std cli arg bool fallback missing\" ... ok\n", + "test \"explicit std cli typed option\" ... ok\n", + "test \"explicit std cli typed custom fallback\" ... ok\n", + "test \"explicit std cli facade all\" ... ok\n", + "17 test(s) passed\n", + ), + "std import cli project test", + ); +} + +fn assert_project_std_import_vec_i32_tooling_matches_fixture(project: &Path) { + assert!(project.join("slovo.toml").is_file()); + assert!(project.join("src/main.slo").is_file()); + assert!( + !project.join("src/vec_i32.slo").exists(), + "std import vec_i32 fixture must use repo-root std/vec_i32.slo, not a local copy" + ); + + let main = read(&project.join("src/main.slo")); + let slovo_vec_i32 = read(&repo_root().join("lib/std/vec_i32.slo")); + assert!( + main.starts_with("(module main)\n\n(import std.vec_i32 ("), + "std import vec_i32 fixture must use explicit `std.vec_i32` import syntax" + ); + assert!( + slovo_vec_i32.starts_with("(module vec_i32 (export "), + "repo-root Slovo std/vec_i32.slo must directly export imported helpers" + ); + assert_std_only_contains( + &slovo_vec_i32, + &[ + "std.vec.i32.empty", + "std.vec.i32.append", + "std.vec.i32.len", + "std.vec.i32.index", + "std.option", + ], + "std/vec_i32.slo must use only the existing promoted std.vec.i32 runtime names", + ); + assert!( + !slovo_vec_i32.contains("capacity") + && !slovo_vec_i32.contains("reserve") + && !slovo_vec_i32.contains("shrink") + && !slovo_vec_i32.contains("sort") + && !slovo_vec_i32.contains("map") + && !slovo_vec_i32.contains("filter") + && !slovo_vec_i32.contains("(option i64)") + && !slovo_vec_i32.contains("(option f64)") + && !slovo_vec_i32.contains("(option string)") + && !slovo_vec_i32.contains("(option bool)"), + "std vec_i32 source facade must stay narrow and explicit" + ); + for helper in STANDARD_VEC_I32_SOURCE_FACADE_ALPHA { + assert!( + slovo_vec_i32.contains(&format!("(fn {} ", helper)), + "Slovo std/vec_i32.slo is missing helper `{}`", + helper + ); + assert!( + main.contains(helper), + "std import vec_i32 main fixture import/use is missing helper `{}`", + helper + ); + } + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std import vec_i32 project check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + test, + concat!( + "test \"explicit std vec_i32 empty len facade\" ... ok\n", + "test \"explicit std vec_i32 direct at facade\" ... ok\n", + "test \"explicit std vec_i32 builder helpers\" ... ok\n", + "test \"explicit std vec_i32 constructor helpers\" ... ok\n", + "test \"explicit std vec_i32 range helper\" ... ok\n", + "test \"explicit std vec_i32 query helpers\" ... ok\n", + "test \"explicit std vec_i32 option query helpers\" ... ok\n", + "test \"explicit std vec_i32 starts_with helper\" ... ok\n", + "test \"explicit std vec_i32 ends_with helper\" ... ok\n", + "test \"explicit std vec_i32 without_suffix helper\" ... ok\n", + "test \"explicit std vec_i32 without_prefix helper\" ... ok\n", + "test \"explicit std vec_i32 transform helpers\" ... ok\n", + "test \"explicit std vec_i32 subvec helper\" ... ok\n", + "test \"explicit std vec_i32 insert helper\" ... ok\n", + "test \"explicit std vec_i32 insert range helper\" ... ok\n", + "test \"explicit std vec_i32 replace helper\" ... ok\n", + "test \"explicit std vec_i32 replace range helper\" ... ok\n", + "test \"explicit std vec_i32 remove helper\" ... ok\n", + "test \"explicit std vec_i32 remove range helper\" ... ok\n", + "test \"explicit std vec_i32 count_of helper\" ... ok\n", + "test \"explicit std vec_i32 real program helpers\" ... ok\n", + "test \"explicit std vec_i32 helpers all\" ... ok\n", + "22 test(s) passed\n", + ), + "std import vec_i32 project test", + ); +} + +fn assert_project_std_import_vec_i64_tooling_matches_fixture(project: &Path) { + assert!(project.join("slovo.toml").is_file()); + assert!(project.join("src/main.slo").is_file()); + assert!( + !project.join("src/vec_i64.slo").exists(), + "std import vec_i64 fixture must not use a source-root vec_i64 module copy" + ); + assert!( + !project.join("std/vec_i64.slo").exists(), + "std import vec_i64 fixture must use repo-root Slovo std/vec_i64.slo, not a project-local copy" + ); + + let main = read(&project.join("src/main.slo")); + let std_vec_i64 = read(&repo_root().join("lib/std/vec_i64.slo")); + assert!( + main.starts_with("(module main)\n\n(import std.vec_i64 ("), + "std import vec_i64 fixture must use explicit `std.vec_i64` import syntax" + ); + assert!( + std_vec_i64.starts_with("(module vec_i64 (export "), + "repo-root Slovo std/vec_i64.slo must directly export imported helpers" + ); + assert_std_only_contains( + &std_vec_i64, + &[ + "std.vec.i64.empty", + "std.vec.i64.append", + "std.vec.i64.len", + "std.vec.i64.index", + ], + "std/vec_i64.slo must use only the existing promoted std.vec.i64 runtime names", + ); + assert!( + !std_vec_i64.contains("(vec i32)") + && !std_vec_i64.contains("(vec f64)") + && !std_vec_i64.contains("(vec string)") + && !std_vec_i64.contains("(vec bool)") + && !std_vec_i64.contains("(option f64)") + && !std_vec_i64.contains("(option string)") + && !std_vec_i64.contains("(option bool)") + && !std_vec_i64.contains("(result i32") + && !std_vec_i64.contains("(result i64") + && !std_vec_i64.contains("(result f64") + && !std_vec_i64.contains("(result string") + && !std_vec_i64.contains("(result bool") + && !std_vec_i64.contains("capacity") + && !std_vec_i64.contains("reserve") + && !std_vec_i64.contains("shrink") + && !std_vec_i64.contains("sort") + && !std_vec_i64.contains("map") + && !std_vec_i64.contains("filter"), + "std vec_i64 source facade must stay narrow and explicit" + ); + for helper in STANDARD_VEC_I64_SOURCE_FACADE_ALPHA { + assert!( + std_vec_i64.contains(&format!("(fn {} ", helper)), + "Slovo std/vec_i64.slo is missing helper `{}`", + helper + ); + assert!( + main.contains(helper), + "std import vec_i64 main fixture import/use is missing helper `{}`", + helper + ); + } + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std import vec_i64 project check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + test, + concat!( + "test \"explicit std vec_i64 empty len facade\" ... ok\n", + "test \"explicit std vec_i64 direct at facade\" ... ok\n", + "test \"explicit std vec_i64 builder helpers\" ... ok\n", + "test \"explicit std vec_i64 query helpers\" ... ok\n", + "test \"explicit std vec_i64 option query helpers\" ... ok\n", + "test \"explicit std vec_i64 transform helpers\" ... ok\n", + "test \"explicit std vec_i64 subvec helper\" ... ok\n", + "test \"explicit std vec_i64 insert helper\" ... ok\n", + "test \"explicit std vec_i64 insert range helper\" ... ok\n", + "test \"explicit std vec_i64 replace helper\" ... ok\n", + "test \"explicit std vec_i64 replace range helper\" ... ok\n", + "test \"explicit std vec_i64 remove helper\" ... ok\n", + "test \"explicit std vec_i64 remove range helper\" ... ok\n", + "test \"explicit std vec_i64 real program helpers\" ... ok\n", + "test \"explicit std vec_i64 helpers all\" ... ok\n", + "15 test(s) passed\n", + ), + "std import vec_i64 project test", + ); +} + +fn assert_project_std_import_vec_f64_tooling_matches_fixture(project: &Path) { + assert!(project.join("slovo.toml").is_file()); + assert!(project.join("src/main.slo").is_file()); + assert!( + !project.join("src/vec_f64.slo").exists(), + "std import vec_f64 fixture must not use a source-root vec_f64 module copy" + ); + assert!( + !project.join("std/vec_f64.slo").exists(), + "std import vec_f64 fixture must use repo-root Slovo std/vec_f64.slo, not a project-local copy" + ); + + let main = read(&project.join("src/main.slo")); + let std_vec_f64 = read(&repo_root().join("lib/std/vec_f64.slo")); + assert!( + main.starts_with("(module main)\n\n(import std.vec_f64 ("), + "std import vec_f64 fixture must use explicit `std.vec_f64` import syntax" + ); + assert!( + std_vec_f64.starts_with("(module vec_f64 (export "), + "repo-root Slovo std/vec_f64.slo must directly export imported helpers" + ); + assert_std_only_contains( + &std_vec_f64, + &[ + "std.vec.f64.empty", + "std.vec.f64.append", + "std.vec.f64.len", + "std.vec.f64.index", + ], + "std/vec_f64.slo must use only the existing promoted std.vec.f64 runtime names", + ); + assert!( + !std_vec_f64.contains("(vec i32)") + && !std_vec_f64.contains("(vec i64)") + && !std_vec_f64.contains("(vec string)") + && !std_vec_f64.contains("(vec bool)") + && !std_vec_f64.contains("(option i64)") + && !std_vec_f64.contains("(option string)") + && !std_vec_f64.contains("(option bool)") + && !std_vec_f64.contains("(result i32") + && !std_vec_f64.contains("(result i64") + && !std_vec_f64.contains("(result f64") + && !std_vec_f64.contains("(result string") + && !std_vec_f64.contains("(result bool") + && !std_vec_f64.contains("capacity") + && !std_vec_f64.contains("reserve") + && !std_vec_f64.contains("shrink") + && !std_vec_f64.contains("sort") + && !std_vec_f64.contains("map") + && !std_vec_f64.contains("filter"), + "std vec_f64 source facade must stay concrete to vec f64 plus option i32/f64 helpers" + ); + for helper in STANDARD_VEC_F64_SOURCE_FACADE_ALPHA { + assert!( + std_vec_f64.contains(&format!("(fn {} ", helper)), + "Slovo std/vec_f64.slo is missing helper `{}`", + helper + ); + assert!( + main.contains(helper), + "std import vec_f64 main fixture import/use is missing helper `{}`", + helper + ); + } + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std import vec_f64 project check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + test, + concat!( + "test \"explicit std vec_f64 empty len facade\" ... ok\n", + "test \"explicit std vec_f64 direct at facade\" ... ok\n", + "test \"explicit std vec_f64 builder helpers\" ... ok\n", + "test \"explicit std vec_f64 query helpers\" ... ok\n", + "test \"explicit std vec_f64 option query helpers\" ... ok\n", + "test \"explicit std vec_f64 starts_with helper\" ... ok\n", + "test \"explicit std vec_f64 ends_with helper\" ... ok\n", + "test \"explicit std vec_f64 without_suffix helper\" ... ok\n", + "test \"explicit std vec_f64 without_prefix helper\" ... ok\n", + "test \"explicit std vec_f64 transform helpers\" ... ok\n", + "test \"explicit std vec_f64 subvec helper\" ... ok\n", + "test \"explicit std vec_f64 insert helper\" ... ok\n", + "test \"explicit std vec_f64 insert range helper\" ... ok\n", + "test \"explicit std vec_f64 replace helper\" ... ok\n", + "test \"explicit std vec_f64 replace range helper\" ... ok\n", + "test \"explicit std vec_f64 remove helper\" ... ok\n", + "test \"explicit std vec_f64 remove range helper\" ... ok\n", + "test \"explicit std vec_f64 real program helpers\" ... ok\n", + "test \"explicit std vec_f64 helpers all\" ... ok\n", + "19 test(s) passed\n", + ), + "std import vec_f64 project test", + ); +} + +fn assert_project_std_import_vec_bool_tooling_matches_fixture(project: &Path) { + assert!(project.join("slovo.toml").is_file()); + assert!(project.join("src/main.slo").is_file()); + assert!( + !project.join("src/vec_bool.slo").exists(), + "std import vec_bool fixture must not use a source-root vec_bool module copy" + ); + assert!( + !project.join("std/vec_bool.slo").exists(), + "std import vec_bool fixture must use repo-root Slovo std/vec_bool.slo, not a project-local copy" + ); + + let main = read(&project.join("src/main.slo")); + let std_vec_bool = read(&repo_root().join("lib/std/vec_bool.slo")); + assert!( + main.starts_with("(module main)\n\n(import std.vec_bool ("), + "std import vec_bool fixture must use explicit `std.vec_bool` import syntax" + ); + assert!( + std_vec_bool.starts_with("(module vec_bool (export "), + "repo-root Slovo std/vec_bool.slo must directly export imported helpers" + ); + assert_std_only_contains( + &std_vec_bool, + &[ + "std.vec.bool.empty", + "std.vec.bool.append", + "std.vec.bool.len", + "std.vec.bool.index", + ], + "std/vec_bool.slo must use only the existing promoted std.vec.bool runtime names", + ); + assert!( + !std_vec_bool.contains("(vec i32)") + && !std_vec_bool.contains("(vec i64)") + && !std_vec_bool.contains("(vec string)") + && !std_vec_bool.contains("(option i64)") + && !std_vec_bool.contains("(option f64)") + && !std_vec_bool.contains("(option string)") + && !std_vec_bool.contains("(result i32") + && !std_vec_bool.contains("(result i64") + && !std_vec_bool.contains("(result f64") + && !std_vec_bool.contains("(result string") + && !std_vec_bool.contains("(result bool") + && !std_vec_bool.contains("capacity") + && !std_vec_bool.contains("reserve") + && !std_vec_bool.contains("shrink") + && !std_vec_bool.contains("sort") + && !std_vec_bool.contains("map") + && !std_vec_bool.contains("filter"), + "std vec_bool source facade must stay concrete to vec bool plus option i32/bool helpers" + ); + for helper in STANDARD_VEC_BOOL_SOURCE_FACADE_ALPHA { + assert!( + std_vec_bool.contains(&format!("(fn {} ", helper)), + "Slovo std/vec_bool.slo is missing helper `{}`", + helper + ); + assert!( + main.contains(helper), + "std import vec_bool main fixture import/use is missing helper `{}`", + helper + ); + } + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std import vec_bool project check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + test, + concat!( + "test \"explicit std vec_bool empty len facade\" ... ok\n", + "test \"explicit std vec_bool direct at facade\" ... ok\n", + "test \"explicit std vec_bool builder helpers\" ... ok\n", + "test \"explicit std vec_bool query helpers\" ... ok\n", + "test \"explicit std vec_bool option query helpers\" ... ok\n", + "test \"explicit std vec_bool starts_with helper\" ... ok\n", + "test \"explicit std vec_bool ends_with helper\" ... ok\n", + "test \"explicit std vec_bool without_suffix helper\" ... ok\n", + "test \"explicit std vec_bool without_prefix helper\" ... ok\n", + "test \"explicit std vec_bool transform helpers\" ... ok\n", + "test \"explicit std vec_bool subvec helper\" ... ok\n", + "test \"explicit std vec_bool insert helper\" ... ok\n", + "test \"explicit std vec_bool insert range helper\" ... ok\n", + "test \"explicit std vec_bool replace helper\" ... ok\n", + "test \"explicit std vec_bool replace range helper\" ... ok\n", + "test \"explicit std vec_bool remove helper\" ... ok\n", + "test \"explicit std vec_bool remove range helper\" ... ok\n", + "test \"explicit std vec_bool real program helpers\" ... ok\n", + "test \"explicit std vec_bool helpers all\" ... ok\n", + "19 test(s) passed\n", + ), + "std import vec_bool project test", + ); +} + +fn assert_project_std_import_vec_string_tooling_matches_fixture(project: &Path) { + assert!(project.join("slovo.toml").is_file()); + assert!(project.join("src/main.slo").is_file()); + assert!( + !project.join("src/vec_string.slo").exists(), + "std import vec_string fixture must not use a source-root vec_string module copy" + ); + assert!( + !project.join("std/vec_string.slo").exists(), + "std import vec_string fixture must use repo-root Slovo std/vec_string.slo, not a project-local copy" + ); + + let main = read(&project.join("src/main.slo")); + assert!( + main.starts_with("(module main)\n\n(import std.vec_string ("), + "std import vec_string fixture must use explicit `std.vec_string` import syntax" + ); + for helper in STANDARD_VEC_STRING_SOURCE_FACADE_ALPHA { + assert!( + main.contains(helper), + "std import vec_string main fixture import/use is missing helper `{}`", + helper + ); + } + + let fmt = run_glagol([ + OsStr::new("fmt"), + OsStr::new("--check"), + project.as_os_str(), + ]); + assert_success_stdout(fmt, "", "std import vec_string project fmt"); + + let slovo_vec_string = repo_root().join("lib/std/vec_string.slo"); + if !slovo_vec_string.exists() { + eprintln!( + "skipping std import vec_string check/test: missing `{}`", + slovo_vec_string.display() + ); + return; + } + + let std_vec_string = read(&slovo_vec_string); + assert!( + std_vec_string.starts_with("(module vec_string (export "), + "repo-root Slovo std/vec_string.slo must directly export imported helpers" + ); + assert_std_only_contains( + &std_vec_string, + &[ + "std.vec.string.empty", + "std.vec.string.append", + "std.vec.string.len", + "std.vec.string.index", + ], + "std/vec_string.slo must use only the existing promoted std.vec.string runtime names", + ); + assert!( + !std_vec_string.contains("(vec i32)") + && !std_vec_string.contains("(vec i64)") + && !std_vec_string.contains("(vec f64)") + && !std_vec_string.contains("(vec bool)") + && !std_vec_string.contains("(option i64)") + && !std_vec_string.contains("(option f64)") + && !std_vec_string.contains("(option bool)") + && !std_vec_string.contains("(result i32") + && !std_vec_string.contains("(result i64") + && !std_vec_string.contains("(result f64") + && !std_vec_string.contains("(result string") + && !std_vec_string.contains("(result bool") + && !std_vec_string.contains("capacity") + && !std_vec_string.contains("reserve") + && !std_vec_string.contains("shrink") + && !std_vec_string.contains("sort") + && !std_vec_string.contains("map") + && !std_vec_string.contains("filter") + && !std_vec_string.contains("concat_all"), + "std vec_string source facade must stay narrow and explicit within concrete vec string plus option i32/string helpers" + ); + for helper in STANDARD_VEC_STRING_SOURCE_FACADE_ALPHA { + assert!( + std_vec_string.contains(&format!("(fn {} ", helper)), + "Slovo std/vec_string.slo is missing helper `{}`", + helper + ); + } + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std import vec_string project check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + test, + concat!( + "test \"explicit std vec_string empty len facade\" ... ok\n", + "test \"explicit std vec_string direct at facade\" ... ok\n", + "test \"explicit std vec_string builder helpers\" ... ok\n", + "test \"explicit std vec_string query helpers\" ... ok\n", + "test \"explicit std vec_string option query helpers\" ... ok\n", + "test \"explicit std vec_string starts_with helper\" ... ok\n", + "test \"explicit std vec_string ends_with helper\" ... ok\n", + "test \"explicit std vec_string without_suffix helper\" ... ok\n", + "test \"explicit std vec_string without_prefix helper\" ... ok\n", + "test \"explicit std vec_string transform helpers\" ... ok\n", + "test \"explicit std vec_string subvec helper\" ... ok\n", + "test \"explicit std vec_string insert helper\" ... ok\n", + "test \"explicit std vec_string insert range helper\" ... ok\n", + "test \"explicit std vec_string replace helper\" ... ok\n", + "test \"explicit std vec_string replace range helper\" ... ok\n", + "test \"explicit std vec_string remove helper\" ... ok\n", + "test \"explicit std vec_string remove range helper\" ... ok\n", + "test \"explicit std vec_string real-program helpers\" ... ok\n", + "test \"explicit std vec_string helpers all\" ... ok\n", + "19 test(s) passed\n", + ), + "std import vec_string project test", + ); +} + +fn assert_project_std_import_core_facade_tooling_matches_fixture( + project: &Path, + module: &str, + helpers: &[&str], + expected: &str, +) { + assert_project_std_import_host_facade_shape(project, module, helpers); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std import core facade project check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout(test, expected, "std import core facade project test"); +} + +fn assert_project_std_import_host_facade_tooling_matches_fixture( + project: &Path, + module: &str, + helpers: &[&str], + expected: &str, +) { + assert_project_std_import_host_facade_shape(project, module, helpers); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std import host facade project check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout(test, expected, "std import host facade project test"); +} + +fn assert_project_std_import_host_facade_shape(project: &Path, module: &str, helpers: &[&str]) { + assert!(project.join("slovo.toml").is_file()); + assert!(project.join("src/main.slo").is_file()); + assert!( + !project.join(format!("src/{}.slo", module)).exists(), + "std import {} fixture must use repo-root std/{}.slo, not a local copy", + module, + module + ); + + let main = read(&project.join("src/main.slo")); + let slovo_source = read(&repo_root().join(format!("lib/std/{}.slo", module))); + assert!( + main.starts_with(&format!("(module main)\n\n(import std.{} (", module)), + "std import {} fixture must use explicit `std.{}` import syntax", + module, + module + ); + assert!( + slovo_source.starts_with(&format!("(module {} (export ", module)), + "repo-root Slovo std/{}.slo must directly export imported helpers", + module + ); + for helper in helpers { + assert!( + slovo_source.contains(&format!("(fn {} ", helper)), + "Slovo std/{}.slo is missing helper `{}`", + module, + helper + ); + assert!( + main.contains(helper), + "std import {} main fixture import/use is missing helper `{}`", + module, + helper + ); + } +} + +fn assert_installed_standard_library_discovery_is_promotable(repo: &Path) { + let project_rs = read(&repo.join("compiler/src/project.rs")); + + assert!( + project_rs.contains("env::current_exe()") + && project_rs.contains("installed_standard_library_candidates") + && project_rs.contains("../share/slovo/std"), + "standard-library discovery must include executable-relative installed `share/slovo/std`" + ); + assert!( + project_rs.contains("env::split_paths(&paths)"), + "SLOVO_STD_PATH must support OS path-list discovery roots" + ); + assert!( + project_rs.contains(".find(|path| path.is_file())"), + "standard-library discovery must choose the first candidate containing the requested module" + ); + assert!( + project_rs.contains("set SLOVO_STD_PATH, install `share/slovo/std`"), + "missing standard-library diagnostics must mention installed stdlib discovery" + ); +} + +fn assert_workspace_std_import_option_tooling_matches_fixture(workspace: &Path) { + let app = workspace.join("packages/app"); + let main = app.join("src/main.slo"); + assert!(workspace.join("slovo.toml").is_file()); + assert!(app.join("slovo.toml").is_file()); + assert!(main.is_file()); + assert!( + !app.join("src/option.slo").exists(), + "workspace std import option fixture must use repo-root std/option.slo, not a local copy" + ); + assert!( + !app.join("src/bridge.slo").exists(), + "workspace std import option fixture must not depend on a local bridge shim" + ); + + let main_source = read(&main); + assert!( + main_source.starts_with("(module main)\n\n(import std.option ("), + "workspace std import option fixture must use explicit `std.option` import syntax" + ); + for helper in ["is_some_i32", "is_none_i32", "unwrap_or_i32"] { + assert!( + main_source.matches(helper).count() >= 2, + "workspace std import option fixture import/use is missing helper `{}`", + helper + ); + } + for helper in [ + "some_or_err_i32", + "some_or_err_i64", + "some_or_err_f64", + "some_or_err_bool", + "some_or_err_string", + ] { + assert!( + main_source.contains(helper), + "workspace std import option main/import is missing bridge helper `{}`", + helper + ); + } + + let check = run_glagol([OsStr::new("check"), workspace.as_os_str()]); + assert_success_stdout(check, "", "workspace std import option check"); + + let test = run_glagol([OsStr::new("test"), workspace.as_os_str()]); + assert_success_stdout( + test, + "test \"workspace std option import\" ... ok\n1 test(s) passed\n", + "workspace std import option test", + ); +} + +fn assert_standard_math_source_helpers_alpha(project: &Path) { + let math = read(&project.join("src/math.slo")); + let main = read(&project.join("src/main.slo")); + let math_export_line = math.lines().next().unwrap_or_default(); + + assert!( + math.starts_with("(module math (export "), + "local math fixture must stay an explicitly exported local module" + ); + assert!( + main.starts_with("(module main)\n\n(import math ("), + "local math fixture must stay an explicit local import" + ); + assert!( + !math.contains("std.") && !main.contains("std."), + "standard math source helper fixture must not use compiler-known std runtime names" + ); + + for helper in STANDARD_MATH_SOURCE_HELPERS_ALPHA { + assert!( + math.contains(&format!("(fn {} ", helper)), + "local math fixture is missing helper `{}`", + helper + ); + assert!( + math_export_line.contains(helper), + "local math fixture export list is missing helper `{}`", + helper + ); + assert!( + main.matches(helper).count() >= 2, + "main fixture import/use is missing helper `{}`", + helper + ); + } +} + +fn normalize_local_math_fixture_helpers(source: &str) -> String { + let helper_source = source + .split("\n(fn i32_helpers_ok") + .next() + .expect("split local math helper source"); + let mut lines = helper_source.lines(); + let first = lines.next().unwrap_or_default(); + assert!( + first.starts_with("(module math (export "), + "local math fixture must start with an explicit export module form" + ); + + let mut normalized = first.to_string(); + for line in lines { + normalized.push('\n'); + normalized.push_str(line); + } + normalized.trim_end().to_string() +} + +fn normalize_local_vec_i32_fixture_helpers(source: &str) -> String { + source + .replace( + "(import option (some_i32 none_i32))", + "(import std.option (some_i32 none_i32))", + ) + .trim_end() + .to_string() +} + +fn assert_math_loop_benchmark_scaffold_is_promotable(root: &Path) { + for relative in [ + "slovo.toml", + "README.md", + "benchmark.json", + "src/main.slo", + "clojure/math_loop.clj", + "common-lisp/math_loop.lisp", + "c/math_loop.c", + "rust/math_loop.rs", + "python/math_loop.py", + "run.py", + ] { + assert!( + root.join(relative).is_file(), + "math-loop benchmark scaffold is missing `{}`", + relative + ); + } + + let slovo = read(&root.join("src/main.slo")); + let readme = read(&root.join("README.md")); + let spec = read(&root.join("benchmark.json")); + let runner = read(&root.join("run.py")); + let c = read(&root.join("c/math_loop.c")); + let rust = read(&root.join("rust/math_loop.rs")); + let python = read(&root.join("python/math_loop.py")); + let clojure = read(&root.join("clojure/math_loop.clj")); + let common_lisp = read(&root.join("common-lisp/math_loop.lisp")); + + assert!( + spec.contains(r#""benchmark": "math-loop""#) + && spec.contains(r#""source_stem": "math_loop""#) + && spec.contains(r#""expected_checksum": "5000001""#) + && spec.contains(r#""hot_loop_count": 10000000"#) + && spec.contains(r#""hot_expected_checksum": "50000001""#), + "math-loop benchmark metadata must keep benchmark identity and cold/hot checksums" + ); + assert!( + readme.contains("not a published benchmark result") + && readme.contains("loop count") + && readme.contains("run.py --list"), + "math-loop benchmark README must document local-only timing boundaries" + ); + assert!( + slovo.contains("(fn math_loop ((limit i32)) -> i32") + && slovo.contains("(std.io.read_stdin_result)") + && slovo.contains("(std.process.arg 1)") + && slovo.contains("(std.string.parse_i32_result text)") + && slovo.contains("(std.io.print_i32 result)"), + "Slovo math-loop benchmark must keep runtime loop-count input and checksum output" + ); + assert!( + runner.contains("local-machine comparison only") + || runner.contains("from runner import main"), + "math-loop runner must stay an explicit local timing harness wrapper" + ); + for (language, source) in [ + ("C", c), + ("Rust", rust), + ("Python", python), + ("Clojure", clojure), + ("Common Lisp", common_lisp), + ] { + assert!( + (source.contains("EXPECTED_CHECKSUM") + || source.contains("expected-checksum") + || source.contains("+expected-checksum+")) + && (source.contains("LOOP_COUNT") + || source.contains("loop-count") + || source.contains("+loop-count+")), + "{} math-loop fixture must keep shared constants", + language + ); + } +} + +fn assert_named_benchmark_scaffold_is_promotable(root: &Path, name: &str, source_stem: &str) { + for relative in [ + "slovo.toml", + "README.md", + "benchmark.json", + "src/main.slo", + "run.py", + ] { + assert!( + root.join(relative).is_file(), + "{} benchmark scaffold is missing `{}`", + name, + relative + ); + } + + for relative in [ + format!("clojure/{}.clj", source_stem), + format!("common-lisp/{}.lisp", source_stem), + format!("c/{}.c", source_stem), + format!("rust/{}.rs", source_stem), + format!("python/{}.py", source_stem), + ] { + assert!( + root.join(&relative).is_file(), + "{} benchmark scaffold is missing `{}`", + name, + relative + ); + } + + let spec = read(&root.join("benchmark.json")); + let slovo = read(&root.join("src/main.slo")); + let readme = read(&root.join("README.md")); + let runner = read(&root.join("run.py")); + + assert!( + spec.contains(&format!(r#""benchmark": "{}""#, name)) + && spec.contains(&format!(r#""source_stem": "{}""#, source_stem)) + && spec.contains(r#""loop_count": 1000000"#) + && spec.contains(r#""expected_checksum": "#) + && spec.contains(r#""hot_loop_count": 10000000"#) + && spec.contains(r#""hot_expected_checksum": "#), + "{} benchmark metadata must keep identity, source stem, cold/hot loop counts, and checksums", + name + ); + assert!( + readme.contains("not a published benchmark result") + && readme.contains("same machine") + && readme.contains("runtime"), + "{} benchmark README must document local-only timing boundaries", + name + ); + assert!( + slovo.contains("(std.io.read_stdin_result)") + && slovo.contains("(std.process.arg ") + && slovo.contains("(std.io.print_i32 result)") + && runner.contains("from runner import main"), + "{} benchmark must keep runtime loop-count input and shared runner wrapper", + name + ); +} + +fn assert_project_std_layout_local_option_tooling_matches_fixture(project: &Path) { + assert!(project.join("slovo.toml").is_file()); + assert!(project.join("src/option.slo").is_file()); + assert!(project.join("src/main.slo").is_file()); + assert_standard_option_source_helpers_alpha(project); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std layout local option project check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + test, + concat!( + "test \"explicit local option i32 observation\" ... ok\n", + "test \"explicit local option i32 unwrap_some\" ... ok\n", + "test \"explicit local option i32 unwrap_or\" ... ok\n", + "test \"explicit local option i32 some_or_err ok\" ... ok\n", + "test \"explicit local option i32 some_or_err err\" ... ok\n", + "test \"explicit local option u32 observation\" ... ok\n", + "test \"explicit local option u32 unwrap_some\" ... ok\n", + "test \"explicit local option u32 unwrap_or\" ... ok\n", + "test \"explicit local option u32 some_or_err ok\" ... ok\n", + "test \"explicit local option u32 some_or_err err\" ... ok\n", + "test \"explicit local option i64 observation\" ... ok\n", + "test \"explicit local option i64 unwrap_some\" ... ok\n", + "test \"explicit local option i64 unwrap_or\" ... ok\n", + "test \"explicit local option i64 some_or_err ok\" ... ok\n", + "test \"explicit local option i64 some_or_err err\" ... ok\n", + "test \"explicit local option u64 observation\" ... ok\n", + "test \"explicit local option u64 unwrap_some\" ... ok\n", + "test \"explicit local option u64 unwrap_or\" ... ok\n", + "test \"explicit local option u64 some_or_err ok\" ... ok\n", + "test \"explicit local option u64 some_or_err err\" ... ok\n", + "test \"explicit local option f64 observation\" ... ok\n", + "test \"explicit local option f64 unwrap_some\" ... ok\n", + "test \"explicit local option f64 unwrap_or\" ... ok\n", + "test \"explicit local option f64 some_or_err ok\" ... ok\n", + "test \"explicit local option f64 some_or_err err\" ... ok\n", + "test \"explicit local option bool observation\" ... ok\n", + "test \"explicit local option bool unwrap_some\" ... ok\n", + "test \"explicit local option bool unwrap_or\" ... ok\n", + "test \"explicit local option bool some_or_err ok\" ... ok\n", + "test \"explicit local option bool some_or_err err\" ... ok\n", + "test \"explicit local option string observation\" ... ok\n", + "test \"explicit local option string unwrap_some\" ... ok\n", + "test \"explicit local option string unwrap_or\" ... ok\n", + "test \"explicit local option string some_or_err ok\" ... ok\n", + "test \"explicit local option string some_or_err err\" ... ok\n", + "test \"explicit local option helpers all\" ... ok\n", + "36 test(s) passed\n", + ), + "std layout local option project test", + ); +} + +fn assert_standard_option_source_helpers_alpha(project: &Path) { + let option = read(&project.join("src/option.slo")); + let main = read(&project.join("src/main.slo")); + + assert!( + option.starts_with("(module option (export "), + "local option fixture must stay an explicitly exported local module" + ); + assert!( + main.starts_with("(module main)\n\n(import option ("), + "local option fixture must stay an explicit local import" + ); + assert!( + !option.contains("std.") && !main.contains("std."), + "standard option source helper fixture must not use compiler-known std runtime names" + ); + assert!( + !main.contains("(import std") && !main.contains("(import slovo.std"), + "standard option source helper fixture must not use automatic std imports" + ); + assert!( + !option.contains("std.result.") + && !main.contains("std.result.") + && !main.contains("(import result"), + "standard option source helper fixture must bridge through raw concrete result forms, not compiler-known std.result names or a separate local result module" + ); + + for helper in STANDARD_OPTION_SOURCE_HELPERS_ALPHA { + assert!( + option.contains(&format!("(fn {} ", helper)), + "local option fixture is missing helper `{}`", + helper + ); + assert!( + main.contains(helper), + "main fixture import/use is missing helper `{}`", + helper + ); + } + for helper in STANDARD_OPTION_RESULT_BRIDGE_HELPERS_ALPHA { + assert!( + option.contains(&format!("(fn {} ", helper)), + "local option fixture is missing bridge helper `{}`", + helper + ); + assert!( + main.contains(helper), + "main fixture import/use is missing bridge helper `{}`", + helper + ); + } +} + +fn assert_project_std_layout_local_num_tooling_matches_fixture(project: &Path) { + assert!(project.join("slovo.toml").is_file()); + assert!(project.join("src/num.slo").is_file()); + assert!(project.join("src/main.slo").is_file()); + assert_standard_num_source_fallback_helpers_alpha(project); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std layout local num project check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + test, + concat!( + "test \"explicit local num widening\" ... ok\n", + "test \"explicit local num checked conversions\" ... ok\n", + "test \"explicit local num to string\" ... ok\n", + "test \"explicit local num checked fallbacks\" ... ok\n", + "test \"explicit local num helpers all\" ... ok\n", + "5 test(s) passed\n", + ), + "std layout local num project test", + ); +} + +fn assert_standard_num_source_fallback_helpers_alpha(project: &Path) { + let num_source = read(&project.join("src/num.slo")); + let main = read(&project.join("src/main.slo")); + + assert!( + num_source.starts_with("(module num (export "), + "local num fixture must stay an explicitly exported local module" + ); + assert!( + main.starts_with("(module main)\n\n(import num ("), + "local num fixture must stay an explicit local import" + ); + assert!( + !main.contains("(import std") && !main.contains("(import slovo.std"), + "standard num source facade fixture must not use automatic std imports" + ); + assert_std_only_contains( + &num_source, + &[ + "std.num.i32_to_i64", + "std.num.i32_to_f64", + "std.num.i64_to_f64", + "std.num.i64_to_i32_result", + "std.num.f64_to_i32_result", + "std.num.f64_to_i64_result", + "std.num.i32_to_string", + "std.num.u32_to_string", + "std.num.i64_to_string", + "std.num.u64_to_string", + "std.num.f64_to_string", + ], + "standard num source facade fixture must use only existing std.num runtime names", + ); + assert!( + !main.contains("std."), + "standard num source facade main fixture must remain local and explicit" + ); + assert!( + !num_source.contains("saturat") + && !num_source.contains("round") + && !num_source.contains("floor") + && !num_source.contains("ceil") + && !num_source.contains("generic") + && !main.contains("saturat") + && !main.contains("round") + && !main.contains("floor") + && !main.contains("ceil") + && !main.contains("generic"), + "standard num source facade fixture must not claim deferred numeric conversion policies or generic abstractions" + ); + for helper in STANDARD_NUM_SOURCE_FACADE_ALPHA { + assert!( + num_source.contains(&format!("(fn {} ", helper)), + "local num fixture is missing facade `{}`", + helper + ); + assert!( + main.contains(helper), + "main fixture import/use is missing facade `{}`", + helper + ); + } +} + +fn assert_project_std_layout_local_process_tooling_matches_fixture(project: &Path) { + assert!(project.join("slovo.toml").is_file()); + assert!(project.join("src/process.slo").is_file()); + assert!(project.join("src/string.slo").is_file()); + assert!(project.join("src/main.slo").is_file()); + assert_standard_process_source_fallback_helpers_alpha(project); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std layout local process project check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + test, + concat!( + "test \"explicit local process argc facade\" ... ok\n", + "test \"explicit local process arg result oob facade\" ... ok\n", + "test \"explicit local process arg option facade\" ... ok\n", + "test \"explicit local process has arg facade\" ... ok\n", + "test \"explicit local process arg fallback missing facade\" ... ok\n", + "test \"explicit local process arg fallback present facade\" ... ok\n", + "test \"explicit local process arg i32 missing facade\" ... ok\n", + "test \"explicit local process arg i32 invalid fallback facade\" ... ok\n", + "test \"explicit local process arg u32 missing facade\" ... ok\n", + "test \"explicit local process arg u32 invalid fallback facade\" ... ok\n", + "test \"explicit local process arg i64 missing facade\" ... ok\n", + "test \"explicit local process arg i64 invalid fallback facade\" ... ok\n", + "test \"explicit local process arg u64 missing facade\" ... ok\n", + "test \"explicit local process arg u64 invalid fallback facade\" ... ok\n", + "test \"explicit local process arg f64 missing facade\" ... ok\n", + "test \"explicit local process arg f64 invalid fallback facade\" ... ok\n", + "test \"explicit local process arg bool missing facade\" ... ok\n", + "test \"explicit local process arg bool invalid fallback facade\" ... ok\n", + "test \"explicit local process typed option facade\" ... ok\n", + "test \"explicit local process typed custom fallback facade\" ... ok\n", + "test \"explicit local process facade all\" ... ok\n", + "21 test(s) passed\n", + ), + "std layout local process project test", + ); +} + +fn assert_standard_process_source_fallback_helpers_alpha(project: &Path) { + let process = read(&project.join("src/process.slo")); + let result = read(&project.join("src/result.slo")); + let string = read(&project.join("src/string.slo")); + let main = read(&project.join("src/main.slo")); + + assert!( + process.starts_with("(module process (export "), + "local process fixture must stay an explicitly exported local module" + ); + assert!( + string.starts_with("(module string (export "), + "local process string fixture must stay an explicitly exported local module" + ); + assert!( + result.starts_with("(module result (export "), + "local process result fixture must stay an explicitly exported local module" + ); + assert!( + main.starts_with("(module main)\n\n(import process ("), + "local process fixture must stay an explicit local import" + ); + assert!( + !main.contains("(import std") && !main.contains("(import slovo.std"), + "standard process source facade fixture must not use automatic std imports" + ); + assert!( + process.contains("(import result (ok_or_none_string ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool))") + && process.contains("(import string (parse_i32_result parse_u32_result parse_i64_result parse_u64_result parse_f64_result parse_bool_result))") + && !process.contains("std.string.") + && !process.contains("(import std.") + && !main.contains("std."), + "standard process source facade fixture must stay local while using the promoted result bridge and parse result calls" + ); + assert_std_only_contains( + &process, + &[ + "std.process.arg_result", + "std.process.argc", + "std.process.arg", + ], + "standard process source facade fixture must use only existing std.process runtime names directly", + ); + assert_std_only_contains( + &string, + &[ + "std.string.len", + "std.string.concat", + "std.string.parse_i32_result", + "std.string.parse_u32_result", + "std.string.parse_i64_result", + "std.string.parse_u64_result", + "std.string.parse_f64_result", + "std.string.parse_bool_result", + ], + "standard process source local string fixture must use only existing std.string runtime names", + ); + assert!( + !main.contains("std."), + "standard process source facade main fixture must remain local and explicit" + ); + assert!( + !process.contains("spawn") + && !process.contains("exit") + && !process.contains("cwd") + && !process.contains("signal") + && !process.contains("shell") + && !process.contains("flag"), + "standard process source facade fixture must not claim deferred process or CLI framework APIs" + ); + + for helper in STANDARD_PROCESS_SOURCE_FACADE_ALPHA { + assert!( + process.contains(&format!("(fn {} ", helper)), + "local process fixture is missing facade `{}`", + helper + ); + assert!( + main.contains(helper), + "main fixture import/use is missing facade `{}`", + helper + ); + } + for helper in STANDARD_STRING_PARSE_RESULT_HELPERS_ALPHA { + assert!( + string.contains(&format!("(fn {} ", helper)), + "local process string fixture is missing parse helper `{}`", + helper + ); + } + for helper in [ + "ok_or_none_string", + "ok_or_none_i32", + "ok_or_none_u32", + "ok_or_none_i64", + "ok_or_none_u64", + "ok_or_none_f64", + "ok_or_none_bool", + ] { + assert!( + result.contains(&format!("(fn {} ", helper)), + "local process result fixture is missing bridge helper `{}`", + helper + ); + } +} + +fn assert_project_std_layout_local_cli_tooling_matches_fixture(project: &Path) { + assert!(project.join("slovo.toml").is_file()); + assert!(project.join("src/cli.slo").is_file()); + assert!(project.join("src/process.slo").is_file()); + assert!(project.join("src/result.slo").is_file()); + assert!(project.join("src/string.slo").is_file()); + assert!(project.join("src/main.slo").is_file()); + assert_standard_cli_source_fallback_helpers_alpha(project); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std layout local cli project check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + test, + STANDARD_CLI_LOCAL_SOURCE_FALLBACK_EXPECTED_OUTPUT, + "std layout local cli project test", + ); +} + +fn assert_standard_cli_source_fallback_helpers_alpha(project: &Path) { + let cli = read(&project.join("src/cli.slo")); + let process = read(&project.join("src/process.slo")); + let result = read(&project.join("src/result.slo")); + let string = read(&project.join("src/string.slo")); + let main = read(&project.join("src/main.slo")); + + assert!( + cli.starts_with("(module cli (export "), + "local cli fixture must stay an explicitly exported local module" + ); + assert!( + process.starts_with("(module process (export "), + "local cli process fixture must stay an explicitly exported local module" + ); + assert!( + result.starts_with("(module result (export "), + "local cli result fixture must stay an explicitly exported local module" + ); + assert!( + string.starts_with("(module string (export "), + "local cli string fixture must stay an explicitly exported local module" + ); + assert!( + main.starts_with("(module main)\n\n(import cli ("), + "local cli fixture must stay an explicit local import" + ); + assert!( + !main.contains("(import std") && !main.contains("(import slovo.std"), + "standard cli source fallback fixture must not use automatic std imports" + ); + assert!( + cli.contains("(import process (arg_result))") + && cli.contains("(import result (ok_or_none_string ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool))") + && cli.contains( + "(import string (parse_i32_result parse_u32_result parse_i64_result parse_u64_result parse_f64_result parse_bool_result))" + ) + && !cli.contains("(import std.") + && !main.contains("std."), + "standard cli source fallback fixture must stay local while using local process, result, and parse helper imports" + ); + assert!( + !cli.contains("std."), + "standard cli source fallback module must stay fully local and explicit" + ); + assert_std_only_contains( + &process, + &[ + "std.process.arg_result", + "std.process.argc", + "std.process.arg", + "std.string.parse_i32_result", + "std.string.parse_u32_result", + "std.string.parse_i64_result", + "std.string.parse_u64_result", + "std.string.parse_f64_result", + "std.string.parse_bool_result", + ], + "standard cli source local process fixture must use only existing std.process runtime names", + ); + assert_std_only_contains( + &string, + &[ + "std.string.len", + "std.string.concat", + "std.string.parse_i32_result", + "std.string.parse_u32_result", + "std.string.parse_i64_result", + "std.string.parse_u64_result", + "std.string.parse_f64_result", + "std.string.parse_bool_result", + ], + "standard cli source local string fixture must use only existing std.string runtime names", + ); + assert!( + !process.contains("spawn") + && !process.contains("exit") + && !process.contains("cwd") + && !process.contains("signal") + && !process.contains("shell") + && !process.contains("flag"), + "standard cli source local process fixture must stay narrow and avoid deferred process APIs" + ); + + for helper in STANDARD_CLI_SOURCE_FACADE_ALPHA { + assert!( + cli.contains(&format!("(fn {} ", helper)), + "local cli fixture is missing facade `{}`", + helper + ); + assert!( + main.contains(helper), + "main fixture import/use is missing facade `{}`", + helper + ); + } + assert!( + process.contains("(fn arg_result "), + "local cli process fixture is missing arg_result helper" + ); + for helper in [ + "ok_or_none_string", + "ok_or_none_i32", + "ok_or_none_u32", + "ok_or_none_i64", + "ok_or_none_u64", + "ok_or_none_f64", + "ok_or_none_bool", + ] { + assert!( + result.contains(&format!("(fn {} ", helper)), + "local cli result fixture is missing bridge helper `{}`", + helper + ); + } + for helper in STANDARD_STRING_PARSE_RESULT_HELPERS_ALPHA { + assert!( + string.contains(&format!("(fn {} ", helper)), + "local cli string fixture is missing parse helper `{}`", + helper + ); + } +} + +fn assert_project_std_layout_local_string_tooling_matches_fixture(project: &Path) { + assert!(project.join("slovo.toml").is_file()); + assert!(project.join("src/result.slo").is_file()); + assert!(project.join("src/string.slo").is_file()); + assert!(project.join("src/main.slo").is_file()); + assert_standard_string_source_fallback_helpers_alpha(project); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std layout local string project check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + test, + concat!( + "test \"explicit local string len concat\" ... ok\n", + "test \"explicit local string parse result wrappers\" ... ok\n", + "test \"explicit local string parse option wrappers\" ... ok\n", + "test \"explicit local string parse integer fallbacks\" ... ok\n", + "test \"explicit local string parse float bool fallbacks\" ... ok\n", + "test \"explicit local string parse custom fallbacks\" ... ok\n", + "test \"explicit local string helpers all\" ... ok\n", + "7 test(s) passed\n", + ), + "std layout local string project test", + ); +} + +fn assert_standard_string_source_fallback_helpers_alpha(project: &Path) { + let string = read(&project.join("src/string.slo")); + let main = read(&project.join("src/main.slo")); + + assert!( + string.starts_with("(module string (export "), + "local string fixture must stay an explicitly exported local module" + ); + assert!( + main.starts_with("(module main)\n\n(import string ("), + "local string fixture must stay an explicit local import" + ); + assert!( + !main.contains("(import std") && !main.contains("(import slovo.std"), + "standard string source helper fixture must not use automatic std imports" + ); + assert_std_only_contains( + &string, + &[ + "std.string.parse_bool_result", + "std.string.parse_f64_result", + "std.string.parse_u64_result", + "std.string.parse_i64_result", + "std.string.parse_u32_result", + "std.string.parse_i32_result", + "std.string.concat", + "std.string.len", + ], + "standard string source helper fixture must use only existing std.string runtime names", + ); + assert!( + !string.contains("trim") + && !string.contains("locale") + && !string.contains("unicode") + && !string.contains("bytes") + && !string.contains("case_insensitive") + && !string.contains("host_error"), + "standard string source helper fixture must not claim deferred parsing or richer error APIs" + ); + + for helper in STANDARD_STRING_SOURCE_FACADE_ALPHA { + assert!( + string.contains(&format!("(fn {} ", helper)), + "local string fixture is missing helper `{}`", + helper + ); + assert!( + main.contains(helper), + "main fixture import/use is missing helper `{}`", + helper + ); + } +} + +fn assert_project_std_layout_local_time_tooling_matches_fixture(project: &Path) { + assert!(project.join("slovo.toml").is_file()); + assert!(project.join("src/time.slo").is_file()); + assert!(project.join("src/main.slo").is_file()); + assert_standard_time_source_facade_alpha(project); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std layout local time project check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + test, + concat!( + "test \"explicit local time monotonic facade\" ... ok\n", + "test \"explicit local time sleep zero facade\" ... ok\n", + "test \"explicit local time facade all\" ... ok\n", + "3 test(s) passed\n", + ), + "std layout local time project test", + ); +} + +fn assert_standard_time_source_facade_alpha(project: &Path) { + let time = read(&project.join("src/time.slo")); + let main = read(&project.join("src/main.slo")); + + assert!( + time.starts_with("(module time (export "), + "local time fixture must stay an explicitly exported local module" + ); + assert!( + main.starts_with("(module main)\n\n(import time ("), + "local time fixture must stay an explicit local import" + ); + assert!( + !main.contains("(import std") && !main.contains("(import slovo.std"), + "standard time source facade fixture must not use automatic std imports" + ); + + let mut non_time_std = time.clone(); + for allowed in ["std.time.monotonic_ms", "std.time.sleep_ms"] { + non_time_std = non_time_std.replace(allowed, ""); + } + assert!( + !non_time_std.contains("std.") && !main.contains("std."), + "standard time source facade fixture must use only existing std.time runtime names" + ); + assert!( + time.contains("(fn sleep_ms_zero () -> i32\n (std.time.sleep_ms 0)\n 0)"), + "standard time source facade fixture must adapt unit-return sleep_ms to source i32 return" + ); + assert!( + !time.contains("std.time.now") + && !time.contains("calendar") + && !time.contains("timezone") + && !time.contains("async") + && !time.contains("cancel") + && !time.contains("schedule"), + "standard time source facade fixture must not claim deferred time APIs or scheduling semantics" + ); + + for helper in STANDARD_TIME_SOURCE_FACADE_ALPHA { + assert!( + time.contains(&format!("(fn {} ", helper)), + "local time fixture is missing facade `{}`", + helper + ); + assert!( + main.contains(helper), + "main fixture import/use is missing facade `{}`", + helper + ); + } +} + +fn assert_project_std_layout_local_random_tooling_matches_fixture(project: &Path) { + assert!(project.join("slovo.toml").is_file()); + assert!(project.join("src/random.slo").is_file()); + assert!(project.join("src/main.slo").is_file()); + assert_standard_random_source_facade_alpha(project); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std layout local random project check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + test, + concat!( + "test \"local random i32 non-negative facade\" ... ok\n", + "test \"explicit local random i32 non-negative facade\" ... ok\n", + "test \"explicit local random facade all\" ... ok\n", + "3 test(s) passed\n", + ), + "std layout local random project test", + ); +} + +fn assert_standard_random_source_facade_alpha(project: &Path) { + let random = read(&project.join("src/random.slo")); + let main = read(&project.join("src/main.slo")); + + assert!( + random.starts_with("(module random (export "), + "local random fixture must stay an explicitly exported local module" + ); + assert!( + main.starts_with("(module main)\n\n(import random ("), + "local random fixture must stay an explicit local import" + ); + assert!( + !main.contains("(import std") && !main.contains("(import slovo.std"), + "standard random source facade fixture must not use automatic std imports" + ); + assert!( + random.contains("(std.random.i32)"), + "standard random source facade fixture must use the promoted std.random.i32 call" + ); + assert_std_only_contains( + &random, + &["std.random.i32"], + "standard random source facade fixture must use only existing std.random runtime names", + ); + assert!( + !main.contains("std.") && !main.contains("seed") && !main.contains("crypto"), + "standard random source facade main fixture must remain local and narrow" + ); + for helper in STANDARD_RANDOM_SOURCE_FACADE_ALPHA { + assert!( + random.contains(&format!("(fn {} ", helper)), + "local random fixture is missing facade `{}`", + helper + ); + assert!( + main.contains(helper), + "main fixture import/use is missing facade `{}`", + helper + ); + } +} + +fn assert_project_std_layout_local_env_tooling_matches_fixture(project: &Path) { + assert!(project.join("slovo.toml").is_file()); + assert!(project.join("src/env.slo").is_file()); + assert!(project.join("src/result.slo").is_file()); + assert!(project.join("src/string.slo").is_file()); + assert!(project.join("src/main.slo").is_file()); + assert_standard_env_source_facade_alpha(project); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std layout local env project check"); + + let test = run_glagol_configured([OsStr::new("test"), project.as_os_str()], |command| { + configure_standard_local_env(command); + }); + assert_success_stdout( + test, + concat!( + "test \"explicit local env get missing facade\" ... ok\n", + "test \"explicit local env get result missing facade\" ... ok\n", + "test \"explicit local env has missing facade\" ... ok\n", + "test \"explicit local env get or missing facade\" ... ok\n", + "test \"explicit local env get option missing facade\" ... ok\n", + "test \"explicit local env has present facade\" ... ok\n", + "test \"explicit local env get or present facade\" ... ok\n", + "test \"explicit local env get option present facade\" ... ok\n", + "test \"explicit local env get i32 result present facade\" ... ok\n", + "test \"explicit local env get i32 or zero invalid facade\" ... ok\n", + "test \"explicit local env get u32 result present facade\" ... ok\n", + "test \"explicit local env get u32 or zero invalid facade\" ... ok\n", + "test \"explicit local env get i64 result present facade\" ... ok\n", + "test \"explicit local env get i64 or zero missing facade\" ... ok\n", + "test \"explicit local env get u64 result present facade\" ... ok\n", + "test \"explicit local env get u64 or zero missing facade\" ... ok\n", + "test \"explicit local env get f64 result present facade\" ... ok\n", + "test \"explicit local env get f64 or zero invalid facade\" ... ok\n", + "test \"explicit local env get bool result present facade\" ... ok\n", + "test \"explicit local env get bool or false invalid facade\" ... ok\n", + "test \"explicit local env typed option facade\" ... ok\n", + "test \"explicit local env typed custom fallback facade\" ... ok\n", + "test \"explicit local env facade all\" ... ok\n", + "23 test(s) passed\n", + ), + "std layout local env project test", + ); +} + +fn assert_standard_env_source_facade_alpha(project: &Path) { + let env_source = read(&project.join("src/env.slo")); + let result_source = read(&project.join("src/result.slo")); + let string_source = read(&project.join("src/string.slo")); + let main = read(&project.join("src/main.slo")); + + assert!( + env_source.starts_with("(module env (export "), + "local env fixture must stay an explicitly exported local module" + ); + assert!( + string_source.starts_with("(module string (export "), + "local env string fixture must stay an explicitly exported local module" + ); + assert!( + result_source.starts_with("(module result (export "), + "local env result fixture must stay an explicitly exported local module" + ); + assert!( + main.starts_with("(module main)\n\n(import env ("), + "local env fixture must stay an explicit local import" + ); + assert!( + !main.contains("(import std") && !main.contains("(import slovo.std"), + "standard env source facade fixture must not use automatic std imports" + ); + assert!( + env_source.contains("(import result (ok_or_none_string ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool))") + && env_source.contains("(import string (parse_i32_result parse_u32_result parse_i64_result parse_u64_result parse_f64_result parse_bool_result))") + && !env_source.contains("std.string.") + && !env_source.contains("(import std.") + && !main.contains("std.") + && env_source.contains("(std.env.get name)") + && env_source.contains("(std.env.get_result name)"), + "standard env source facade fixture must stay local while using the promoted env lookup, result bridge, and parse result calls" + ); + assert_std_only_contains( + &env_source, + &["std.env.get_result", "std.env.get"], + "standard env source facade fixture must use only existing std.env runtime names directly", + ); + assert_std_only_contains( + &string_source, + &[ + "std.string.len", + "std.string.concat", + "std.string.parse_i32_result", + "std.string.parse_u32_result", + "std.string.parse_i64_result", + "std.string.parse_u64_result", + "std.string.parse_f64_result", + "std.string.parse_bool_result", + ], + "standard env source local string fixture must use only existing std.string runtime names", + ); + assert!( + !main.contains("std.") + && main.contains(STANDARD_ENV_MISSING_NAME) + && main.contains(STANDARD_ENV_PRESENT_NAME) + && main.contains(STANDARD_ENV_PRESENT_VALUE), + "standard env source facade main fixture must remain local and deterministic" + ); + assert!( + main.contains(STANDARD_ENV_PRESENT_I32_NAME) + && main.contains(STANDARD_ENV_PRESENT_U32_NAME) + && main.contains(STANDARD_ENV_PRESENT_I64_NAME) + && main.contains(STANDARD_ENV_PRESENT_U64_NAME) + && main.contains(STANDARD_ENV_PRESENT_F64_NAME) + && main.contains(STANDARD_ENV_PRESENT_BOOL_NAME) + && main.contains(STANDARD_ENV_INVALID_NAME), + "standard env source facade main fixture must cover deterministic typed env names" + ); + for helper in STANDARD_ENV_SOURCE_FACADE_ALPHA { + assert!( + env_source.contains(&format!("(fn {} ", helper)), + "local env fixture is missing facade `{}`", + helper + ); + assert!( + main.contains(helper), + "main fixture import/use is missing facade `{}`", + helper + ); + } + for helper in [ + "parse_i32_result", + "parse_u32_result", + "parse_i64_result", + "parse_u64_result", + "parse_f64_result", + "parse_bool_result", + ] { + assert!( + string_source.contains(&format!("(fn {} ", helper)), + "local env string fixture is missing parse helper `{}`", + helper + ); + } + for helper in [ + "ok_or_none_string", + "ok_or_none_i32", + "ok_or_none_u32", + "ok_or_none_i64", + "ok_or_none_u64", + "ok_or_none_f64", + "ok_or_none_bool", + ] { + assert!( + result_source.contains(&format!("(fn {} ", helper)), + "local env result fixture is missing bridge helper `{}`", + helper + ); + } +} + +fn assert_project_std_layout_local_fs_tooling_matches_fixture(project: &Path) { + assert!(project.join("slovo.toml").is_file()); + assert!(project.join("src/fs.slo").is_file()); + assert!(project.join("src/result.slo").is_file()); + assert!(project.join("src/string.slo").is_file()); + assert!(project.join("src/main.slo").is_file()); + assert_standard_fs_source_facade_alpha(project); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std layout local fs project check"); + + let test_cwd = temp_root("std-layout-local-fs"); + let test = run_glagol_in([OsStr::new("test"), project.as_os_str()], &test_cwd); + assert_success_stdout( + test, + concat!( + "test \"explicit local fs write text status facade\" ... ok\n", + "test \"explicit local fs read text facade\" ... ok\n", + "test \"explicit local fs write text result facade\" ... ok\n", + "test \"explicit local fs read text result facade\" ... ok\n", + "test \"explicit local fs read text option missing facade\" ... ok\n", + "test \"explicit local fs read text option present facade\" ... ok\n", + "test \"explicit local fs read text or missing facade\" ... ok\n", + "test \"explicit local fs read text or present facade\" ... ok\n", + "test \"explicit local fs write text ok facade\" ... ok\n", + "test \"explicit local fs read i32 result present facade\" ... ok\n", + "test \"explicit local fs read i32 or zero invalid facade\" ... ok\n", + "test \"explicit local fs read u32 result present facade\" ... ok\n", + "test \"explicit local fs read u32 or zero invalid facade\" ... ok\n", + "test \"explicit local fs read i64 result present facade\" ... ok\n", + "test \"explicit local fs read i64 or zero missing facade\" ... ok\n", + "test \"explicit local fs read u64 result present facade\" ... ok\n", + "test \"explicit local fs read u64 or zero missing facade\" ... ok\n", + "test \"explicit local fs read f64 result present facade\" ... ok\n", + "test \"explicit local fs read f64 or zero invalid facade\" ... ok\n", + "test \"explicit local fs read bool result present facade\" ... ok\n", + "test \"explicit local fs read bool or false invalid facade\" ... ok\n", + "test \"explicit local fs typed option facade\" ... ok\n", + "test \"explicit local fs typed custom fallback facade\" ... ok\n", + "test \"explicit local fs facade all\" ... ok\n", + "24 test(s) passed\n", + ), + "std layout local fs project test", + ); +} + +fn assert_standard_fs_source_facade_alpha(project: &Path) { + let fs_source = read(&project.join("src/fs.slo")); + let result_source = read(&project.join("src/result.slo")); + let string_source = read(&project.join("src/string.slo")); + let main = read(&project.join("src/main.slo")); + + assert!( + fs_source.starts_with("(module fs (export "), + "local fs fixture must stay an explicitly exported local module" + ); + assert!( + string_source.starts_with("(module string (export "), + "local fs string fixture must stay an explicitly exported local module" + ); + assert!( + result_source.starts_with("(module result (export "), + "local fs result fixture must stay an explicitly exported local module" + ); + assert!( + main.starts_with("(module main)\n\n(import fs ("), + "local fs fixture must stay an explicit local import" + ); + assert!( + !main.contains("(import std") && !main.contains("(import slovo.std"), + "standard fs source facade fixture must not use automatic std imports" + ); + assert!( + fs_source.contains("(import result (ok_or_none_string ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool))") + && fs_source.contains("(import string (parse_i32_result parse_u32_result parse_i64_result parse_u64_result parse_f64_result parse_bool_result))") + && fs_source.contains("(std.fs.read_text path)") + && fs_source.contains("(std.fs.read_text_result path)") + && fs_source.contains("(std.fs.write_text path text)") + && fs_source.contains("(std.fs.write_text_result path text)") + && !fs_source.contains("std.string.") + && !fs_source.contains("(import std.") + && !main.contains("std."), + "standard fs source facade fixture must stay local while using promoted fs, result bridge, and parse result calls" + ); + assert_std_only_contains( + &fs_source, + &[ + "std.fs.read_text_result", + "std.fs.write_text_result", + "std.fs.read_text", + "std.fs.write_text", + ], + "standard fs source facade fixture must use only existing std.fs runtime names directly", + ); + assert_std_only_contains( + &string_source, + &[ + "std.string.len", + "std.string.concat", + "std.string.parse_i32_result", + "std.string.parse_u32_result", + "std.string.parse_i64_result", + "std.string.parse_u64_result", + "std.string.parse_f64_result", + "std.string.parse_bool_result", + ], + "standard fs source local string fixture must use only existing std.string runtime names", + ); + assert!( + !main.contains("std.") + && main.contains("\"glagol-std-layout-local-fs-alpha.txt\"") + && main.contains("\"std fs source search alpha\"") + && main.contains("\"glagol-std-layout-local-fs-alpha-missing.txt\"") + && main.contains("\"std fs source fallback alpha\"") + && main.contains("\"glagol-std-layout-local-fs-alpha-i32.txt\"") + && main.contains("\"glagol-std-layout-local-fs-alpha-u32.txt\"") + && main.contains("\"glagol-std-layout-local-fs-alpha-i64.txt\"") + && main.contains("\"glagol-std-layout-local-fs-alpha-u64.txt\"") + && main.contains("\"glagol-std-layout-local-fs-alpha-f64.txt\"") + && main.contains("\"glagol-std-layout-local-fs-alpha-bool.txt\"") + && main.contains("\"glagol-std-layout-local-fs-alpha-invalid.txt\""), + "standard fs source facade main fixture must remain local and deterministic" + ); + for helper in STANDARD_FS_SOURCE_FACADE_ALPHA { + assert!( + fs_source.contains(&format!("(fn {} ", helper)), + "local fs fixture is missing facade `{}`", + helper + ); + assert!( + main.contains(helper), + "main fixture import/use is missing facade `{}`", + helper + ); + } + for helper in [ + "parse_i32_result", + "parse_u32_result", + "parse_i64_result", + "parse_u64_result", + "parse_f64_result", + "parse_bool_result", + ] { + assert!( + string_source.contains(&format!("(fn {} ", helper)), + "local fs string fixture is missing parse helper `{}`", + helper + ); + } + for helper in [ + "ok_or_none_string", + "ok_or_none_i32", + "ok_or_none_u32", + "ok_or_none_i64", + "ok_or_none_u64", + "ok_or_none_f64", + "ok_or_none_bool", + ] { + assert!( + result_source.contains(&format!("(fn {} ", helper)), + "local fs result fixture is missing bridge helper `{}`", + helper + ); + } +} + +fn assert_project_std_layout_local_io_tooling_matches_fixture(project: &Path) { + assert!(project.join("slovo.toml").is_file()); + assert!(project.join("src/io.slo").is_file()); + assert!(project.join("src/result.slo").is_file()); + assert!(project.join("src/string.slo").is_file()); + assert!(project.join("src/main.slo").is_file()); + assert_standard_io_source_value_helpers_alpha(project); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std layout local io project check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + test, + concat!( + "test \"explicit local io i64 zero facade\" ... ok\n", + "test \"explicit local io u32 zero facade\" ... ok\n", + "test \"explicit local io u64 zero facade\" ... ok\n", + "test \"explicit local io f64 zero facade\" ... ok\n", + "test \"explicit local io value facade\" ... ok\n", + "test \"explicit local io stdin result facade\" ... ok\n", + "test \"explicit local io stdin option facade\" ... ok\n", + "test \"explicit local io stdin text fallback facade\" ... ok\n", + "test \"explicit local io stdin typed result facade\" ... ok\n", + "test \"explicit local io stdin typed option facade\" ... ok\n", + "test \"explicit local io stdin typed fallback facade\" ... ok\n", + "test \"explicit local io helpers all\" ... ok\n", + "12 test(s) passed\n", + ), + "std layout local io project test", + ); +} + +fn assert_standard_io_source_value_helpers_alpha(project: &Path) { + let io_source = read(&project.join("src/io.slo")); + let result_source = read(&project.join("src/result.slo")); + let string_source = read(&project.join("src/string.slo")); + let main = read(&project.join("src/main.slo")); + + assert!( + io_source.starts_with("(module io (export "), + "local io fixture must stay an explicitly exported local module" + ); + assert!( + result_source.starts_with("(module result (export "), + "local io result fixture must stay an explicitly exported local module" + ); + assert!( + string_source.starts_with("(module string (export "), + "local io string fixture must stay an explicitly exported local module" + ); + assert!( + main.starts_with("(module main)\n\n(import io ("), + "local io fixture must stay an explicit local import" + ); + assert!( + !main.contains("(import std") && !main.contains("(import slovo.std"), + "standard io source helper fixture must not use automatic std imports" + ); + assert_std_only_contains( + &io_source, + &[ + "std.io.print_i32", + "std.io.print_i64", + "std.io.print_u32", + "std.io.print_u64", + "std.io.print_f64", + "std.io.print_string", + "std.io.print_bool", + "std.io.read_stdin_result", + ], + "standard io source helper fixture must use only existing std.io print and stdin-result runtime names directly", + ); + assert!( + io_source.contains( + "(import result (ok_or_none_string ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool))", + ) + && io_source.contains( + "(import string (parse_i32_result parse_u32_result parse_i64_result parse_u64_result parse_f64_result parse_bool_result))", + ) + && io_source.contains("(std.io.read_stdin_result)") + && !io_source.contains("eprint") + && !io_source.contains("read_line") + && !io_source.contains("stream") + && !io_source.contains("async") + && !main.contains("std.") + && !main.contains("eprint") + && !main.contains("read_line") + && !main.contains("stream") + && !main.contains("async"), + "standard io source helper fixture must stay local while using only the released stdin-result lane and must not claim deferred io helpers", + ); + + assert_std_only_contains( + &string_source, + &[ + "std.string.len", + "std.string.concat", + "std.string.parse_i32_result", + "std.string.parse_u32_result", + "std.string.parse_i64_result", + "std.string.parse_u64_result", + "std.string.parse_f64_result", + "std.string.parse_bool_result", + ], + "local io string fixture must not introduce other compiler-known std names", + ); + assert_std_only_contains( + &result_source, + &[ + "std.result.is_ok", + "std.result.is_err", + "std.result.unwrap_ok", + "std.result.unwrap_err", + ], + "local io result fixture must not introduce other compiler-known std names", + ); + + for helper in STANDARD_IO_SOURCE_FACADE_ALPHA { + assert!( + io_source.contains(&format!("(fn {} ", helper)), + "local io fixture is missing facade `{}`", + helper + ); + assert!( + main.contains(helper), + "main fixture import/use is missing facade `{}`", + helper + ); + } +} + +fn assert_project_std_layout_local_vec_i32_tooling_matches_fixture(project: &Path) { + assert!(project.join("slovo.toml").is_file()); + assert!(project.join("src/option.slo").is_file()); + assert!(project.join("src/vec_i32.slo").is_file()); + assert!(project.join("src/main.slo").is_file()); + assert_standard_vec_i32_source_helpers_alpha(project); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std layout local vec_i32 project check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + test, + concat!( + "test \"explicit local vec_i32 empty len facade\" ... ok\n", + "test \"explicit local vec_i32 direct at facade\" ... ok\n", + "test \"explicit local vec_i32 builder helpers\" ... ok\n", + "test \"explicit local vec_i32 constructor helpers\" ... ok\n", + "test \"explicit local vec_i32 range helper\" ... ok\n", + "test \"explicit local vec_i32 query helpers\" ... ok\n", + "test \"explicit local vec_i32 option query helpers\" ... ok\n", + "test \"explicit local vec_i32 starts_with helper\" ... ok\n", + "test \"explicit local vec_i32 ends_with helper\" ... ok\n", + "test \"explicit local vec_i32 without_suffix helper\" ... ok\n", + "test \"explicit local vec_i32 without_prefix helper\" ... ok\n", + "test \"explicit local vec_i32 transform helpers\" ... ok\n", + "test \"explicit local vec_i32 subvec helper\" ... ok\n", + "test \"explicit local vec_i32 insert helper\" ... ok\n", + "test \"explicit local vec_i32 insert range helper\" ... ok\n", + "test \"explicit local vec_i32 replace helper\" ... ok\n", + "test \"explicit local vec_i32 replace range helper\" ... ok\n", + "test \"explicit local vec_i32 remove helper\" ... ok\n", + "test \"explicit local vec_i32 remove range helper\" ... ok\n", + "test \"explicit local vec_i32 count_of helper\" ... ok\n", + "test \"explicit local vec_i32 real program helpers\" ... ok\n", + "test \"explicit local vec_i32 helpers all\" ... ok\n", + "22 test(s) passed\n", + ), + "std layout local vec_i32 project test", + ); +} + +fn assert_standard_vec_i32_source_helpers_alpha(project: &Path) { + let option_source = read(&project.join("src/option.slo")); + let vec_i32_source = read(&project.join("src/vec_i32.slo")); + let main = read(&project.join("src/main.slo")); + + assert!( + option_source.starts_with("(module option (export "), + "local option helper fixture must stay an explicitly exported local module" + ); + assert!( + vec_i32_source.starts_with("(module vec_i32 (export "), + "local vec_i32 fixture must stay an explicitly exported local module" + ); + assert!( + main.starts_with("(module main)\n\n(import vec_i32 ("), + "local vec_i32 fixture must stay an explicit local import" + ); + assert!( + !option_source.contains("(import std") + && !vec_i32_source.contains("(import std") + && !main.contains("(import std") + && !option_source.contains("(import slovo.std") + && !vec_i32_source.contains("(import slovo.std") + && !main.contains("(import slovo.std"), + "standard vec_i32 source helper fixture must not use automatic std imports" + ); + assert_std_only_contains( + &vec_i32_source, + &[ + "std.vec.i32.empty", + "std.vec.i32.append", + "std.vec.i32.len", + "std.vec.i32.index", + ], + "standard vec_i32 source helper fixture must use only existing std.vec.i32 runtime names", + ); + assert!( + !option_source.contains("std.") && !main.contains("std."), + "standard vec_i32 source helper main fixture must remain local and explicit" + ); + assert!( + !option_source.contains("(result ") + && !option_source.contains("(option i64)") + && !option_source.contains("(option f64)") + && !option_source.contains("(option string)") + && !option_source.contains("(option bool)") + && !vec_i32_source.contains("capacity") + && !vec_i32_source.contains("reserve") + && !vec_i32_source.contains("shrink") + && !vec_i32_source.contains("sort") + && !vec_i32_source.contains("map") + && !vec_i32_source.contains("filter") + && !main.contains("capacity") + && !main.contains("reserve") + && !main.contains("shrink") + && !main.contains("sort") + && !main.contains("map") + && !main.contains("filter") + && !vec_i32_source.contains("(vec i64)") + && !main.contains("(vec i64)") + && !vec_i32_source.contains("(vec f64)") + && !main.contains("(vec f64)") + && !vec_i32_source.contains("(vec string)") + && !main.contains("(vec string)") + && !vec_i32_source.contains("(vec bool)") + && !main.contains("(vec bool)") + && !vec_i32_source.contains("(option i64)") + && !main.contains("(option i64)") + && !vec_i32_source.contains("(option f64)") + && !main.contains("(option f64)") + && !vec_i32_source.contains("(option string)") + && !main.contains("(option string)") + && !vec_i32_source.contains("(option bool)") + && !main.contains("(option bool)"), + "standard vec_i32 source helper fixture must stay concrete to i32 vec and option helpers and must not claim deferred collection APIs" + ); + + for helper in STANDARD_VEC_I32_SOURCE_FACADE_ALPHA { + assert!( + vec_i32_source.contains(&format!("(fn {} ", helper)), + "local vec_i32 fixture is missing facade `{}`", + helper + ); + assert!( + main.contains(helper), + "main fixture import/use is missing facade `{}`", + helper + ); + } + for helper in [ + "repeat_loop", + "range_from_zero_loop", + "range_loop", + "concat_loop", + "take_loop", + "drop_loop", + "reverse_loop", + ] { + assert!( + vec_i32_source.contains(&format!("(fn {} ", helper)), + "local vec_i32 fixture is missing private recursive helper `{}`", + helper + ); + } +} + +fn assert_project_std_layout_local_vec_i64_tooling_matches_fixture(project: &Path) { + assert!(project.join("slovo.toml").is_file()); + assert!(project.join("src/vec_i64.slo").is_file()); + assert!(project.join("src/main.slo").is_file()); + assert_standard_vec_i64_source_helpers_alpha(project); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std layout local vec_i64 project check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + test, + concat!( + "test \"explicit local vec_i64 empty len facade\" ... ok\n", + "test \"explicit local vec_i64 direct at facade\" ... ok\n", + "test \"explicit local vec_i64 builder helpers\" ... ok\n", + "test \"explicit local vec_i64 query helpers\" ... ok\n", + "test \"explicit local vec_i64 option query helpers\" ... ok\n", + "test \"explicit local vec_i64 transform helpers\" ... ok\n", + "test \"explicit local vec_i64 subvec helper\" ... ok\n", + "test \"explicit local vec_i64 insert helper\" ... ok\n", + "test \"explicit local vec_i64 insert range helper\" ... ok\n", + "test \"explicit local vec_i64 replace helper\" ... ok\n", + "test \"explicit local vec_i64 replace range helper\" ... ok\n", + "test \"explicit local vec_i64 remove helper\" ... ok\n", + "test \"explicit local vec_i64 remove range helper\" ... ok\n", + "test \"explicit local vec_i64 real program helpers\" ... ok\n", + "test \"explicit local vec_i64 helpers all\" ... ok\n", + "15 test(s) passed\n", + ), + "std layout local vec_i64 project test", + ); +} + +fn assert_project_std_layout_local_vec_f64_tooling_matches_fixture(project: &Path) { + assert!(project.join("slovo.toml").is_file()); + assert!(project.join("src/vec_f64.slo").is_file()); + assert!(project.join("src/main.slo").is_file()); + assert_standard_vec_f64_source_helpers_alpha(project); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std layout local vec_f64 project check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + test, + concat!( + "test \"explicit local vec_f64 empty len facade\" ... ok\n", + "test \"explicit local vec_f64 direct at facade\" ... ok\n", + "test \"explicit local vec_f64 builder helpers\" ... ok\n", + "test \"explicit local vec_f64 query helpers\" ... ok\n", + "test \"explicit local vec_f64 option query helpers\" ... ok\n", + "test \"explicit local vec_f64 starts_with helper\" ... ok\n", + "test \"explicit local vec_f64 ends_with helper\" ... ok\n", + "test \"explicit local vec_f64 without_suffix helper\" ... ok\n", + "test \"explicit local vec_f64 without_prefix helper\" ... ok\n", + "test \"explicit local vec_f64 transform helpers\" ... ok\n", + "test \"explicit local vec_f64 subvec helper\" ... ok\n", + "test \"explicit local vec_f64 insert helper\" ... ok\n", + "test \"explicit local vec_f64 insert range helper\" ... ok\n", + "test \"explicit local vec_f64 replace helper\" ... ok\n", + "test \"explicit local vec_f64 replace range helper\" ... ok\n", + "test \"explicit local vec_f64 remove helper\" ... ok\n", + "test \"explicit local vec_f64 remove range helper\" ... ok\n", + "test \"explicit local vec_f64 real program helpers\" ... ok\n", + "test \"explicit local vec_f64 helpers all\" ... ok\n", + "19 test(s) passed\n", + ), + "std layout local vec_f64 project test", + ); +} + +fn assert_project_std_layout_local_vec_bool_tooling_matches_fixture(project: &Path) { + assert!(project.join("slovo.toml").is_file()); + assert!(project.join("src/vec_bool.slo").is_file()); + assert!(project.join("src/main.slo").is_file()); + assert_standard_vec_bool_source_helpers_alpha(project); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std layout local vec_bool project check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + test, + concat!( + "test \"explicit local vec_bool empty len facade\" ... ok\n", + "test \"explicit local vec_bool direct at facade\" ... ok\n", + "test \"explicit local vec_bool builder helpers\" ... ok\n", + "test \"explicit local vec_bool query helpers\" ... ok\n", + "test \"explicit local vec_bool option query helpers\" ... ok\n", + "test \"explicit local vec_bool starts_with helper\" ... ok\n", + "test \"explicit local vec_bool ends_with helper\" ... ok\n", + "test \"explicit local vec_bool without_suffix helper\" ... ok\n", + "test \"explicit local vec_bool without_prefix helper\" ... ok\n", + "test \"explicit local vec_bool transform helpers\" ... ok\n", + "test \"explicit local vec_bool subvec helper\" ... ok\n", + "test \"explicit local vec_bool insert helper\" ... ok\n", + "test \"explicit local vec_bool insert range helper\" ... ok\n", + "test \"explicit local vec_bool replace helper\" ... ok\n", + "test \"explicit local vec_bool replace range helper\" ... ok\n", + "test \"explicit local vec_bool remove helper\" ... ok\n", + "test \"explicit local vec_bool remove range helper\" ... ok\n", + "test \"explicit local vec_bool real program helpers\" ... ok\n", + "test \"explicit local vec_bool helpers all\" ... ok\n", + "19 test(s) passed\n", + ), + "std layout local vec_bool project test", + ); +} + +fn assert_standard_vec_i64_source_helpers_alpha(project: &Path) { + let vec_i64_source = read(&project.join("src/vec_i64.slo")); + let main = read(&project.join("src/main.slo")); + + assert!( + vec_i64_source.starts_with("(module vec_i64 (export "), + "local vec_i64 fixture must stay an explicitly exported local module" + ); + assert!( + main.starts_with("(module main)\n\n(import vec_i64 ("), + "local vec_i64 fixture must stay an explicit local import" + ); + assert!( + !vec_i64_source.contains("(import std") + && !main.contains("(import std.") + && !vec_i64_source.contains("(import slovo.std") + && !main.contains("(import slovo.std"), + "standard vec_i64 source helper fixture must not use automatic std imports" + ); + assert_std_only_contains( + &vec_i64_source, + &[ + "std.vec.i64.empty", + "std.vec.i64.append", + "std.vec.i64.len", + "std.vec.i64.index", + ], + "standard vec_i64 source helper fixture must use only existing std.vec.i64 runtime names", + ); + assert!( + !main.contains("std."), + "standard vec_i64 source helper main fixture must remain local and explicit" + ); + assert!( + !vec_i64_source.contains("(var ") + && !main.contains("(var ") + && !vec_i64_source.contains("(set ") + && !main.contains("(set ") + && !vec_i64_source.contains("capacity") + && !vec_i64_source.contains("reserve") + && !vec_i64_source.contains("shrink") + && !vec_i64_source.contains("sort") + && !vec_i64_source.contains("map") + && !vec_i64_source.contains("filter") + && !vec_i64_source.contains("(vec i32)") + && !main.contains("(vec i32)") + && !vec_i64_source.contains("(vec f64)") + && !main.contains("(vec f64)") + && !vec_i64_source.contains("(vec string)") + && !main.contains("(vec string)") + && !vec_i64_source.contains("(vec bool)") + && !main.contains("(vec bool)") + && !vec_i64_source.contains("(option f64)") + && !main.contains("(option f64)") + && !vec_i64_source.contains("(option string)") + && !main.contains("(option string)") + && !vec_i64_source.contains("(option bool)") + && !main.contains("(option bool)") + && !vec_i64_source.contains("(result i32") + && !main.contains("(result i32") + && !vec_i64_source.contains("(result i64") + && !main.contains("(result i64") + && !vec_i64_source.contains("(result f64") + && !main.contains("(result f64") + && !vec_i64_source.contains("(result string") + && !main.contains("(result string") + && !vec_i64_source.contains("(result bool") + && !main.contains("(result bool"), + "standard vec_i64 source helper fixture must stay concrete to vec i64 plus option i32/i64 helpers and must not claim deferred collection APIs" + ); + + for helper in STANDARD_VEC_I64_SOURCE_FACADE_ALPHA { + assert!( + vec_i64_source.contains(&format!("(fn {} ", helper)), + "local vec_i64 fixture is missing facade `{}`", + helper + ); + assert!( + main.contains(helper), + "main fixture import/use is missing facade `{}`", + helper + ); + } + for helper in [ + "index_of_option_loop", + "last_index_of_option_loop", + "contains_loop", + "sum_loop", + "concat_loop", + "take_loop", + "drop_loop", + "reverse_loop", + ] { + assert!( + vec_i64_source.contains(&format!("(fn {} ", helper)), + "local vec_i64 fixture is missing private recursive helper `{}`", + helper + ); + } +} + +fn assert_standard_vec_f64_source_helpers_alpha(project: &Path) { + let vec_f64_source = read(&project.join("src/vec_f64.slo")); + let main = read(&project.join("src/main.slo")); + + assert!( + vec_f64_source.starts_with("(module vec_f64 (export "), + "local vec_f64 fixture must stay an explicitly exported local module" + ); + assert!( + main.starts_with("(module main)\n\n(import vec_f64 ("), + "local vec_f64 fixture must stay an explicit local import" + ); + assert!( + !vec_f64_source.contains("(import std") + && !main.contains("(import std.") + && !vec_f64_source.contains("(import slovo.std") + && !main.contains("(import slovo.std"), + "standard vec_f64 source helper fixture must not use automatic std imports" + ); + assert_std_only_contains( + &vec_f64_source, + &[ + "std.vec.f64.empty", + "std.vec.f64.append", + "std.vec.f64.len", + "std.vec.f64.index", + ], + "standard vec_f64 source helper fixture must use only existing std.vec.f64 runtime names", + ); + assert!( + !main.contains("std."), + "standard vec_f64 source helper main fixture must remain local and explicit" + ); + assert!( + !vec_f64_source.contains("(var ") + && !main.contains("(var ") + && !vec_f64_source.contains("(set ") + && !main.contains("(set ") + && !vec_f64_source.contains("capacity") + && !vec_f64_source.contains("reserve") + && !vec_f64_source.contains("shrink") + && !vec_f64_source.contains("sort") + && !vec_f64_source.contains("map") + && !vec_f64_source.contains("filter") + && !vec_f64_source.contains("(vec i32)") + && !main.contains("(vec i32)") + && !vec_f64_source.contains("(vec i64)") + && !main.contains("(vec i64)") + && !vec_f64_source.contains("(vec string)") + && !main.contains("(vec string)") + && !vec_f64_source.contains("(vec bool)") + && !main.contains("(vec bool)") + && !vec_f64_source.contains("(option i64)") + && !main.contains("(option i64)") + && !vec_f64_source.contains("(option string)") + && !main.contains("(option string)") + && !vec_f64_source.contains("(option bool)") + && !main.contains("(option bool)") + && !vec_f64_source.contains("(result i32") + && !main.contains("(result i32") + && !vec_f64_source.contains("(result i64") + && !main.contains("(result i64") + && !vec_f64_source.contains("(result f64") + && !main.contains("(result f64") + && !vec_f64_source.contains("(result string") + && !main.contains("(result string") + && !vec_f64_source.contains("(result bool") + && !main.contains("(result bool"), + "standard vec_f64 source helper fixture must stay concrete to vec f64 plus option i32/f64 helpers and must not claim deferred generic or mutation-heavy collection APIs" + ); + + for helper in STANDARD_VEC_F64_SOURCE_FACADE_ALPHA { + assert!( + vec_f64_source.contains(&format!("(fn {} ", helper)), + "local vec_f64 fixture is missing facade `{}`", + helper + ); + assert!( + main.contains(helper), + "main fixture import/use is missing facade `{}`", + helper + ); + } + for helper in [ + "index_of_option_loop", + "last_index_of_option_loop", + "contains_loop", + "sum_loop", + "concat_loop", + "take_loop", + "drop_loop", + "reverse_loop", + ] { + assert!( + vec_f64_source.contains(&format!("(fn {} ", helper)), + "local vec_f64 fixture is missing private recursive helper `{}`", + helper + ); + } +} + +fn assert_standard_vec_bool_source_helpers_alpha(project: &Path) { + let vec_bool_source = read(&project.join("src/vec_bool.slo")); + let main = read(&project.join("src/main.slo")); + + assert!( + vec_bool_source.starts_with("(module vec_bool (export "), + "local vec_bool fixture must stay an explicitly exported local module" + ); + assert!( + main.starts_with("(module main)\n\n(import vec_bool ("), + "local vec_bool fixture must stay an explicit local import" + ); + assert!( + !vec_bool_source.contains("(import std") + && !main.contains("(import std.") + && !vec_bool_source.contains("(import slovo.std") + && !main.contains("(import slovo.std"), + "standard vec_bool source helper fixture must not use automatic std imports" + ); + assert_std_only_contains( + &vec_bool_source, + &[ + "std.vec.bool.empty", + "std.vec.bool.append", + "std.vec.bool.len", + "std.vec.bool.index", + ], + "standard vec_bool source helper fixture must use only existing std.vec.bool runtime names", + ); + assert!( + !main.contains("std."), + "standard vec_bool source helper main fixture must remain local and explicit" + ); + assert!( + !vec_bool_source.contains("(var ") + && !main.contains("(var ") + && !vec_bool_source.contains("(set ") + && !main.contains("(set ") + && !vec_bool_source.contains("capacity") + && !vec_bool_source.contains("reserve") + && !vec_bool_source.contains("shrink") + && !vec_bool_source.contains("sort") + && !vec_bool_source.contains("map") + && !vec_bool_source.contains("filter") + && !vec_bool_source.contains("(vec i32)") + && !main.contains("(vec i32)") + && !vec_bool_source.contains("(vec i64)") + && !main.contains("(vec i64)") + && !vec_bool_source.contains("(vec f64)") + && !main.contains("(vec f64)") + && !vec_bool_source.contains("(vec string)") + && !main.contains("(vec string)") + && !vec_bool_source.contains("(option i64)") + && !main.contains("(option i64)") + && !vec_bool_source.contains("(option f64)") + && !main.contains("(option f64)") + && !vec_bool_source.contains("(option string)") + && !main.contains("(option string)") + && !vec_bool_source.contains("(result i32") + && !main.contains("(result i32") + && !vec_bool_source.contains("(result i64") + && !main.contains("(result i64") + && !vec_bool_source.contains("(result f64") + && !main.contains("(result f64") + && !vec_bool_source.contains("(result string") + && !main.contains("(result string") + && !vec_bool_source.contains("(result bool") + && !main.contains("(result bool"), + "standard vec_bool source helper fixture must stay concrete to vec bool plus option i32/bool helpers and must not claim deferred generic or mutation-heavy collection APIs" + ); + + for helper in STANDARD_VEC_BOOL_SOURCE_FACADE_ALPHA { + assert!( + vec_bool_source.contains(&format!("(fn {} ", helper)), + "local vec_bool fixture is missing facade `{}`", + helper + ); + assert!( + main.contains(helper), + "main fixture import/use is missing facade `{}`", + helper + ); + } + for helper in [ + "index_of_option_loop", + "last_index_of_option_loop", + "contains_loop", + "count_of_loop", + "concat_loop", + "take_loop", + "drop_loop", + "reverse_loop", + ] { + assert!( + vec_bool_source.contains(&format!("(fn {} ", helper)), + "local vec_bool fixture is missing private recursive helper `{}`", + helper + ); + } +} + +fn assert_project_std_layout_local_vec_string_tooling_matches_fixture(project: &Path) { + assert!(project.join("slovo.toml").is_file()); + assert!(project.join("src/vec_string.slo").is_file()); + assert!(project.join("src/main.slo").is_file()); + assert_standard_vec_string_source_helpers_alpha(project); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std layout local vec_string project check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + test, + concat!( + "test \"explicit local vec_string empty len facade\" ... ok\n", + "test \"explicit local vec_string direct at facade\" ... ok\n", + "test \"explicit local vec_string builder helpers\" ... ok\n", + "test \"explicit local vec_string query helpers\" ... ok\n", + "test \"explicit local vec_string option query helpers\" ... ok\n", + "test \"explicit local vec_string starts_with helper\" ... ok\n", + "test \"explicit local vec_string ends_with helper\" ... ok\n", + "test \"explicit local vec_string without_suffix helper\" ... ok\n", + "test \"explicit local vec_string without_prefix helper\" ... ok\n", + "test \"explicit local vec_string transform helpers\" ... ok\n", + "test \"explicit local vec_string subvec helper\" ... ok\n", + "test \"explicit local vec_string insert helper\" ... ok\n", + "test \"explicit local vec_string insert range helper\" ... ok\n", + "test \"explicit local vec_string replace helper\" ... ok\n", + "test \"explicit local vec_string replace range helper\" ... ok\n", + "test \"explicit local vec_string remove helper\" ... ok\n", + "test \"explicit local vec_string remove range helper\" ... ok\n", + "test \"explicit local vec_string real-program helpers\" ... ok\n", + "test \"explicit local vec_string helpers all\" ... ok\n", + "19 test(s) passed\n", + ), + "std layout local vec_string project test", + ); +} + +fn assert_standard_vec_string_source_helpers_alpha(project: &Path) { + let vec_string_source = read(&project.join("src/vec_string.slo")); + let main = read(&project.join("src/main.slo")); + + assert!( + vec_string_source.starts_with("(module vec_string (export "), + "local vec_string fixture must stay an explicitly exported local module" + ); + assert!( + main.starts_with("(module main)\n\n(import vec_string ("), + "local vec_string fixture must stay an explicit local import" + ); + assert!( + !vec_string_source.contains("(import std") + && !main.contains("(import std.") + && !vec_string_source.contains("(import slovo.std") + && !main.contains("(import slovo.std"), + "standard vec_string source helper fixture must not use automatic std imports" + ); + assert_std_only_contains( + &vec_string_source, + &[ + "std.vec.string.empty", + "std.vec.string.append", + "std.vec.string.len", + "std.vec.string.index", + ], + "standard vec_string source helper fixture must use only existing std.vec.string runtime names", + ); + assert!( + !main.contains("std."), + "standard vec_string source helper main fixture must remain local and explicit" + ); + assert!( + !vec_string_source.contains("(var ") + && !main.contains("(var ") + && !vec_string_source.contains("(set ") + && !main.contains("(set ") + && !vec_string_source.contains("capacity") + && !vec_string_source.contains("reserve") + && !vec_string_source.contains("shrink") + && !vec_string_source.contains("sort") + && !vec_string_source.contains("map") + && !vec_string_source.contains("filter") + && !vec_string_source.contains("concat_all") + && !vec_string_source.contains("(vec i32)") + && !main.contains("(vec i32)") + && !vec_string_source.contains("(vec i64)") + && !main.contains("(vec i64)") + && !vec_string_source.contains("(vec f64)") + && !main.contains("(vec f64)") + && !vec_string_source.contains("(vec bool)") + && !main.contains("(vec bool)") + && !vec_string_source.contains("(option i64)") + && !main.contains("(option i64)") + && !vec_string_source.contains("(option f64)") + && !main.contains("(option f64)") + && !vec_string_source.contains("(option bool)") + && !main.contains("(option bool)") + && !vec_string_source.contains("(result i32") + && !main.contains("(result i32") + && !vec_string_source.contains("(result i64") + && !main.contains("(result i64") + && !vec_string_source.contains("(result f64") + && !main.contains("(result f64") + && !vec_string_source.contains("(result string") + && !main.contains("(result string") + && !vec_string_source.contains("(result bool") + && !main.contains("(result bool"), + "standard vec_string source helper fixture must stay concrete to vec string plus option i32/string helpers and must not claim deferred collection APIs" + ); + + for helper in STANDARD_VEC_STRING_SOURCE_FACADE_ALPHA { + assert!( + vec_string_source.contains(&format!("(fn {} ", helper)), + "local vec_string fixture is missing facade `{}`", + helper + ); + assert!( + main.contains(helper), + "main fixture import/use is missing facade `{}`", + helper + ); + } + for helper in [ + "index_of_option_loop", + "last_index_of_option_loop", + "contains_loop", + "count_of_loop", + "concat_loop", + "take_loop", + "drop_loop", + "reverse_loop", + ] { + assert!( + vec_string_source.contains(&format!("(fn {} ", helper)), + "local vec_string fixture is missing private recursive helper `{}`", + helper + ); + } +} + +fn assert_project_std_layout_local_result_tooling_matches_fixture(project: &Path) { + assert!(project.join("slovo.toml").is_file()); + assert!(project.join("src/result.slo").is_file()); + assert!(project.join("src/main.slo").is_file()); + assert_standard_result_source_helpers_alpha(project); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std layout local result project check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + test, + concat!( + "test \"explicit local result i32 wrappers\" ... ok\n", + "test \"explicit local result err wrappers\" ... ok\n", + "test \"explicit local result unwrap_or i32\" ... ok\n", + "test \"explicit local result ok_or_none i32 ok\" ... ok\n", + "test \"explicit local result ok_or_none i32 none\" ... ok\n", + "test \"explicit local result u32 wrappers\" ... ok\n", + "test \"explicit local result unwrap_or u32\" ... ok\n", + "test \"explicit local result ok_or_none u32 ok\" ... ok\n", + "test \"explicit local result ok_or_none u32 none\" ... ok\n", + "test \"explicit local result unwrap_or i64\" ... ok\n", + "test \"explicit local result ok_or_none i64 ok\" ... ok\n", + "test \"explicit local result ok_or_none i64 none\" ... ok\n", + "test \"explicit local result u64 wrappers\" ... ok\n", + "test \"explicit local result unwrap_or u64\" ... ok\n", + "test \"explicit local result ok_or_none u64 ok\" ... ok\n", + "test \"explicit local result ok_or_none u64 none\" ... ok\n", + "test \"explicit local result unwrap_or string\" ... ok\n", + "test \"explicit local result ok_or_none string ok\" ... ok\n", + "test \"explicit local result ok_or_none string none\" ... ok\n", + "test \"explicit local result unwrap_or f64\" ... ok\n", + "test \"explicit local result ok_or_none f64 ok\" ... ok\n", + "test \"explicit local result ok_or_none f64 none\" ... ok\n", + "test \"explicit local result bool helpers\" ... ok\n", + "test \"explicit local result ok_or_none bool ok\" ... ok\n", + "test \"explicit local result ok_or_none bool none\" ... ok\n", + "test \"explicit local result helpers all\" ... ok\n", + "26 test(s) passed\n", + ), + "std layout local result project test", + ); +} + +fn assert_standard_result_source_helpers_alpha(project: &Path) { + let result = read(&project.join("src/result.slo")); + let main = read(&project.join("src/main.slo")); + + assert!( + result.starts_with("(module result (export "), + "local result fixture must stay an explicitly exported local module" + ); + assert!( + main.starts_with("(module main)\n\n(import result ("), + "local result fixture must stay an explicit local import" + ); + assert!( + !main.contains("(import std") && !main.contains("(import slovo.std"), + "standard result source helper fixture must not use automatic std imports" + ); + assert!( + !result.contains("std.result.unwrap_or") + && !result.contains("std.result.map") + && !result.contains("std.result.and_then") + && !main.contains("std.result.unwrap_or") + && !main.contains("std.result.map") + && !main.contains("std.result.and_then"), + "standard result source helper fixture must keep deferred result helpers out" + ); + + let mut non_result_std = result.clone(); + for allowed in [ + "std.result.is_ok", + "std.result.is_err", + "std.result.unwrap_ok", + "std.result.unwrap_err", + ] { + non_result_std = non_result_std.replace(allowed, ""); + } + assert!( + !non_result_std.contains("std."), + "standard result source helper definitions must use only existing std.result names" + ); + assert!( + !result.contains("std.option.") + && !main.contains("std.option.") + && !main.contains("(import option"), + "standard result source helper fixture must bridge through raw concrete option forms, not compiler-known std.option names or a separate local option module" + ); + + for helper in STANDARD_RESULT_SOURCE_HELPERS_ALPHA { + assert!( + result.contains(&format!("(fn {} ", helper)), + "local result fixture is missing helper `{}`", + helper + ); + assert!( + main.contains(helper), + "main fixture import/use is missing helper `{}`", + helper + ); + } + for helper in STANDARD_RESULT_OPTION_BRIDGE_HELPERS_ALPHA { + assert!( + result.contains(&format!("(fn {} ", helper)), + "local result fixture is missing bridge helper `{}`", + helper + ); + assert!( + main.contains(helper), + "main fixture import/use is missing bridge helper `{}`", + helper + ); + } +} + +fn assert_success_stdout(output: Output, expected: &str, context: &str) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert_eq!(stdout, expected, "{} stdout drifted", context); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} + +fn assert_diagnostic_snapshots_are_promotable(repo: &Path) { + assert_file_name_inventory( + &repo.join("tests"), + &["diag"], + DIAGNOSTIC_SNAPSHOTS, + "promotion gate expects every diagnostic snapshot to be explicitly inventoried", + ); + + for snapshot in DIAGNOSTIC_SNAPSHOTS { + let path = repo.join("tests").join(snapshot); + let content = read(&path); + + assert!( + content.starts_with("(diagnostic\n"), + "`{}` must use the Slovo v1 diagnostic root", + path.display() + ); + assert!( + content.contains(" (schema slovo.diagnostic)\n"), + "`{}` is missing Slovo diagnostic schema marker", + path.display() + ); + assert!( + content.contains(" (version 1)\n"), + "`{}` is missing Slovo diagnostic schema version", + path.display() + ); + assert!( + content.contains("(severity error)"), + "`{}` is missing machine severity", + path.display() + ); + assert!( + content.contains(" (code "), + "`{}` is missing a machine diagnostic code", + path.display() + ); + assert!( + content.contains(" (file \"\")"), + "`{}` must normalize fixture paths", + path.display() + ); + assert!( + content.contains(" (span\n") && content.contains(" (bytes "), + "`{}` is missing byte span", + path.display() + ); + assert!( + content.contains(" (range "), + "`{}` is missing line/column range", + path.display() + ); + + if *snapshot == "test-duplicate-name.diag" { + assert!( + content.contains(" (related\n") + && content.contains(" (message \"original test name\")") + && content.contains(" (bytes "), + "`{}` is missing duplicate-name related span", + path.display() + ); + } + } +} + +fn assert_file_name_inventory(dir: &Path, extensions: &[&str], expected: &[&str], context: &str) { + let mut actual = read_dir_file_names_with_extensions(dir, extensions); + actual.sort(); + + let mut expected = expected + .iter() + .map(|name| (*name).to_string()) + .collect::>(); + expected.sort(); + + assert_eq!(actual, expected, "{}", context); +} + +fn required_llvm_shapes(expected: &str) -> Vec<&str> { + expected + .lines() + .map(str::trim) + .filter(|line| !line.is_empty()) + .filter(|line| !line.starts_with(';')) + .collect() +} + +fn strip_leading_comments_and_blank_lines(source: &str) -> String { + source + .lines() + .skip_while(|line| line.trim().is_empty() || line.trim_start().starts_with(';')) + .collect::>() + .join("\n") + + "\n" +} + +fn strip_comments(source: &str) -> String { + source + .lines() + .map(|line| line.split_once(';').map_or(line, |(code, _)| code)) + .collect::>() + .join("\n") +} + +fn contains_word(source: &str, word: &str) -> bool { + source + .split(|ch: char| !ch.is_ascii_alphanumeric() && ch != '_') + .any(|token| token == word) +} + +fn read(path: &Path) -> String { + fs::read_to_string(path).unwrap_or_else(|err| panic!("read `{}`: {}", path.display(), err)) +} + +fn read_dir_files(path: &Path) -> Vec { + fs::read_dir(path) + .unwrap_or_else(|err| panic!("read dir `{}`: {}", path.display(), err)) + .filter_map(|entry| { + let entry = entry.unwrap_or_else(|err| panic!("read dir entry: {}", err)); + let path = entry.path(); + path.is_file().then_some(path) + }) + .collect() +} + +fn read_dir_file_names_with_extensions(path: &Path, extensions: &[&str]) -> Vec { + fs::read_dir(path) + .unwrap_or_else(|err| panic!("read dir `{}`: {}", path.display(), err)) + .filter_map(|entry| { + let entry = entry.unwrap_or_else(|err| panic!("read dir entry: {}", err)); + let path = entry.path(); + let extension = path.extension().and_then(OsStr::to_str)?; + if path.is_file() && extensions.contains(&extension) { + path.file_name().and_then(OsStr::to_str).map(str::to_string) + } else { + None + } + }) + .collect() +} + +fn assert_std_only_contains(source: &str, allowed: &[&str], context: &str) { + let mut remaining = source.to_string(); + for name in allowed { + remaining = remaining.replace(name, ""); + } + assert!(!remaining.contains("std."), "{}", context); +} + +fn repo_root() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")) + .parent() + .expect("compiler crate has repo parent") + .to_path_buf() +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .output() + .expect("run glagol") +} + +fn run_glagol_in(args: I, cwd: &Path) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(cwd) + .output() + .expect("run glagol") +} + +fn run_glagol_configured(args: I, configure: F) -> Output +where + I: IntoIterator, + S: AsRef, + F: FnOnce(&mut Command), +{ + let mut command = Command::new(env!("CARGO_BIN_EXE_glagol")); + command.args(args); + configure(&mut command); + command.output().expect("run glagol") +} + +fn configure_standard_local_env(command: &mut Command) { + command + .env_remove(STANDARD_ENV_MISSING_NAME) + .env(STANDARD_ENV_PRESENT_NAME, STANDARD_ENV_PRESENT_VALUE) + .env( + STANDARD_ENV_PRESENT_I32_NAME, + STANDARD_ENV_PRESENT_I32_VALUE, + ) + .env( + STANDARD_ENV_PRESENT_U32_NAME, + STANDARD_ENV_PRESENT_U32_VALUE, + ) + .env( + STANDARD_ENV_PRESENT_I64_NAME, + STANDARD_ENV_PRESENT_I64_VALUE, + ) + .env( + STANDARD_ENV_PRESENT_U64_NAME, + STANDARD_ENV_PRESENT_U64_VALUE, + ) + .env( + STANDARD_ENV_PRESENT_F64_NAME, + STANDARD_ENV_PRESENT_F64_VALUE, + ) + .env( + STANDARD_ENV_PRESENT_BOOL_NAME, + STANDARD_ENV_PRESENT_BOOL_VALUE, + ) + .env(STANDARD_ENV_INVALID_NAME, STANDARD_ENV_INVALID_VALUE); +} + +fn configure_standard_import_env(command: &mut Command) { + command + .env_remove(STANDARD_IMPORT_ENV_MISSING_NAME) + .env( + STANDARD_IMPORT_ENV_PRESENT_NAME, + STANDARD_IMPORT_ENV_PRESENT_VALUE, + ) + .env( + STANDARD_IMPORT_ENV_PRESENT_I32_NAME, + STANDARD_IMPORT_ENV_PRESENT_I32_VALUE, + ) + .env( + STANDARD_IMPORT_ENV_PRESENT_U32_NAME, + STANDARD_IMPORT_ENV_PRESENT_U32_VALUE, + ) + .env( + STANDARD_IMPORT_ENV_PRESENT_I64_NAME, + STANDARD_IMPORT_ENV_PRESENT_I64_VALUE, + ) + .env( + STANDARD_IMPORT_ENV_PRESENT_U64_NAME, + STANDARD_IMPORT_ENV_PRESENT_U64_VALUE, + ) + .env( + STANDARD_IMPORT_ENV_PRESENT_F64_NAME, + STANDARD_IMPORT_ENV_PRESENT_F64_VALUE, + ) + .env( + STANDARD_IMPORT_ENV_PRESENT_BOOL_NAME, + STANDARD_IMPORT_ENV_PRESENT_BOOL_VALUE, + ) + .env( + STANDARD_IMPORT_ENV_INVALID_NAME, + STANDARD_IMPORT_ENV_INVALID_VALUE, + ); +} + +fn temp_root(name: &str) -> PathBuf { + let path = + std::env::temp_dir().join(format!("glagol-promotion-{}-{}", name, std::process::id())); + let _ = fs::remove_dir_all(&path); + fs::create_dir_all(&path).unwrap_or_else(|err| panic!("create `{}`: {}", path.display(), err)); + path +} diff --git a/compiler/tests/reliability_hardening.rs b/compiler/tests/reliability_hardening.rs new file mode 100644 index 0000000..48d264b --- /dev/null +++ b/compiler/tests/reliability_hardening.rs @@ -0,0 +1,833 @@ +use std::{ + env, + ffi::OsStr, + fs, + path::{Path, PathBuf}, + process::{Command, Output}, + sync::atomic::{AtomicUsize, Ordering}, +}; + +static NEXT_ID: AtomicUsize = AtomicUsize::new(0); + +#[test] +fn promoted_fixtures_format_idempotently_and_remain_checkable() { + let manifest = Path::new(env!("CARGO_MANIFEST_DIR")); + let mut fixtures = vec![ + manifest.join("../tests/comments.slo"), + manifest.join("../tests/formatter-stability-v1.fmt"), + manifest.join("../tests/top-level-test.slo"), + manifest.join("../tests/owned-string-concat.slo"), + manifest.join("../tests/vec-i32.slo"), + manifest.join("../tests/standard-runtime.slo"), + manifest.join("../tests/standard-io-host-env.slo"), + manifest.join("../tests/enum-basic.slo"), + ]; + + let generated = [ + ( + "generated-branches", + r#" +(module main) + +(fn clamp ((value i32)) -> i32 + (if (< value 0) 0 value)) + +(test "clamp positive" + (= (clamp 7) 7)) +"#, + ), + ( + "generated-vec-string-time", + r#" +(module main) + +(fn main () -> i32 + (std.time.sleep_ms 0) + (+ (std.vec.i32.len (std.vec.i32.append (std.vec.i32.empty) 1)) + (std.string.len (std.string.concat "a" "bc")))) +"#, + ), + ]; + + for (name, source) in generated { + fixtures.push(write_fixture(name, source)); + } + + for fixture in fixtures { + let formatted = run_glagol(["fmt".as_ref(), fixture.as_os_str()]); + assert_success(&format!("format {}", fixture.display()), &formatted); + assert!( + formatted.stderr.is_empty(), + "formatter wrote stderr for `{}`:\n{}", + fixture.display(), + String::from_utf8_lossy(&formatted.stderr) + ); + + let formatted_source = + String::from_utf8(formatted.stdout).expect("formatted source is UTF-8"); + let formatted_fixture = write_fixture("exp9-formatted", &formatted_source); + + let second = run_glagol(["fmt".as_ref(), formatted_fixture.as_os_str()]); + assert_success( + &format!("format second pass {}", fixture.display()), + &second, + ); + assert_eq!( + String::from_utf8(second.stdout).expect("second formatted source is UTF-8"), + formatted_source, + "formatter was not idempotent for `{}`", + fixture.display() + ); + + let fmt_check = run_glagol([ + "fmt".as_ref(), + "--check".as_ref(), + formatted_fixture.as_os_str(), + ]); + assert_success(&format!("fmt --check {}", fixture.display()), &fmt_check); + + let check = run_glagol(["check".as_ref(), formatted_fixture.as_os_str()]); + assert_success_stdout(&format!("check formatted {}", fixture.display()), check, ""); + } +} + +#[test] +fn bounded_malformed_inputs_emit_structured_diagnostics_without_panic() { + let cases = [ + ("unclosed-top-list", "(module main"), + ( + "unbalanced-function", + "(module main)\n\n(fn main () -> i32\n (+ 1 2)\n", + ), + ( + "unknown-top-level", + "(module main)\n\n(exp9 unknown form)\n", + ), + ( + "malformed-if", + "(module main)\n\n(fn main () -> i32\n (if true 1))\n", + ), + ( + "bounded-parens", + "((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((\n", + ), + ( + "nul-byte", + "(module main)\n\n(fn main () -> i32\n (print_string \"a\0b\")\n 0)\n", + ), + ( + "mutated-promoted-call", + "(module main)\n\n(fn main () -> i32\n (std.vec.i32.append (std.vec.i32.empty)))\n", + ), + ]; + + for (name, source) in cases { + let fixture = write_fixture(name, source); + let output = run_glagol([ + "--json-diagnostics".as_ref(), + "check".as_ref(), + fixture.as_os_str(), + ]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert_eq!( + output.status.code(), + Some(1), + "malformed case `{}` exit code drifted\nstdout:\n{}\nstderr:\n{}", + name, + stdout, + stderr + ); + assert!(stdout.is_empty(), "malformed case `{}` wrote stdout", name); + assert_no_panic_text(name, &stderr); + assert_json_diagnostic_shape(name, &stderr); + assert!( + stderr.contains(r#""severity":"error""#), + "malformed case `{}` did not report an error diagnostic:\n{}", + name, + stderr + ); + } +} + +#[test] +fn json_diagnostic_schema_and_spans_are_sane_across_pipeline_snapshots() { + let cases = [ + DiagnosticCase { + name: "parse", + args: &["--json-diagnostics", "check"], + source: "(module main", + expected_code: "UnclosedList", + }, + DiagnosticCase { + name: "lower", + args: &["--json-diagnostics", "check"], + source: "(module main)\n\n(unknown top level)\n", + expected_code: "UnknownTopLevelForm", + }, + DiagnosticCase { + name: "type-check", + args: &["--json-diagnostics", "check"], + source: r#" +(module main) + +(fn id ((value i32)) -> i32 + value) + +(fn main () -> i32 + (id true)) +"#, + expected_code: "TypeMismatch", + }, + DiagnosticCase { + name: "formatter", + args: &["--json-diagnostics", "fmt"], + source: r#" +(module main) ; trailing comments are outside the formatter subset + +(fn main () -> i32 + 0) +"#, + expected_code: "UnsupportedFormatterComment", + }, + DiagnosticCase { + name: "test-runner", + args: &["--json-diagnostics", "test"], + source: r#" +(module main) + +(test "false" + false) +"#, + expected_code: "TestFailed", + }, + DiagnosticCase { + name: "related-span", + args: &["--json-diagnostics", "check"], + source: r#" +(module main) + +(fn dup () -> i32 + 1) + +(fn dup () -> i32 + 2) +"#, + expected_code: "DuplicateFunction", + }, + ]; + + for case in cases { + let fixture = write_fixture(case.name, case.source); + let mut args = case.args.iter().map(OsStr::new).collect::>(); + args.push(fixture.as_os_str()); + let output = run_glagol(args); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert_eq!( + output.status.code(), + Some(1), + "diagnostic case `{}` exit code drifted\nstdout:\n{}\nstderr:\n{}", + case.name, + String::from_utf8_lossy(&output.stdout), + stderr + ); + assert_json_diagnostic_shape(case.name, &stderr); + assert!( + stderr.contains(&format!(r#""code":"{}""#, case.expected_code)), + "diagnostic case `{}` did not contain expected code `{}`:\n{}", + case.name, + case.expected_code, + stderr + ); + assert_span_bounds(case.name, case.source, &stderr); + } +} + +#[test] +fn project_workspace_graph_and_manifest_output_are_deterministic() { + let workspace = write_workspace("exp9-workspace"); + let first_manifest = temp_path("exp9-workspace-manifest-a", "slo"); + let second_manifest = temp_path("exp9-workspace-manifest-b", "slo"); + + let first = run_glagol([ + "check".as_ref(), + "--manifest".as_ref(), + first_manifest.as_os_str(), + workspace.as_os_str(), + ]); + assert_success("workspace check first", &first); + + let second = run_glagol([ + "check".as_ref(), + "--manifest".as_ref(), + second_manifest.as_os_str(), + workspace.as_os_str(), + ]); + assert_success("workspace check second", &second); + + let first_manifest = fs::read_to_string(&first_manifest).expect("read first manifest"); + let second_manifest = fs::read_to_string(&second_manifest).expect("read second manifest"); + assert_manifest_schema("first workspace manifest", &first_manifest); + assert_manifest_schema("second workspace manifest", &second_manifest); + assert_eq!( + project_block(&first_manifest), + project_block(&second_manifest), + "workspace project manifest block was not deterministic" + ); + + let block = project_block(&first_manifest); + assert_order( + block, + r#"(member "packages/app")"#, + r#"(member "packages/mathlib")"#, + ); + assert_order(block, r#"(name "mathlib")"#, r#"(name "app")"#); + assert_order(block, r#"(name "mathlib.math")"#, r#"(name "app.main")"#); + assert!( + block.contains(r#"(package_dependency"#) + && block.contains(r#"(from "app")"#) + && block.contains(r#"(to "mathlib")"#) + && block.contains(r#"(import_edge"#) + && block.contains(r#"(from "app.main")"#) + && block.contains(r#"(to "mathlib.math")"#), + "workspace graph manifest missed dependency/import edges:\n{}", + block + ); + + let filtered = run_glagol([ + "test".as_ref(), + workspace.as_os_str(), + "--filter".as_ref(), + "app imports".as_ref(), + ]); + assert_success_stdout( + "workspace filtered test", + filtered, + "test \"mathlib local\" ... skipped\ntest \"app imports mathlib\" ... ok\n1 test(s) passed (total_discovered 2, selected 1, passed 1, failed 0, skipped 1, filter \"app imports\")\n", + ); +} + +#[test] +fn project_manifest_boundary_failures_stay_structured_and_non_panicking() { + let cases = [ + ( + "invalid-manifest", + write_project_with_manifest( + "exp9-invalid-manifest", + "[project]\nname = \"Bad_Name\"\nsource_root = \"src\"\nentry = \"main\"\n", + &[("main", "(module main)\n\n(fn main () -> i32\n 0)\n")], + ), + "ProjectManifestInvalid", + ), + ( + "workspace-path-escape", + write_workspace_with_manifest( + "exp9-path-escape", + "[workspace]\nmembers = [\"../outside\"]\n", + ), + "WorkspaceMemberPathEscape", + ), + ( + "duplicate-package", + write_workspace_duplicate_packages("exp9-duplicate-package"), + "DuplicatePackageName", + ), + ( + "package-cycle", + write_workspace_package_cycle("exp9-package-cycle"), + "PackageDependencyCycle", + ), + ]; + + for (name, root, expected_code) in cases { + let output = run_glagol([ + "--json-diagnostics".as_ref(), + "check".as_ref(), + root.as_os_str(), + ]); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert_eq!( + output.status.code(), + Some(1), + "project boundary `{}` exit code drifted\nstdout:\n{}\nstderr:\n{}", + name, + String::from_utf8_lossy(&output.stdout), + stderr + ); + assert_no_panic_text(name, &stderr); + assert_json_diagnostic_shape(name, &stderr); + assert!( + stderr.contains(&format!(r#""code":"{}""#, expected_code)), + "project boundary `{}` did not report `{}`:\n{}", + name, + expected_code, + stderr + ); + } +} + +#[test] +fn promoted_runtime_api_smoke_and_traps_are_stable_through_test_runner() { + let fixture = write_fixture( + "exp9-runtime-smoke", + r#" +(module main) + +(enum Color Red Blue) + +(fn joined () -> string + (std.string.concat "slo" "vo")) + +(fn vector_len () -> i32 + (std.vec.i32.len (std.vec.i32.append (std.vec.i32.empty) 9))) + +(fn sleep_zero_then_one () -> i32 + (std.time.sleep_ms 0) + 1) + +(test "string concat length" + (= (std.string.len (joined)) 5)) + +(test "vec append length" + (= (vector_len) 1)) + +(test "env missing remains empty" + (= (std.env.get "GLAGOL_EXP9_MISSING") "")) + +(test "time smoke" + (= (sleep_zero_then_one) 1)) + +(test "enum equality" + (= (Color.Red) (Color.Red))) +"#, + ); + + let output = run_glagol(["test".as_ref(), fixture.as_os_str()]); + assert_success_stdout( + "promoted runtime smoke", + output, + concat!( + "test \"string concat length\" ... ok\n", + "test \"vec append length\" ... ok\n", + "test \"env missing remains empty\" ... ok\n", + "test \"time smoke\" ... ok\n", + "test \"enum equality\" ... ok\n", + "5 test(s) passed\n", + ), + ); + + let traps = [ + ( + "vec-index-trap", + r#" +(module main) + +(test "vec index trap" + (= (std.vec.i32.index (std.vec.i32.empty) 0) 0)) +"#, + "slovo runtime error: vector index out of bounds", + ), + ( + "sleep-negative-trap", + r#" +(module main) + +(test "negative sleep trap" + (= (do_negative_sleep) 0)) + +(fn do_negative_sleep () -> i32 + (std.time.sleep_ms -1) + 0) +"#, + "slovo runtime error: sleep_ms negative duration", + ), + ( + "process-arg-trap", + r#" +(module main) + +(test "process arg trap" + (= (std.string.len (std.process.arg 99)) 0)) +"#, + "slovo runtime error: process argument index out of bounds", + ), + ]; + + for (name, source, expected_trap) in traps { + let fixture = write_fixture(name, source); + let output = run_glagol(["test".as_ref(), fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert_eq!( + output.status.code(), + Some(1), + "runtime trap `{}` exit code drifted\nstdout:\n{}\nstderr:\n{}", + name, + stdout, + stderr + ); + assert!( + stdout.is_empty(), + "runtime trap `{}` wrote stdout:\n{}", + name, + stdout + ); + assert!( + stderr.contains("TestRuntimeTrap") && stderr.contains(expected_trap), + "runtime trap `{}` diagnostic drifted:\n{}", + name, + stderr + ); + } +} + +struct DiagnosticCase<'a> { + name: &'a str, + args: &'a [&'a str], + source: &'a str, + expected_code: &'a str, +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn write_fixture(name: &str, source: &str) -> PathBuf { + let path = temp_path(&format!("{}.slo", name), "slo"); + fs::write(&path, source).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err)); + path +} + +fn temp_path(name: &str, extension: &str) -> PathBuf { + env::temp_dir().join(format!( + "glagol-exp9-{}-{}-{}.{}", + std::process::id(), + NEXT_ID.fetch_add(1, Ordering::Relaxed), + sanitize_name(name), + extension + )) +} + +fn sanitize_name(name: &str) -> String { + name.chars() + .map(|ch| { + if ch.is_ascii_alphanumeric() || ch == '-' || ch == '_' { + ch + } else { + '-' + } + }) + .collect() +} + +fn unique_dir(name: &str) -> PathBuf { + env::temp_dir().join(format!( + "glagol-exp9-{}-{}-{}", + std::process::id(), + NEXT_ID.fetch_add(1, Ordering::Relaxed), + sanitize_name(name) + )) +} + +fn write_project_with_manifest(name: &str, manifest: &str, modules: &[(&str, &str)]) -> PathBuf { + let root = unique_dir(name); + let src = root.join("src"); + fs::create_dir_all(&src).expect("create project src"); + fs::write(root.join("slovo.toml"), manifest).expect("write project manifest"); + for (module, source) in modules { + fs::write(src.join(format!("{}.slo", module)), source).expect("write module source"); + } + root +} + +fn write_workspace(name: &str) -> PathBuf { + let root = write_workspace_with_manifest( + name, + "[workspace]\nmembers = [\"packages/app\", \"packages/mathlib\"]\n", + ); + write_package( + &root, + "packages/mathlib", + "[package]\nname = \"mathlib\"\nversion = \"0.1.0\"\nsource_root = \"src\"\n", + &[( + "math", + r#"(module math (export add_one)) + +(fn add_one ((value i32)) -> i32 + (+ value 1)) + +(test "mathlib local" + (= (add_one 1) 2)) +"#, + )], + ); + write_package( + &root, + "packages/app", + "[package]\nname = \"app\"\nversion = \"0.1.0\"\nsource_root = \"src\"\nentry = \"main\"\n\n[dependencies]\nmathlib = { path = \"../mathlib\" }\n", + &[( + "main", + r#"(module main) + +(import mathlib.math (add_one)) + +(fn main () -> i32 + (add_one 41)) + +(test "app imports mathlib" + (= (add_one 41) 42)) +"#, + )], + ); + root +} + +fn write_workspace_with_manifest(name: &str, manifest: &str) -> PathBuf { + let root = unique_dir(name); + fs::create_dir_all(&root).expect("create workspace root"); + fs::write(root.join("slovo.toml"), manifest).expect("write workspace manifest"); + root +} + +fn write_workspace_duplicate_packages(name: &str) -> PathBuf { + let root = write_workspace_with_manifest( + name, + "[workspace]\nmembers = [\"packages/a\", \"packages/b\"]\n", + ); + for member in ["packages/a", "packages/b"] { + write_package( + &root, + member, + "[package]\nname = \"dup\"\nversion = \"0.1.0\"\nsource_root = \"src\"\n", + &[("main", "(module main)\n\n(fn main () -> i32\n 0)\n")], + ); + } + root +} + +fn write_workspace_package_cycle(name: &str) -> PathBuf { + let root = write_workspace_with_manifest( + name, + "[workspace]\nmembers = [\"packages/app\", \"packages/mathlib\"]\n", + ); + write_package( + &root, + "packages/app", + "[package]\nname = \"app\"\nversion = \"0.1.0\"\nsource_root = \"src\"\n\n[dependencies]\nmathlib = { path = \"../mathlib\" }\n", + &[("main", "(module main)\n\n(fn main () -> i32\n 0)\n")], + ); + write_package( + &root, + "packages/mathlib", + "[package]\nname = \"mathlib\"\nversion = \"0.1.0\"\nsource_root = \"src\"\n\n[dependencies]\napp = { path = \"../app\" }\n", + &[("math", "(module math)\n\n(fn value () -> i32\n 1)\n")], + ); + root +} + +fn write_package(root: &Path, member: &str, manifest: &str, modules: &[(&str, &str)]) { + let package_root = root.join(member); + let src = package_root.join("src"); + fs::create_dir_all(&src).expect("create package src"); + fs::write(package_root.join("slovo.toml"), manifest).expect("write package manifest"); + for (module, source) in modules { + fs::write(src.join(format!("{}.slo", module)), source).expect("write package module"); + } +} + +fn assert_success(context: &str, output: &Output) { + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); +} + +fn assert_success_stdout(context: &str, output: Output, expected: &str) { + assert_success(context, &output); + assert_eq!( + String::from_utf8_lossy(&output.stdout), + expected, + "{} stdout mismatch", + context + ); + assert!( + output.stderr.is_empty(), + "{} wrote stderr:\n{}", + context, + String::from_utf8_lossy(&output.stderr) + ); +} + +fn assert_no_panic_text(context: &str, stderr: &str) { + assert!( + !stderr.contains("panicked at") + && !stderr.contains("thread 'main' panicked") + && !stderr.contains("internal compiler error"), + "{} exposed panic text:\n{}", + context, + stderr + ); +} + +fn assert_json_diagnostic_shape(context: &str, stderr: &str) { + let lines = stderr + .lines() + .filter(|line| !line.trim().is_empty()) + .collect::>(); + assert!(!lines.is_empty(), "{} did not emit diagnostics", context); + for line in lines { + assert!( + line.starts_with('{') && line.ends_with('}'), + "{} emitted non-JSON diagnostic line:\n{}", + context, + stderr + ); + assert!( + line.contains(r#""schema":"slovo.diagnostic""#) + && line.contains(r#""version":1"#) + && line.contains(r#""severity":"#) + && line.contains(r#""code":"#) + && line.contains(r#""message":"#) + && line.contains(r#""file":"#) + && line.contains(r#""span":"#), + "{} JSON diagnostic missed required fields:\n{}", + context, + line + ); + } +} + +fn assert_span_bounds(context: &str, source: &str, stderr: &str) { + let source_len = source.len(); + let line_count = source.lines().count().max(1); + let spans = json_span_objects(stderr); + assert!( + !spans.is_empty(), + "{} did not include any concrete spans:\n{}", + context, + stderr + ); + + for span in spans { + let byte_start = json_number(span, "byte_start").expect("span byte_start"); + let byte_end = json_number(span, "byte_end").expect("span byte_end"); + assert!( + byte_start <= byte_end && byte_end <= source_len, + "{} span byte bounds invalid for source length {}: {}", + context, + source_len, + span + ); + + if let Some(line_start) = json_number(span, "line_start") { + let column_start = json_number(span, "column_start").expect("span column_start"); + let line_end = json_number(span, "line_end").expect("span line_end"); + let column_end = json_number(span, "column_end").expect("span column_end"); + assert!( + line_start >= 1 + && line_start <= line_end + && line_end <= line_count + 1 + && column_start >= 1 + && column_end >= 1, + "{} span line/column bounds invalid for {} lines: {}", + context, + line_count, + span + ); + } + } +} + +fn json_span_objects(json_lines: &str) -> Vec<&str> { + let mut spans = Vec::new(); + let needle = r#""span":{"#; + for line in json_lines.lines() { + let mut search_start = 0; + while let Some(relative) = line[search_start..].find(needle) { + let object_start = search_start + relative + r#""span":"#.len(); + let mut depth = 0usize; + let mut object_end = None; + for (offset, ch) in line[object_start..].char_indices() { + match ch { + '{' => depth += 1, + '}' => { + depth -= 1; + if depth == 0 { + object_end = Some(object_start + offset + 1); + break; + } + } + _ => {} + } + } + if let Some(end) = object_end { + spans.push(&line[object_start..end]); + search_start = end; + } else { + break; + } + } + } + spans +} + +fn json_number(object: &str, field: &str) -> Option { + let needle = format!(r#""{}":"#, field); + let start = object.find(&needle)? + needle.len(); + let end = object[start..] + .find(|ch: char| !ch.is_ascii_digit()) + .map(|relative| start + relative) + .unwrap_or(object.len()); + object[start..end].parse().ok() +} + +fn assert_manifest_schema(context: &str, manifest: &str) { + assert!( + manifest.contains(" (schema slovo.artifact-manifest)\n") + && manifest.contains(" (version 1)\n") + && manifest.contains(" (diagnostics-schema-version 1)\n") + && manifest.contains(" (diagnostics-encoding sexpr)\n"), + "{} manifest schema fields drifted:\n{}", + context, + manifest + ); +} + +fn project_block(manifest: &str) -> &str { + let start = manifest + .find(" (project\n") + .expect("manifest did not contain project block"); + manifest[start..] + .strip_suffix("\n)\n") + .expect("manifest did not end with artifact-manifest close") +} + +fn assert_order(haystack: &str, before: &str, after: &str) { + let before_index = haystack + .find(before) + .unwrap_or_else(|| panic!("missing `{}` in:\n{}", before, haystack)); + let after_index = haystack + .find(after) + .unwrap_or_else(|| panic!("missing `{}` in:\n{}", after, haystack)); + assert!( + before_index < after_index, + "`{}` did not appear before `{}` in:\n{}", + before, + after, + haystack + ); +} diff --git a/compiler/tests/result_based_host_errors.rs b/compiler/tests/result_based_host_errors.rs new file mode 100644 index 0000000..34b0dfd --- /dev/null +++ b/compiler/tests/result_based_host_errors.rs @@ -0,0 +1,728 @@ +use std::{ + env, + ffi::OsStr, + fs, + io::Write, + path::{Path, PathBuf}, + process::{Command, Output, Stdio}, + sync::atomic::{AtomicUsize, Ordering}, +}; + +static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0); + +#[test] +fn exp10_fixture_formats_and_lowers_when_implementation_lands() { + let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/host-io-result.slo"); + + let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]); + assert_success_stdout( + formatted, + &fs::read_to_string(&fixture).expect("read exp-10 formatter fixture"), + "exp-10 formatter fixture", + ); + + let surface = run_glagol([ + OsStr::new("--inspect-lowering=surface"), + fixture.as_os_str(), + ]); + assert_success("exp-10 surface lowering", &surface); + let surface_stdout = String::from_utf8_lossy(&surface.stdout); + assert!( + surface_stdout.contains("fn ok_text(value: string) -> (result string i32)") + && surface_stdout.contains("ok string i32") + && surface_stdout.contains("call std.process.arg_result") + && surface_stdout.contains("call std.env.get_result") + && surface_stdout.contains("call std.fs.read_text_result") + && surface_stdout.contains("call std.fs.write_text_result") + && surface_stdout.contains("match") + && surface_stdout.contains("unwrap_ok") + && surface_stdout.contains("unwrap_err"), + "surface lowering lost exp-10 result host shape\nstdout:\n{}", + surface_stdout + ); + + let checked = run_glagol([ + OsStr::new("--inspect-lowering=checked"), + fixture.as_os_str(), + ]); + assert_success("exp-10 checked lowering", &checked); + let checked_stdout = String::from_utf8_lossy(&checked.stdout); + assert!( + checked_stdout.contains("ok : (result string i32)") + && checked_stdout.contains("err : (result string i32)") + && checked_stdout.contains("call std.process.arg_result : (result string i32)") + && checked_stdout.contains("call std.env.get_result : (result string i32)") + && checked_stdout.contains("call std.fs.read_text_result : (result string i32)") + && checked_stdout.contains("call std.fs.write_text_result : (result i32 i32)"), + "checked lowering lost exp-10 typed result host shape\nstdout:\n{}", + checked_stdout + ); +} + +#[test] +fn exp10_fixture_emits_private_result_host_runtime_shape() { + let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/host-io-result.slo"); + let compile = run_glagol([fixture.as_os_str()]); + assert_success("compile exp-10 host result fixture", &compile); + let stdout = String::from_utf8_lossy(&compile.stdout); + + assert!( + stdout.contains("__glagol_process_arg_result") + && stdout.contains("__glagol_env_get_result") + && stdout.contains("__glagol_fs_read_text_result") + && stdout.contains("__glagol_fs_write_text_result") + && stdout.contains("ptr") + && stdout.contains("i32") + && !stdout.contains("@std.process.arg_result") + && !stdout.contains("@std.env.get_result") + && !stdout.contains("@std.fs.read_text_result") + && !stdout.contains("@std.fs.write_text_result"), + "LLVM output did not contain expected exp-10 private runtime shape\nstdout:\n{}", + stdout + ); +} + +#[test] +fn test_runner_executes_deterministic_exp10_ok_and_err_paths() { + let root = temp_root("test-runner"); + fs::create_dir_all(&root).unwrap_or_else(|err| panic!("create `{}`: {}", root.display(), err)); + let existing = root.join("existing.txt"); + let roundtrip = root.join("roundtrip.txt"); + let missing = root.join("missing.txt"); + let unwritable = root.join("missing-dir").join("out.txt"); + fs::write(&existing, "fixture text") + .unwrap_or_else(|err| panic!("write `{}`: {}", existing.display(), err)); + + let missing_env = format!( + "GLAGOL_EXP10_MISSING_{}_{}", + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + ); + let source = format!( + r#" +(module main) + +(test "env present result ok" + (= (unwrap_ok (std.env.get_result "GLAGOL_EXP10_PRESENT")) "env-value")) + +(test "env missing result err one" + (= (unwrap_err (std.env.get_result "{}")) 1)) + +(test "arg zero result ok" + (is_ok (std.process.arg_result 0))) + +(test "arg negative result err one" + (= (unwrap_err (std.process.arg_result -1)) 1)) + +(test "arg out of range result err one" + (= (unwrap_err (std.process.arg_result 99999)) 1)) + +(test "read text result ok" + (= (unwrap_ok (std.fs.read_text_result "{}")) "fixture text")) + +(test "read text result err one" + (= (unwrap_err (std.fs.read_text_result "{}")) 1)) + +(test "write text result ok zero" + (= (unwrap_ok (std.fs.write_text_result "{}" "roundtrip")) 0)) + +(test "write text result err one" + (= (unwrap_err (std.fs.write_text_result "{}" "nope")) 1)) + +(test "read written text result ok" + (= (unwrap_ok (std.fs.read_text_result "{}")) "roundtrip")) + +(test "string payload can be matched" + (= (match (std.fs.read_text_result "{}") + ((ok payload) + (std.string.len payload)) + ((err code) + code)) + 12)) + +(test "stdin result deterministic ok" + (is_ok (std.io.read_stdin_result))) + +(test "stdin result deterministic empty payload" + (= (std.string.len (unwrap_ok (std.io.read_stdin_result))) 0)) +"#, + missing_env, + slovo_path(&existing), + slovo_path(&missing), + slovo_path(&roundtrip), + slovo_path(&unwritable), + slovo_path(&roundtrip), + slovo_path(&existing) + ); + let fixture = write_fixture("test-runner", &source); + let run = run_glagol_configured([OsStr::new("test"), fixture.as_os_str()], |command| { + command + .env("GLAGOL_EXP10_PRESENT", "env-value") + .env_remove(&missing_env); + }); + + assert_success_stdout( + run, + concat!( + "test \"env present result ok\" ... ok\n", + "test \"env missing result err one\" ... ok\n", + "test \"arg zero result ok\" ... ok\n", + "test \"arg negative result err one\" ... ok\n", + "test \"arg out of range result err one\" ... ok\n", + "test \"read text result ok\" ... ok\n", + "test \"read text result err one\" ... ok\n", + "test \"write text result ok zero\" ... ok\n", + "test \"write text result err one\" ... ok\n", + "test \"read written text result ok\" ... ok\n", + "test \"string payload can be matched\" ... ok\n", + "test \"stdin result deterministic ok\" ... ok\n", + "test \"stdin result deterministic empty payload\" ... ok\n", + "13 test(s) passed\n", + ), + "exp-10 test-runner host result output", + ); +} + +#[test] +fn exp3_host_calls_keep_trap_and_status_behavior() { + let root = temp_root("exp3-regression"); + fs::create_dir_all(&root).unwrap_or_else(|err| panic!("create `{}`: {}", root.display(), err)); + let unwritable = root.join("missing-dir").join("out.txt"); + let missing_env = format!( + "GLAGOL_EXP10_EXP3_MISSING_{}_{}", + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + ); + let source = format!( + r#" +(module main) + +(test "exp3 missing env is still empty" + (= (std.env.get "{}") "")) + +(test "exp3 write failure is still status one" + (= (std.fs.write_text "{}" "nope") 1)) +"#, + missing_env, + slovo_path(&unwritable) + ); + let fixture = write_fixture("exp3-regression", &source); + let run = run_glagol_configured([OsStr::new("test"), fixture.as_os_str()], |command| { + command.env_remove(&missing_env); + }); + + assert_success_stdout( + run, + concat!( + "test \"exp3 missing env is still empty\" ... ok\n", + "test \"exp3 write failure is still status one\" ... ok\n", + "2 test(s) passed\n", + ), + "exp-3 host regression output", + ); +} + +#[test] +fn exp10_diagnostics_cover_promoted_and_deferred_boundaries() { + let cases = [ + ( + "arg-result-arity", + r#" +(module main) + +(fn main () -> i32 + (std.process.arg_result) + 0) +"#, + "ArityMismatch", + ), + ( + "env-result-type", + r#" +(module main) + +(fn main () -> i32 + (std.env.get_result 1) + 0) +"#, + "TypeMismatch", + ), + ( + "write-result-type", + r#" +(module main) + +(fn main () -> i32 + (std.fs.write_text_result "path" 1) + 0) +"#, + "TypeMismatch", + ), + ( + "unsupported-result-payload-family", + r#" +(module main) + +(fn main () -> i32 + (ok string bool "value")) +"#, + "UnsupportedResultPayloadType", + ), + ( + "result-equality", + r#" +(module main) + +(fn main () -> i32 + (if (= (ok string i32 "a") (err string i32 1)) 1 0)) +"#, + "UnsupportedOptionResultEquality", + ), + ( + "result-printing", + r#" +(module main) + +(fn main () -> i32 + (std.io.print_i32 (ok string i32 "a")) + 0) +"#, + "UnsupportedOptionResultPrint", + ), + ( + "result-mapping", + r#" +(module main) + +(fn main () -> i32 + (std.result.map (ok string i32 "a")) + 0) +"#, + "UnsupportedStandardLibraryCall", + ), + ( + "promoted-name-shadow", + r#" +(module main) + +(fn std.process.arg_result ((index i32)) -> (result string i32) + (err string i32 1)) + +(fn main () -> i32 + 0) +"#, + "DuplicateFunction", + ), + ( + "helper-shadow", + r#" +(module main) + +(fn __glagol_process_arg_result ((index i32)) -> (result string i32) + (err string i32 1)) + +(fn main () -> i32 + 0) +"#, + "DuplicateFunction", + ), + ]; + + for (name, source, expected_code) in cases { + let fixture = write_fixture(name, source); + let output = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + !output.status.success(), + "compiler unexpectedly accepted `{}`\nstdout:\n{}\nstderr:\n{}", + name, + stdout, + stderr + ); + assert!( + stdout.is_empty(), + "rejected exp-10 diagnostic case `{}` wrote stdout:\n{}", + name, + stdout + ); + assert!( + stderr.contains(expected_code), + "diagnostic `{}` was not reported for `{}`\nstderr:\n{}", + expected_code, + name, + stderr + ); + } +} + +#[test] +fn hosted_runtime_executes_exp10_results_when_clang_is_available() { + let Some(clang) = find_clang() else { + eprintln!("skipping exp-10 runtime smoke: set GLAGOL_CLANG or install clang"); + return; + }; + + let root = temp_root("native"); + fs::create_dir_all(&root).unwrap_or_else(|err| panic!("create `{}`: {}", root.display(), err)); + let output = root.join("native.txt"); + let missing = root.join("missing.txt"); + let source = format!( + r#" +(module main) + +(fn write_status () -> i32 + (match (std.fs.write_text_result "{}" "native text") + ((ok code) + code) + ((err code) + code))) + +(fn main () -> i32 + (std.io.print_string (unwrap_ok (std.env.get_result "GLAGOL_EXP10_NATIVE_PRESENT"))) + (std.io.print_string (unwrap_ok (std.process.arg_result 1))) + (std.io.print_i32 (write_status)) + (std.io.print_string (unwrap_ok (std.fs.read_text_result "{}"))) + (unwrap_err (std.fs.read_text_result "{}"))) +"#, + slovo_path(&output), + slovo_path(&output), + slovo_path(&missing) + ); + let fixture = write_fixture("native", &source); + let compile = run_glagol([fixture.as_os_str()]); + assert_success("compile exp-10 native smoke", &compile); + + let run = compile_and_run_with_runtime(&clang, "exp10-native", &compile.stdout, |command| { + command + .arg("argv-native") + .env("GLAGOL_EXP10_NATIVE_PRESENT", "env-native"); + }); + + assert_eq!( + run.status.code(), + Some(1), + "exp-10 native smoke exit code drifted\nstdout:\n{}\nstderr:\n{}", + String::from_utf8_lossy(&run.stdout), + String::from_utf8_lossy(&run.stderr) + ); + assert_eq!( + String::from_utf8_lossy(&run.stdout), + "env-native\nargv-native\n0\nnative text\n", + "exp-10 native smoke stdout drifted" + ); + assert!( + run.stderr.is_empty(), + "exp-10 native smoke wrote stderr:\n{}", + String::from_utf8_lossy(&run.stderr) + ); +} + +#[test] +fn hosted_runtime_executes_exp12_stdin_result_when_clang_is_available() { + let Some(clang) = find_clang() else { + eprintln!("skipping exp-12 stdin runtime smoke: set GLAGOL_CLANG or install clang"); + return; + }; + + let source = r#" +(module main) + +(fn stdin_len_or_code () -> i32 + (match (std.io.read_stdin_result) + ((ok text) + (std.string.len text)) + ((err code) + code))) + +(fn main () -> i32 + (stdin_len_or_code)) +"#; + let fixture = write_fixture("stdin-native", source); + let compile = run_glagol([fixture.as_os_str()]); + assert_success("compile exp-12 stdin native smoke", &compile); + let ir = String::from_utf8_lossy(&compile.stdout); + assert!( + ir.contains("declare ptr @__glagol_io_read_stdin_result()") + && ir.contains("call ptr @__glagol_io_read_stdin_result()") + && !ir.contains("@std.io.read_stdin_result"), + "LLVM output did not contain expected exp-12 stdin runtime shape\nstdout:\n{}", + ir + ); + + let run = compile_and_run_with_runtime_input( + &clang, + "exp12-stdin-native", + &compile.stdout, + b"native stdin", + |_| {}, + ); + assert_eq!( + run.status.code(), + Some(12), + "exp-12 stdin native smoke exit code drifted\nstdout:\n{}\nstderr:\n{}", + String::from_utf8_lossy(&run.stdout), + String::from_utf8_lossy(&run.stderr) + ); + assert!( + run.stdout.is_empty(), + "exp-12 stdin native smoke wrote stdout:\n{}", + String::from_utf8_lossy(&run.stdout) + ); + assert!( + run.stderr.is_empty(), + "exp-12 stdin native smoke wrote stderr:\n{}", + String::from_utf8_lossy(&run.stderr) + ); + + let eof = + compile_and_run_with_runtime_input(&clang, "exp12-stdin-eof", &compile.stdout, b"", |_| {}); + assert_eq!( + eof.status.code(), + Some(0), + "exp-12 stdin EOF should be ok empty string\nstdout:\n{}\nstderr:\n{}", + String::from_utf8_lossy(&eof.stdout), + String::from_utf8_lossy(&eof.stderr) + ); +} + +#[test] +fn hosted_runtime_keeps_exp3_traps_when_clang_is_available() { + let Some(clang) = find_clang() else { + eprintln!("skipping exp-3 runtime regression: set GLAGOL_CLANG or install clang"); + return; + }; + + let source = r#" +(module main) + +(fn main () -> i32 + (std.string.len (std.process.arg 99))) +"#; + let fixture = write_fixture("exp3-native-trap", source); + let compile = run_glagol([fixture.as_os_str()]); + assert_success("compile exp-3 native trap regression", &compile); + + let run = compile_and_run_with_runtime(&clang, "exp3-native-trap", &compile.stdout, |_| {}); + assert_eq!( + run.status.code(), + Some(1), + "exp-3 native trap exit code drifted\nstdout:\n{}\nstderr:\n{}", + String::from_utf8_lossy(&run.stdout), + String::from_utf8_lossy(&run.stderr) + ); + assert_eq!( + String::from_utf8_lossy(&run.stderr), + "slovo runtime error: process argument index out of bounds\n", + "exp-3 native trap stderr drifted" + ); +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + run_glagol_configured(args, |_| {}) +} + +fn run_glagol_configured(args: I, configure: F) -> Output +where + I: IntoIterator, + S: AsRef, + F: FnOnce(&mut Command), +{ + let mut command = Command::new(env!("CARGO_BIN_EXE_glagol")); + command + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))); + configure(&mut command); + command.output().expect("run glagol") +} + +fn write_fixture(name: &str, source: &str) -> PathBuf { + let mut path = env::temp_dir(); + path.push(format!( + "glagol-exp10-host-result-{}-{}-{}.slo", + name, + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + fs::write(&path, source).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err)); + path +} + +fn temp_root(name: &str) -> PathBuf { + env::temp_dir().join(format!( + "glagol-exp10-host-result-{}-{}-{}", + name, + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )) +} + +fn slovo_path(path: &Path) -> String { + path.to_string_lossy() + .replace('\\', "\\\\") + .replace('"', "\\\"") +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} + +fn assert_success_stdout(output: Output, expected: &str, context: &str) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert_eq!(stdout, expected, "{} stdout drifted", context); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} + +fn compile_and_run_with_runtime(clang: &Path, name: &str, ir: &[u8], configure: F) -> Output +where + F: FnOnce(&mut Command), +{ + let manifest = Path::new(env!("CARGO_MANIFEST_DIR")); + let temp_dir = temp_root("clang"); + fs::create_dir_all(&temp_dir) + .unwrap_or_else(|err| panic!("create `{}`: {}", temp_dir.display(), err)); + + let ir_path = temp_dir.join(format!("{}.ll", name)); + let exe_path = temp_dir.join(name); + fs::write(&ir_path, ir).unwrap_or_else(|err| panic!("write `{}`: {}", ir_path.display(), err)); + + let runtime = manifest.join("../runtime/runtime.c"); + let mut clang_command = Command::new(clang); + clang_command + .arg(&runtime) + .arg(&ir_path) + .arg("-o") + .arg(&exe_path) + .current_dir(manifest); + configure_clang_runtime_env(&mut clang_command, clang); + let clang_output = clang_command + .output() + .unwrap_or_else(|err| panic!("run `{}`: {}", clang.display(), err)); + assert_status_success("clang exp-10 runtime smoke", &clang_output); + + let mut run = Command::new(&exe_path); + configure(&mut run); + run.output() + .unwrap_or_else(|err| panic!("run `{}`: {}", exe_path.display(), err)) +} + +fn compile_and_run_with_runtime_input( + clang: &Path, + name: &str, + ir: &[u8], + input: &[u8], + configure: F, +) -> Output +where + F: FnOnce(&mut Command), +{ + let manifest = Path::new(env!("CARGO_MANIFEST_DIR")); + let temp_dir = temp_root("clang"); + fs::create_dir_all(&temp_dir) + .unwrap_or_else(|err| panic!("create `{}`: {}", temp_dir.display(), err)); + + let ir_path = temp_dir.join(format!("{}.ll", name)); + let exe_path = temp_dir.join(name); + fs::write(&ir_path, ir).unwrap_or_else(|err| panic!("write `{}`: {}", ir_path.display(), err)); + + let runtime = manifest.join("../runtime/runtime.c"); + let mut clang_command = Command::new(clang); + clang_command + .arg(&runtime) + .arg(&ir_path) + .arg("-o") + .arg(&exe_path) + .current_dir(manifest); + configure_clang_runtime_env(&mut clang_command, clang); + let clang_output = clang_command + .output() + .unwrap_or_else(|err| panic!("run `{}`: {}", clang.display(), err)); + assert_status_success("clang exp-12 runtime smoke", &clang_output); + + let mut run = Command::new(&exe_path); + run.stdin(Stdio::piped()); + configure(&mut run); + let mut child = run + .spawn() + .unwrap_or_else(|err| panic!("run `{}`: {}", exe_path.display(), err)); + let mut stdin = child + .stdin + .take() + .unwrap_or_else(|| panic!("open stdin for `{}`", exe_path.display())); + stdin + .write_all(input) + .unwrap_or_else(|err| panic!("write stdin to `{}`: {}", exe_path.display(), err)); + drop(stdin); + child + .wait_with_output() + .unwrap_or_else(|err| panic!("wait for `{}`: {}", exe_path.display(), err)) +} + +fn find_clang() -> Option { + if let Some(path) = env::var_os("GLAGOL_CLANG").filter(|value| !value.is_empty()) { + return Some(PathBuf::from(path)); + } + + let hermetic_clang = PathBuf::from("/tmp/glagol-clang-root/usr/bin/clang"); + if hermetic_clang.is_file() { + return Some(hermetic_clang); + } + + find_on_path("clang") +} + +fn assert_status_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); +} + +fn find_on_path(program: &str) -> Option { + let path = env::var_os("PATH")?; + env::split_paths(&path) + .map(|dir| dir.join(program)) + .find(|candidate| candidate.is_file()) +} + +fn configure_clang_runtime_env(command: &mut Command, clang: &Path) { + if !clang.starts_with("/tmp/glagol-clang-root") { + return; + } + + let root = Path::new("/tmp/glagol-clang-root"); + let lib64 = root.join("usr/lib64"); + let lib = root.join("usr/lib"); + let existing = env::var_os("LD_LIBRARY_PATH").unwrap_or_default(); + let mut paths = vec![lib64, lib]; + paths.extend(env::split_paths(&existing)); + let joined = env::join_paths(paths).expect("join LD_LIBRARY_PATH"); + command.env("LD_LIBRARY_PATH", joined); +} diff --git a/compiler/tests/result_f64_bool_match_alpha.rs b/compiler/tests/result_f64_bool_match_alpha.rs new file mode 100644 index 0000000..9d7b079 --- /dev/null +++ b/compiler/tests/result_f64_bool_match_alpha.rs @@ -0,0 +1,76 @@ +use std::{ + ffi::OsStr, + path::Path, + process::{Command, Output}, +}; + +const EXPECTED_OUTPUT: &str = concat!( + "test \"result f64 ok constructor and match\" ... ok\n", + "test \"result f64 err constructor and match\" ... ok\n", + "test \"result bool ok constructor and match\" ... ok\n", + "test \"result bool err constructor and match\" ... ok\n", + "4 test(s) passed\n", +); + +#[test] +fn result_f64_bool_constructors_and_match_are_source_supported() { + let compiler_root = Path::new(env!("CARGO_MANIFEST_DIR")); + let fixture = compiler_root.join("../examples/result-f64-bool-match.slo"); + + let fmt = run_glagol([ + OsStr::new("fmt"), + OsStr::new("--check"), + fixture.as_os_str(), + ]); + assert_success("result f64 bool match fmt --check", &fmt); + + let check = run_glagol([OsStr::new("check"), fixture.as_os_str()]); + assert_success_stdout(check, "", "result f64 bool match check"); + + let test = run_glagol([OsStr::new("test"), fixture.as_os_str()]); + assert_success_stdout(test, EXPECTED_OUTPUT, "result f64 bool match test"); + + let llvm = run_glagol([OsStr::new("--emit=llvm"), fixture.as_os_str()]); + assert_success("result f64 bool match llvm", &llvm); + let stdout = String::from_utf8_lossy(&llvm.stdout); + assert!( + stdout.contains("insertvalue { i1, double, i32 }") + && stdout.contains("insertvalue { i1, i1, i32 }") + && stdout.contains("extractvalue { i1, double, i32 }") + && stdout.contains("extractvalue { i1, i1, i32 }"), + "lowering must use concrete f64/bool result aggregate shapes\n{}", + stdout + ); +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstatus: {:?}\nstdout:\n{}\nstderr:\n{}", + context, + output.status.code(), + stdout, + stderr + ); +} + +fn assert_success_stdout(output: Output, expected: &str, context: &str) { + assert_success(context, &output); + let stdout = String::from_utf8_lossy(&output.stdout); + assert_eq!(stdout, expected, "{}", context); +} diff --git a/compiler/tests/result_helpers_alpha.rs b/compiler/tests/result_helpers_alpha.rs new file mode 100644 index 0000000..18e6d25 --- /dev/null +++ b/compiler/tests/result_helpers_alpha.rs @@ -0,0 +1,233 @@ +use std::{ + ffi::OsStr, + fs, + path::{Path, PathBuf}, + process::{Command, Output}, + sync::atomic::{AtomicUsize, Ordering}, +}; + +static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0); + +#[test] +fn result_helpers_alpha_fixture_formats_lowers_and_runs() { + let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/result-helpers.slo"); + + let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]); + assert_success_stdout( + formatted, + &fs::read_to_string(&fixture).expect("read result helpers formatter fixture"), + "result helpers formatter fixture", + ); + + let surface = run_glagol([ + OsStr::new("--inspect-lowering=surface"), + fixture.as_os_str(), + ]); + assert_success("result helpers surface lowering", &surface); + assert_eq!( + String::from_utf8_lossy(&surface.stdout), + fs::read_to_string("../tests/result-helpers.surface.lower") + .expect("read result helpers surface snapshot"), + "result helpers surface lowering drifted" + ); + + let checked = run_glagol([ + OsStr::new("--inspect-lowering=checked"), + fixture.as_os_str(), + ]); + assert_success("result helpers checked lowering", &checked); + assert_eq!( + String::from_utf8_lossy(&checked.stdout), + fs::read_to_string("../tests/result-helpers.checked.lower") + .expect("read result helpers checked snapshot"), + "result helpers checked lowering drifted" + ); + + let tests = run_glagol([OsStr::new("--run-tests"), fixture.as_os_str()]); + assert_success_stdout( + tests, + concat!( + "test \"std result i32 observers\" ... ok\n", + "test \"std result i32 unwraps\" ... ok\n", + "test \"std result string observers\" ... ok\n", + "test \"std result string unwraps\" ... ok\n", + "test \"legacy result helpers still work\" ... ok\n", + "5 test(s) passed\n", + ), + "result helpers test runner output", + ); +} + +#[test] +fn result_helpers_alpha_compiles_without_new_runtime_symbols() { + let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/result-helpers.slo"); + let compile = run_glagol([fixture.as_os_str()]); + assert_success("compile result helpers fixture", &compile); + let stdout = String::from_utf8_lossy(&compile.stdout); + + assert!( + stdout.contains("define i1 @observe_i32_ok()") + && stdout.contains("define i32 @unwrap_i32_ok()") + && stdout.contains("define i1 @observe_text_ok()") + && stdout.contains("define ptr @unwrap_text_ok()") + && stdout.contains("extractvalue") + && stdout.contains("__glagol_unwrap_ok_trap") + && stdout.contains("__glagol_unwrap_err_trap") + && !stdout.contains("@std.result.is_ok") + && !stdout.contains("@std.result.is_err") + && !stdout.contains("@std.result.unwrap_ok") + && !stdout.contains("@std.result.unwrap_err"), + "LLVM output did not contain expected result helper lowering shape\nstdout:\n{}", + stdout + ); +} + +#[test] +fn result_helpers_alpha_rejects_deferred_and_misused_std_names() { + let cases = [ + ( + "map-deferred", + r#" +(module main) + +(fn main () -> i32 + (std.result.map (ok i32 i32 1)) + 0) +"#, + "UnsupportedStandardLibraryCall", + ), + ( + "unwrap-or-deferred", + r#" +(module main) + +(fn main () -> i32 + (std.result.unwrap_or (err i32 i32 1) 0)) +"#, + "UnsupportedStandardLibraryCall", + ), + ( + "and-then-deferred", + r#" +(module main) + +(fn main () -> i32 + (std.result.and_then (ok i32 i32 1)) + 0) +"#, + "UnsupportedStandardLibraryCall", + ), + ( + "observer-non-result", + r#" +(module main) + +(fn main () -> i32 + (if (std.result.is_ok 1) 1 0)) +"#, + "ResultObservationTypeMismatch", + ), + ( + "unwrap-non-result", + r#" +(module main) + +(fn main () -> i32 + (std.result.unwrap_ok 1)) +"#, + "ResultUnwrapTypeMismatch", + ), + ( + "std-result-helper-shadow", + r#" +(module main) + +(fn std.result.is_ok ((value (result i32 i32))) -> bool + true) + +(fn main () -> i32 + 0) +"#, + "DuplicateFunction", + ), + ]; + + for (name, source, expected_code) in cases { + let fixture = write_fixture(name, source); + let output = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + !output.status.success(), + "compiler unexpectedly accepted `{}`\nstdout:\n{}\nstderr:\n{}", + name, + stdout, + stderr + ); + assert!( + stdout.is_empty(), + "rejected result helper diagnostic case `{}` wrote stdout:\n{}", + name, + stdout + ); + assert!( + stderr.contains(expected_code), + "diagnostic `{}` was not reported for `{}`\nstderr:\n{}", + expected_code, + name, + stderr + ); + } +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn write_fixture(name: &str, source: &str) -> PathBuf { + let mut path = std::env::temp_dir(); + path.push(format!( + "glagol-exp15-result-helpers-{}-{}-{}.slo", + name, + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + fs::write(&path, source).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err)); + path +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} + +fn assert_success_stdout(output: Output, expected: &str, context: &str) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert_eq!(stdout, expected, "{} stdout drifted", context); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} diff --git a/compiler/tests/standard_cli_facade_source_search_alpha.rs b/compiler/tests/standard_cli_facade_source_search_alpha.rs new file mode 100644 index 0000000..2e3e880 --- /dev/null +++ b/compiler/tests/standard_cli_facade_source_search_alpha.rs @@ -0,0 +1,146 @@ +use std::{ + ffi::OsStr, + fs, + path::Path, + process::{Command, Output}, +}; + +const EXPECTED_STD_CLI_OUTPUT: &str = concat!( + "test \"explicit std cli arg text missing\" ... ok\n", + "test \"explicit std cli arg text option\" ... ok\n", + "test \"explicit std cli arg i32 missing\" ... ok\n", + "test \"explicit std cli arg i32 fallback missing\" ... ok\n", + "test \"explicit std cli arg u32 missing\" ... ok\n", + "test \"explicit std cli arg u32 fallback missing\" ... ok\n", + "test \"explicit std cli arg i64 missing\" ... ok\n", + "test \"explicit std cli arg i64 fallback missing\" ... ok\n", + "test \"explicit std cli arg u64 missing\" ... ok\n", + "test \"explicit std cli arg u64 fallback missing\" ... ok\n", + "test \"explicit std cli arg f64 missing\" ... ok\n", + "test \"explicit std cli arg f64 fallback missing\" ... ok\n", + "test \"explicit std cli arg bool missing\" ... ok\n", + "test \"explicit std cli arg bool fallback missing\" ... ok\n", + "test \"explicit std cli typed option\" ... ok\n", + "test \"explicit std cli typed custom fallback\" ... ok\n", + "test \"explicit std cli facade all\" ... ok\n", + "17 test(s) passed\n", +); + +const STANDARD_CLI_SOURCE_FACADE_ALPHA: &[&str] = &[ + "arg_text_result", + "arg_text_option", + "arg_i32_result", + "arg_i32_option", + "arg_i32_or_zero", + "arg_i32_or", + "arg_u32_result", + "arg_u32_option", + "arg_u32_or_zero", + "arg_u32_or", + "arg_i64_result", + "arg_i64_option", + "arg_i64_or_zero", + "arg_i64_or", + "arg_u64_result", + "arg_u64_option", + "arg_u64_or_zero", + "arg_u64_or", + "arg_f64_result", + "arg_f64_option", + "arg_f64_or_zero", + "arg_f64_or", + "arg_bool_result", + "arg_bool_option", + "arg_bool_or_false", + "arg_bool_or", +]; + +#[test] +fn explicit_std_cli_import_loads_transitive_standard_sources() { + let compiler_root = Path::new(env!("CARGO_MANIFEST_DIR")); + let project = compiler_root.join("../examples/projects/std-import-cli"); + let slovo_cli = compiler_root.join("../lib/std/cli.slo"); + + assert!( + !project.join("src/cli.slo").exists(), + "std-import-cli must not carry a local cli module copy" + ); + assert!( + read(&project.join("src/main.slo")).starts_with("(module main)\n\n(import std.cli ("), + "std-import-cli must exercise explicit `std.cli` import syntax" + ); + + let slovo_source = read(&slovo_cli); + assert!( + slovo_source.starts_with("(module cli (export "), + "repo-root Slovo std/cli.slo must export imported helpers directly" + ); + assert!( + slovo_source.contains("(import std.process (arg_result))") + && slovo_source + .contains("(import std.result (ok_or_none_string ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool))") + && slovo_source + .contains("(import std.string (parse_i32_result parse_u32_result parse_i64_result parse_u64_result parse_f64_result parse_bool_result))"), + "std/cli.slo must gate transitive standard-source imports" + ); + for helper in STANDARD_CLI_SOURCE_FACADE_ALPHA { + assert!( + slovo_source.contains(&format!("(fn {} ", helper)), + "Slovo std/cli.slo is missing helper `{}`", + helper + ); + } + + let fmt = run_glagol([ + OsStr::new("fmt"), + OsStr::new("--check"), + project.as_os_str(), + ]); + assert_success("std cli facade source search fmt --check", &fmt); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std cli facade source search check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + test, + EXPECTED_STD_CLI_OUTPUT, + "std cli facade source search test", + ); +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn read(path: &Path) -> String { + fs::read_to_string(path).unwrap_or_else(|err| panic!("read `{}`: {}", path.display(), err)) +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstatus: {:?}\nstdout:\n{}\nstderr:\n{}", + context, + output.status.code(), + stdout, + stderr + ); +} + +fn assert_success_stdout(output: Output, expected: &str, context: &str) { + assert_success(context, &output); + let stdout = String::from_utf8_lossy(&output.stdout); + assert_eq!(stdout, expected, "{}", context); +} diff --git a/compiler/tests/standard_cli_source_fallback_helpers_alpha.rs b/compiler/tests/standard_cli_source_fallback_helpers_alpha.rs new file mode 100644 index 0000000..7036cf0 --- /dev/null +++ b/compiler/tests/standard_cli_source_fallback_helpers_alpha.rs @@ -0,0 +1,288 @@ +use std::{ + ffi::OsStr, + fs, + path::Path, + process::{Command, Output}, +}; + +const EXPECTED_TEST_OUTPUT: &str = concat!( + "test \"explicit local cli arg text missing\" ... ok\n", + "test \"explicit local cli arg text option\" ... ok\n", + "test \"explicit local cli arg i32 missing\" ... ok\n", + "test \"explicit local cli arg i32 fallback missing\" ... ok\n", + "test \"explicit local cli arg u32 missing\" ... ok\n", + "test \"explicit local cli arg u32 fallback missing\" ... ok\n", + "test \"explicit local cli arg i64 missing\" ... ok\n", + "test \"explicit local cli arg i64 fallback missing\" ... ok\n", + "test \"explicit local cli arg u64 missing\" ... ok\n", + "test \"explicit local cli arg u64 fallback missing\" ... ok\n", + "test \"explicit local cli arg f64 missing\" ... ok\n", + "test \"explicit local cli arg f64 fallback missing\" ... ok\n", + "test \"explicit local cli arg bool missing\" ... ok\n", + "test \"explicit local cli arg bool fallback missing\" ... ok\n", + "test \"explicit local cli typed option\" ... ok\n", + "test \"explicit local cli typed custom fallback\" ... ok\n", + "test \"explicit local cli facade all\" ... ok\n", + "17 test(s) passed\n", +); + +const STANDARD_CLI_SOURCE_FACADE_ALPHA: &[&str] = &[ + "arg_text_result", + "arg_text_option", + "arg_i32_result", + "arg_i32_option", + "arg_i32_or_zero", + "arg_i32_or", + "arg_u32_result", + "arg_u32_option", + "arg_u32_or_zero", + "arg_u32_or", + "arg_i64_result", + "arg_i64_option", + "arg_i64_or_zero", + "arg_i64_or", + "arg_u64_result", + "arg_u64_option", + "arg_u64_or_zero", + "arg_u64_or", + "arg_f64_result", + "arg_f64_option", + "arg_f64_or_zero", + "arg_f64_or", + "arg_bool_result", + "arg_bool_option", + "arg_bool_or_false", + "arg_bool_or", +]; + +const LOCAL_RESULT_BRIDGE_HELPERS: &[&str] = &[ + "ok_or_none_i32", + "ok_or_none_u32", + "ok_or_none_i64", + "ok_or_none_u64", + "ok_or_none_string", + "ok_or_none_f64", + "ok_or_none_bool", +]; + +#[test] +fn standard_cli_source_fallback_helper_project_checks_formats_and_tests() { + let project = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/projects/std-layout-local-cli"); + + assert_local_cli_fixture_is_source_authored(&project); + + let fmt = run_glagol([ + OsStr::new("fmt"), + OsStr::new("--check"), + project.as_os_str(), + ]); + assert_success("std layout local cli fmt --check", &fmt); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std layout local cli check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + test, + EXPECTED_TEST_OUTPUT, + "std layout local cli test output", + ); +} + +fn assert_local_cli_fixture_is_source_authored(project: &Path) { + let cli = read(&project.join("src/cli.slo")); + let process = read(&project.join("src/process.slo")); + let result = read(&project.join("src/result.slo")); + let string = read(&project.join("src/string.slo")); + let main = read(&project.join("src/main.slo")); + + assert!( + cli.starts_with("(module cli (export "), + "cli.slo must stay an explicit local module export" + ); + assert!( + process.starts_with("(module process (export "), + "process.slo must stay an explicit local module export" + ); + assert!( + result.starts_with("(module result (export "), + "result.slo must stay an explicit local module export" + ); + assert!( + string.starts_with("(module string (export "), + "string.slo must stay an explicit local module export" + ); + assert!( + main.starts_with("(module main)\n\n(import cli ("), + "main.slo must stay an explicit local cli import" + ); + assert!( + !main.contains("(import std") && !main.contains("(import slovo.std"), + "local cli fixture must not depend on automatic or package std imports" + ); + assert!( + cli.contains("(import process (arg_result))") + && cli.contains( + "(import result (ok_or_none_string ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool))" + ) + && cli.contains( + "(import string (parse_i32_result parse_u32_result parse_i64_result parse_u64_result parse_f64_result parse_bool_result))" + ) + && !cli.contains("(import std.") + && !main.contains("std."), + "cli fixture must stay local while using local process, result, and parse helper imports" + ); + + assert!( + !cli.contains("std."), + "cli.slo must not use compiler-known std names directly" + ); + + let mut non_process_std = process.clone(); + for allowed in [ + "std.process.arg_result", + "std.process.argc", + "std.process.arg", + "std.string.parse_i32_result", + "std.string.parse_u32_result", + "std.string.parse_i64_result", + "std.string.parse_u64_result", + "std.string.parse_f64_result", + "std.string.parse_bool_result", + ] { + non_process_std = non_process_std.replace(allowed, ""); + } + assert!( + !non_process_std.contains("std."), + "local process fixture must use only the existing promoted std.process runtime names" + ); + + let mut non_string_std = string.clone(); + for allowed in [ + "std.string.len", + "std.string.concat", + "std.string.parse_i32_result", + "std.string.parse_u32_result", + "std.string.parse_i64_result", + "std.string.parse_u64_result", + "std.string.parse_f64_result", + "std.string.parse_bool_result", + ] { + non_string_std = non_string_std.replace(allowed, ""); + } + assert!( + !non_string_std.contains("std."), + "local string fixture must use only the existing promoted std.string runtime names" + ); + + let mut non_result_std = result.clone(); + for allowed in [ + "std.result.is_ok", + "std.result.is_err", + "std.result.unwrap_ok", + "std.result.unwrap_err", + ] { + non_result_std = non_result_std.replace(allowed, ""); + } + assert!( + !non_result_std.contains("std."), + "local result fixture must use only the existing promoted std.result runtime names" + ); + + assert!( + !process.contains("spawn") + && !process.contains("exit") + && !process.contains("cwd") + && !process.contains("signal") + && !process.contains("shell") + && !process.contains("flag"), + "local cli process fixture must not claim deferred process APIs" + ); + + for helper in STANDARD_CLI_SOURCE_FACADE_ALPHA { + assert!( + cli.contains(&format!("(fn {} ", helper)), + "cli.slo is missing source facade `{}`", + helper + ); + assert!( + main.contains(helper), + "main.slo does not explicitly import/use `{}`", + helper + ); + } + + for helper in LOCAL_RESULT_BRIDGE_HELPERS { + assert!( + result.contains(&format!("(fn {} ", helper)), + "result.slo is missing local result bridge helper `{}`", + helper + ); + } + + assert!( + process.contains("(fn arg_result "), + "process.slo is missing local arg_result wrapper" + ); + + for helper in [ + "parse_i32_result", + "parse_u32_result", + "parse_i64_result", + "parse_u64_result", + "parse_f64_result", + "parse_bool_result", + ] { + assert!( + string.contains(&format!("(fn {} ", helper)), + "string.slo is missing local parse helper `{}`", + helper + ); + } +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn read(path: &Path) -> String { + fs::read_to_string(path).unwrap_or_else(|err| panic!("read `{}`: {}", path.display(), err)) +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} + +fn assert_success_stdout(output: Output, expected: &str, context: &str) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert_eq!(stdout, expected, "{} stdout drifted", context); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} diff --git a/compiler/tests/standard_core_facade_source_search_alpha.rs b/compiler/tests/standard_core_facade_source_search_alpha.rs new file mode 100644 index 0000000..4467600 --- /dev/null +++ b/compiler/tests/standard_core_facade_source_search_alpha.rs @@ -0,0 +1,177 @@ +use std::{ + ffi::OsStr, + fs, + path::Path, + process::{Command, Output}, +}; + +const EXPECTED_STD_STRING_OUTPUT: &str = concat!( + "test \"explicit std string len concat\" ... ok\n", + "test \"explicit std string parse result wrappers\" ... ok\n", + "test \"explicit std string parse option wrappers\" ... ok\n", + "test \"explicit std string parse integer fallbacks\" ... ok\n", + "test \"explicit std string parse float bool fallbacks\" ... ok\n", + "test \"explicit std string parse custom fallbacks\" ... ok\n", + "test \"explicit std string helpers all\" ... ok\n", + "7 test(s) passed\n", +); + +const EXPECTED_STD_NUM_OUTPUT: &str = concat!( + "test \"explicit std num widening\" ... ok\n", + "test \"explicit std num checked conversions\" ... ok\n", + "test \"explicit std num to string\" ... ok\n", + "test \"explicit std num checked fallbacks\" ... ok\n", + "test \"explicit std num helpers all\" ... ok\n", + "5 test(s) passed\n", +); + +#[test] +fn explicit_std_string_import_loads_repo_root_standard_source() { + assert_core_facade_project( + "string", + &[ + "len", + "concat", + "parse_i32_result", + "parse_i32_option", + "parse_u32_result", + "parse_u32_option", + "parse_i64_result", + "parse_i64_option", + "parse_u64_result", + "parse_u64_option", + "parse_f64_result", + "parse_f64_option", + "parse_bool_result", + "parse_bool_option", + "parse_i32_or_zero", + "parse_u32_or_zero", + "parse_i64_or_zero", + "parse_u64_or_zero", + "parse_f64_or_zero", + "parse_bool_or_false", + "parse_i32_or", + "parse_u32_or", + "parse_i64_or", + "parse_u64_or", + "parse_f64_or", + "parse_bool_or", + ], + EXPECTED_STD_STRING_OUTPUT, + ); +} + +#[test] +fn explicit_std_num_import_loads_repo_root_standard_source() { + assert_core_facade_project( + "num", + &[ + "i32_to_i64", + "i32_to_f64", + "i64_to_f64", + "i64_to_i32_result", + "f64_to_i32_result", + "f64_to_i64_result", + "i32_to_string", + "u32_to_string", + "i64_to_string", + "u64_to_string", + "f64_to_string", + "i64_to_i32_or", + "f64_to_i32_or", + "f64_to_i64_or", + ], + EXPECTED_STD_NUM_OUTPUT, + ); +} + +fn assert_core_facade_project(module: &str, helpers: &[&str], expected: &str) { + let compiler_root = Path::new(env!("CARGO_MANIFEST_DIR")); + let project = compiler_root.join(format!("../examples/projects/std-import-{}", module)); + let slovo_module = compiler_root.join(format!("../lib/std/{}.slo", module)); + + assert!( + !project.join(format!("src/{}.slo", module)).exists(), + "std-import-{} must not carry a local {} module copy", + module, + module + ); + assert!( + read(&project.join("src/main.slo")) + .starts_with(&format!("(module main)\n\n(import std.{} (", module)), + "std-import-{} must exercise explicit `std.{}` import syntax", + module, + module + ); + + let slovo_source = read(&slovo_module); + assert!( + slovo_source.starts_with(&format!("(module {} (export ", module)), + "repo-root Slovo std/{}.slo must export imported helpers directly", + module + ); + if module == "string" { + assert!( + slovo_source + .contains("(import std.result (ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool))"), + "std/string.slo must compose option helpers through the exp-109 result bridge helpers" + ); + } + for helper in helpers { + assert!( + slovo_source.contains(&format!("(fn {} ", helper)), + "Slovo std/{}.slo is missing helper `{}`", + module, + helper + ); + } + + let fmt = run_glagol([ + OsStr::new("fmt"), + OsStr::new("--check"), + project.as_os_str(), + ]); + assert_success("std core facade source search fmt --check", &fmt); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std core facade source search check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout(test, expected, "std core facade source search test"); +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn read(path: &Path) -> String { + fs::read_to_string(path).unwrap_or_else(|err| panic!("read `{}`: {}", path.display(), err)) +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstatus: {:?}\nstdout:\n{}\nstderr:\n{}", + context, + output.status.code(), + stdout, + stderr + ); +} + +fn assert_success_stdout(output: Output, expected: &str, context: &str) { + assert_success(context, &output); + let stdout = String::from_utf8_lossy(&output.stdout); + assert_eq!(stdout, expected, "{}", context); +} diff --git a/compiler/tests/standard_env_source_facade_alpha.rs b/compiler/tests/standard_env_source_facade_alpha.rs new file mode 100644 index 0000000..b612523 --- /dev/null +++ b/compiler/tests/standard_env_source_facade_alpha.rs @@ -0,0 +1,319 @@ +use std::{ + ffi::OsStr, + fs, + path::Path, + process::{Command, Output}, +}; + +const MISSING_ENV_NAME: &str = "GLAGOL_STD_LAYOUT_LOCAL_ENV_ALPHA_UNLIKELY_MISSING"; +const PRESENT_ENV_NAME: &str = "GLAGOL_STD_LAYOUT_LOCAL_ENV_ALPHA_PRESENT"; +const PRESENT_ENV_VALUE: &str = "glagol-std-layout-local-env-alpha-value"; +const PRESENT_I32_ENV_NAME: &str = "GLAGOL_STD_LAYOUT_LOCAL_ENV_ALPHA_PRESENT_I32"; +const PRESENT_I32_ENV_VALUE: &str = "42"; +const PRESENT_U32_ENV_NAME: &str = "GLAGOL_STD_LAYOUT_LOCAL_ENV_ALPHA_PRESENT_U32"; +const PRESENT_U32_ENV_VALUE: &str = "42"; +const PRESENT_I64_ENV_NAME: &str = "GLAGOL_STD_LAYOUT_LOCAL_ENV_ALPHA_PRESENT_I64"; +const PRESENT_I64_ENV_VALUE: &str = "42000000000"; +const PRESENT_U64_ENV_NAME: &str = "GLAGOL_STD_LAYOUT_LOCAL_ENV_ALPHA_PRESENT_U64"; +const PRESENT_U64_ENV_VALUE: &str = "4294967296"; +const PRESENT_F64_ENV_NAME: &str = "GLAGOL_STD_LAYOUT_LOCAL_ENV_ALPHA_PRESENT_F64"; +const PRESENT_F64_ENV_VALUE: &str = "42.5"; +const PRESENT_BOOL_ENV_NAME: &str = "GLAGOL_STD_LAYOUT_LOCAL_ENV_ALPHA_PRESENT_BOOL"; +const PRESENT_BOOL_ENV_VALUE: &str = "true"; +const INVALID_ENV_NAME: &str = "GLAGOL_STD_LAYOUT_LOCAL_ENV_ALPHA_INVALID"; +const INVALID_ENV_VALUE: &str = "bad"; + +const EXPECTED_TEST_OUTPUT: &str = concat!( + "test \"explicit local env get missing facade\" ... ok\n", + "test \"explicit local env get result missing facade\" ... ok\n", + "test \"explicit local env has missing facade\" ... ok\n", + "test \"explicit local env get or missing facade\" ... ok\n", + "test \"explicit local env get option missing facade\" ... ok\n", + "test \"explicit local env has present facade\" ... ok\n", + "test \"explicit local env get or present facade\" ... ok\n", + "test \"explicit local env get option present facade\" ... ok\n", + "test \"explicit local env get i32 result present facade\" ... ok\n", + "test \"explicit local env get i32 or zero invalid facade\" ... ok\n", + "test \"explicit local env get u32 result present facade\" ... ok\n", + "test \"explicit local env get u32 or zero invalid facade\" ... ok\n", + "test \"explicit local env get i64 result present facade\" ... ok\n", + "test \"explicit local env get i64 or zero missing facade\" ... ok\n", + "test \"explicit local env get u64 result present facade\" ... ok\n", + "test \"explicit local env get u64 or zero missing facade\" ... ok\n", + "test \"explicit local env get f64 result present facade\" ... ok\n", + "test \"explicit local env get f64 or zero invalid facade\" ... ok\n", + "test \"explicit local env get bool result present facade\" ... ok\n", + "test \"explicit local env get bool or false invalid facade\" ... ok\n", + "test \"explicit local env typed option facade\" ... ok\n", + "test \"explicit local env typed custom fallback facade\" ... ok\n", + "test \"explicit local env facade all\" ... ok\n", + "23 test(s) passed\n", +); + +const STANDARD_ENV_SOURCE_FACADE_ALPHA: &[&str] = &[ + "get", + "get_result", + "get_option", + "has", + "get_or", + "get_i32_result", + "get_i32_option", + "get_i32_or_zero", + "get_i32_or", + "get_u32_result", + "get_u32_option", + "get_u32_or_zero", + "get_u32_or", + "get_i64_result", + "get_i64_option", + "get_i64_or_zero", + "get_i64_or", + "get_u64_result", + "get_u64_option", + "get_u64_or_zero", + "get_u64_or", + "get_f64_result", + "get_f64_option", + "get_f64_or_zero", + "get_f64_or", + "get_bool_result", + "get_bool_option", + "get_bool_or_false", + "get_bool_or", +]; + +const LOCAL_RESULT_BRIDGE_HELPERS: &[&str] = &[ + "ok_or_none_i32", + "ok_or_none_u32", + "ok_or_none_i64", + "ok_or_none_u64", + "ok_or_none_string", + "ok_or_none_f64", + "ok_or_none_bool", +]; + +const LOCAL_STRING_STD_NAMES: &[&str] = &[ + "std.string.len", + "std.string.concat", + "std.string.parse_i32_result", + "std.string.parse_u32_result", + "std.string.parse_i64_result", + "std.string.parse_u64_result", + "std.string.parse_f64_result", + "std.string.parse_bool_result", +]; + +#[test] +fn standard_env_source_facade_project_checks_formats_and_tests() { + let project = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/projects/std-layout-local-env"); + + assert_local_env_fixture_is_source_authored(&project); + + let fmt = run_glagol([ + OsStr::new("fmt"), + OsStr::new("--check"), + project.as_os_str(), + ]); + assert_success("std layout local env fmt --check", &fmt); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std layout local env check"); + + let test = run_glagol_configured([OsStr::new("test"), project.as_os_str()], |command| { + configure_local_env_contract(command); + }); + assert_success_stdout( + test, + EXPECTED_TEST_OUTPUT, + "std layout local env test output", + ); +} + +fn assert_local_env_fixture_is_source_authored(project: &Path) { + let env = read(&project.join("src/env.slo")); + let result = read(&project.join("src/result.slo")); + let string = read(&project.join("src/string.slo")); + let main = read(&project.join("src/main.slo")); + + assert!( + env.starts_with("(module env (export "), + "env.slo must stay an explicit local module export" + ); + assert!( + string.starts_with("(module string (export "), + "string.slo must stay an explicit local module export" + ); + assert!( + result.starts_with("(module result (export "), + "result.slo must stay an explicit local module export" + ); + assert!( + main.starts_with("(module main)\n\n(import env ("), + "main.slo must stay an explicit local env import" + ); + assert!( + !main.contains("(import std") && !main.contains("(import slovo.std"), + "env fixture must not depend on automatic or package std imports" + ); + assert!( + env.contains("(import result (ok_or_none_string ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool))") + && env.contains("(import string (parse_i32_result parse_u32_result parse_i64_result parse_u64_result parse_f64_result parse_bool_result))") + && env.contains("(std.env.get name)") + && env.contains("(std.env.get_result name)"), + "env.slo must stay a local source facade over env lookup plus local string and result bridge helpers" + ); + assert!( + !env.contains("std.string.") && !main.contains("std.") && !env.contains("(import std."), + "env.slo and main.slo must stay local and must not depend on repo std imports" + ); + + let mut non_string_std = string.clone(); + for allowed in LOCAL_STRING_STD_NAMES { + non_string_std = non_string_std.replace(allowed, ""); + } + assert!( + !non_string_std.contains("std."), + "local string fixture must use only the promoted std.string runtime names" + ); + + let mut non_result_std = result.clone(); + for allowed in [ + "std.result.is_ok", + "std.result.is_err", + "std.result.unwrap_ok", + "std.result.unwrap_err", + ] { + non_result_std = non_result_std.replace(allowed, ""); + } + assert!( + !non_result_std.contains("std."), + "local result fixture must use only the promoted std.result runtime names" + ); + assert!( + !env.contains("set") + && !env.contains("unset") + && !env.contains("enumer") + && !env.contains("platform") + && !env.contains("host_error") + && !env.contains("errno"), + "env fixture must not claim deferred mutation, enumeration, or rich host error APIs" + ); + assert!( + main.contains(MISSING_ENV_NAME) + && main.contains(PRESENT_ENV_NAME) + && main.contains(PRESENT_ENV_VALUE) + && main.contains(PRESENT_I32_ENV_NAME) + && main.contains(PRESENT_U32_ENV_NAME) + && main.contains(PRESENT_I64_ENV_NAME) + && main.contains(PRESENT_U64_ENV_NAME) + && main.contains(PRESENT_F64_ENV_NAME) + && main.contains(PRESENT_BOOL_ENV_NAME) + && main.contains(INVALID_ENV_NAME), + "main.slo must use deterministic missing, present, and invalid env names" + ); + + for helper in STANDARD_ENV_SOURCE_FACADE_ALPHA { + assert!( + env.contains(&format!("(fn {} ", helper)), + "env.slo is missing source facade `{}`", + helper + ); + assert!( + main.contains(helper), + "main.slo does not explicitly import/use `{}`", + helper + ); + } + + for helper in LOCAL_RESULT_BRIDGE_HELPERS { + assert!( + result.contains(&format!("(fn {} ", helper)), + "result.slo is missing local result bridge helper `{}`", + helper + ); + } + + for helper in [ + "parse_i32_result", + "parse_u32_result", + "parse_i64_result", + "parse_u64_result", + "parse_f64_result", + "parse_bool_result", + ] { + assert!( + string.contains(&format!("(fn {} ", helper)), + "string.slo is missing local parse helper `{}`", + helper + ); + } +} + +fn configure_local_env_contract(command: &mut Command) { + command + .env_remove(MISSING_ENV_NAME) + .env(PRESENT_ENV_NAME, PRESENT_ENV_VALUE) + .env(PRESENT_I32_ENV_NAME, PRESENT_I32_ENV_VALUE) + .env(PRESENT_U32_ENV_NAME, PRESENT_U32_ENV_VALUE) + .env(PRESENT_I64_ENV_NAME, PRESENT_I64_ENV_VALUE) + .env(PRESENT_U64_ENV_NAME, PRESENT_U64_ENV_VALUE) + .env(PRESENT_F64_ENV_NAME, PRESENT_F64_ENV_VALUE) + .env(PRESENT_BOOL_ENV_NAME, PRESENT_BOOL_ENV_VALUE) + .env(INVALID_ENV_NAME, INVALID_ENV_VALUE); +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + run_glagol_configured(args, |_| {}) +} + +fn run_glagol_configured(args: I, configure: F) -> Output +where + I: IntoIterator, + S: AsRef, + F: FnOnce(&mut Command), +{ + let mut command = Command::new(env!("CARGO_BIN_EXE_glagol")); + command + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))); + configure(&mut command); + command.output().expect("run glagol") +} + +fn read(path: &Path) -> String { + fs::read_to_string(path).unwrap_or_else(|err| panic!("read `{}`: {}", path.display(), err)) +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} + +fn assert_success_stdout(output: Output, expected: &str, context: &str) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert_eq!(stdout, expected, "{} stdout drifted", context); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} diff --git a/compiler/tests/standard_fs_source_facade_alpha.rs b/compiler/tests/standard_fs_source_facade_alpha.rs new file mode 100644 index 0000000..04db2b5 --- /dev/null +++ b/compiler/tests/standard_fs_source_facade_alpha.rs @@ -0,0 +1,313 @@ +use std::{ + env, + ffi::OsStr, + fs, + path::{Path, PathBuf}, + process::{Command, Output}, + sync::atomic::{AtomicUsize, Ordering}, +}; + +static NEXT_TEMP_ID: AtomicUsize = AtomicUsize::new(0); + +const EXPECTED_TEST_OUTPUT: &str = concat!( + "test \"explicit local fs write text status facade\" ... ok\n", + "test \"explicit local fs read text facade\" ... ok\n", + "test \"explicit local fs write text result facade\" ... ok\n", + "test \"explicit local fs read text result facade\" ... ok\n", + "test \"explicit local fs read text option missing facade\" ... ok\n", + "test \"explicit local fs read text option present facade\" ... ok\n", + "test \"explicit local fs read text or missing facade\" ... ok\n", + "test \"explicit local fs read text or present facade\" ... ok\n", + "test \"explicit local fs write text ok facade\" ... ok\n", + "test \"explicit local fs read i32 result present facade\" ... ok\n", + "test \"explicit local fs read i32 or zero invalid facade\" ... ok\n", + "test \"explicit local fs read u32 result present facade\" ... ok\n", + "test \"explicit local fs read u32 or zero invalid facade\" ... ok\n", + "test \"explicit local fs read i64 result present facade\" ... ok\n", + "test \"explicit local fs read i64 or zero missing facade\" ... ok\n", + "test \"explicit local fs read u64 result present facade\" ... ok\n", + "test \"explicit local fs read u64 or zero missing facade\" ... ok\n", + "test \"explicit local fs read f64 result present facade\" ... ok\n", + "test \"explicit local fs read f64 or zero invalid facade\" ... ok\n", + "test \"explicit local fs read bool result present facade\" ... ok\n", + "test \"explicit local fs read bool or false invalid facade\" ... ok\n", + "test \"explicit local fs typed option facade\" ... ok\n", + "test \"explicit local fs typed custom fallback facade\" ... ok\n", + "test \"explicit local fs facade all\" ... ok\n", + "24 test(s) passed\n", +); + +const STANDARD_FS_SOURCE_FACADE_ALPHA: &[&str] = &[ + "read_text", + "read_text_result", + "read_text_option", + "write_text_status", + "write_text_result", + "read_text_or", + "write_text_ok", + "read_i32_result", + "read_i32_option", + "read_i32_or_zero", + "read_i32_or", + "read_u32_result", + "read_u32_option", + "read_u32_or_zero", + "read_u32_or", + "read_i64_result", + "read_i64_option", + "read_i64_or_zero", + "read_i64_or", + "read_u64_result", + "read_u64_option", + "read_u64_or_zero", + "read_u64_or", + "read_f64_result", + "read_f64_option", + "read_f64_or_zero", + "read_f64_or", + "read_bool_result", + "read_bool_option", + "read_bool_or_false", + "read_bool_or", +]; + +const LOCAL_RESULT_BRIDGE_HELPERS: &[&str] = &[ + "ok_or_none_i32", + "ok_or_none_u32", + "ok_or_none_i64", + "ok_or_none_u64", + "ok_or_none_string", + "ok_or_none_f64", + "ok_or_none_bool", +]; + +#[test] +fn standard_fs_source_facade_project_checks_formats_and_tests() { + let project = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/projects/std-layout-local-fs"); + + assert_local_fs_fixture_is_source_authored(&project); + + let fmt = run_glagol([ + OsStr::new("fmt"), + OsStr::new("--check"), + project.as_os_str(), + ]); + assert_success("std layout local fs fmt --check", &fmt); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std layout local fs check"); + + let test_cwd = temp_root("test"); + let test = run_glagol_in([OsStr::new("test"), project.as_os_str()], &test_cwd); + assert_success_stdout( + test, + EXPECTED_TEST_OUTPUT, + "std layout local fs test output", + ); +} + +fn assert_local_fs_fixture_is_source_authored(project: &Path) { + let fs_source = read(&project.join("src/fs.slo")); + let result_source = read(&project.join("src/result.slo")); + let string_source = read(&project.join("src/string.slo")); + let main = read(&project.join("src/main.slo")); + + assert!( + fs_source.starts_with("(module fs (export "), + "fs.slo must stay an explicit local module export" + ); + assert!( + string_source.starts_with("(module string (export "), + "string.slo must stay an explicit local module export" + ); + assert!( + result_source.starts_with("(module result (export "), + "result.slo must stay an explicit local module export" + ); + assert!( + main.starts_with("(module main)\n\n(import fs ("), + "main.slo must stay an explicit local fs import" + ); + assert!( + !main.contains("(import std") && !main.contains("(import slovo.std"), + "fs fixture must not depend on automatic or package std imports" + ); + assert!( + main.contains("\"glagol-std-layout-local-fs-alpha.txt\"") + && main.contains("\"std fs source search alpha\"") + && main.contains("\"glagol-std-layout-local-fs-alpha-missing.txt\"") + && main.contains("\"std fs source fallback alpha\"") + && main.contains("\"glagol-std-layout-local-fs-alpha-i32.txt\"") + && main.contains("\"glagol-std-layout-local-fs-alpha-u32.txt\"") + && main.contains("\"glagol-std-layout-local-fs-alpha-i64.txt\"") + && main.contains("\"glagol-std-layout-local-fs-alpha-u64.txt\"") + && main.contains("\"glagol-std-layout-local-fs-alpha-f64.txt\"") + && main.contains("\"glagol-std-layout-local-fs-alpha-bool.txt\"") + && main.contains("\"glagol-std-layout-local-fs-alpha-invalid.txt\""), + "fs fixture must use deterministic relative paths and text content" + ); + assert!( + fs_source.contains("(import result (ok_or_none_string ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool))") + && fs_source.contains("(import string (parse_i32_result parse_u32_result parse_i64_result parse_u64_result parse_f64_result parse_bool_result))") + && fs_source.contains("(std.fs.read_text path)") + && fs_source.contains("(std.fs.read_text_result path)") + && fs_source.contains("(std.fs.write_text path text)") + && fs_source.contains("(std.fs.write_text_result path text)"), + "fs.slo must stay a local source facade over fs calls plus local string and result bridge helpers" + ); + assert!( + !fs_source.contains("std.string.") + && !main.contains("std.") + && !fs_source.contains("(import std."), + "fs fixture must stay local and must not depend on repo std imports" + ); + + let mut non_string_std = string_source.clone(); + for allowed in [ + "std.string.len", + "std.string.concat", + "std.string.parse_i32_result", + "std.string.parse_u32_result", + "std.string.parse_i64_result", + "std.string.parse_u64_result", + "std.string.parse_f64_result", + "std.string.parse_bool_result", + ] { + non_string_std = non_string_std.replace(allowed, ""); + } + assert!( + !non_string_std.contains("std."), + "local string fixture must use only the promoted std.string runtime names" + ); + + let mut non_result_std = result_source.clone(); + for allowed in [ + "std.result.is_ok", + "std.result.is_err", + "std.result.unwrap_ok", + "std.result.unwrap_err", + ] { + non_result_std = non_result_std.replace(allowed, ""); + } + assert!( + !non_result_std.contains("std."), + "local result fixture must use only the promoted std.result runtime names" + ); + assert!( + !fs_source.contains("binary") + && !fs_source.contains("list_dir") + && !fs_source.contains("walk") + && !fs_source.contains("stream") + && !fs_source.contains("async") + && !fs_source.contains("host_error") + && !main.contains("binary") + && !main.contains("list_dir") + && !main.contains("walk") + && !main.contains("stream") + && !main.contains("async") + && !main.contains("host_error"), + "fs fixture must not claim deferred fs APIs or richer host error semantics" + ); + + for helper in STANDARD_FS_SOURCE_FACADE_ALPHA { + assert!( + fs_source.contains(&format!("(fn {} ", helper)), + "fs.slo is missing source facade `{}`", + helper + ); + assert!( + main.contains(helper), + "main.slo does not explicitly import/use `{}`", + helper + ); + } + + for helper in LOCAL_RESULT_BRIDGE_HELPERS { + assert!( + result_source.contains(&format!("(fn {} ", helper)), + "result.slo is missing local result bridge helper `{}`", + helper + ); + } + + for helper in [ + "parse_i32_result", + "parse_u32_result", + "parse_i64_result", + "parse_u64_result", + "parse_f64_result", + "parse_bool_result", + ] { + assert!( + string_source.contains(&format!("(fn {} ", helper)), + "string.slo is missing local parse helper `{}`", + helper + ); + } +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + run_glagol_in(args, Path::new(env!("CARGO_MANIFEST_DIR"))) +} + +fn run_glagol_in(args: I, cwd: &Path) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(cwd) + .output() + .expect("run glagol") +} + +fn temp_root(name: &str) -> PathBuf { + let path = env::temp_dir().join(format!( + "glagol-standard-fs-source-facade-{}-{}-{}", + name, + std::process::id(), + NEXT_TEMP_ID.fetch_add(1, Ordering::Relaxed) + )); + let _ = fs::remove_dir_all(&path); + fs::create_dir_all(&path).unwrap_or_else(|err| panic!("create `{}`: {}", path.display(), err)); + path +} + +fn read(path: &Path) -> String { + fs::read_to_string(path).unwrap_or_else(|err| panic!("read `{}`: {}", path.display(), err)) +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} + +fn assert_success_stdout(output: Output, expected: &str, context: &str) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert_eq!(stdout, expected, "{} stdout drifted", context); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} diff --git a/compiler/tests/standard_host_facade_source_search_alpha.rs b/compiler/tests/standard_host_facade_source_search_alpha.rs new file mode 100644 index 0000000..03cda9a --- /dev/null +++ b/compiler/tests/standard_host_facade_source_search_alpha.rs @@ -0,0 +1,405 @@ +use std::{ + env, + ffi::OsStr, + fs, + path::{Path, PathBuf}, + process::{Command, Output}, + sync::atomic::{AtomicUsize, Ordering}, +}; + +static NEXT_TEMP_ID: AtomicUsize = AtomicUsize::new(0); + +const MISSING_ENV_NAME: &str = "GLAGOL_STD_IMPORT_ENV_ALPHA_UNLIKELY_MISSING"; +const PRESENT_ENV_NAME: &str = "GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT"; +const PRESENT_ENV_VALUE: &str = "glagol-std-import-env-alpha-value"; +const PRESENT_I32_ENV_NAME: &str = "GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT_I32"; +const PRESENT_I32_ENV_VALUE: &str = "42"; +const PRESENT_U32_ENV_NAME: &str = "GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT_U32"; +const PRESENT_U32_ENV_VALUE: &str = "42"; +const PRESENT_I64_ENV_NAME: &str = "GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT_I64"; +const PRESENT_I64_ENV_VALUE: &str = "42000000000"; +const PRESENT_U64_ENV_NAME: &str = "GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT_U64"; +const PRESENT_U64_ENV_VALUE: &str = "4294967296"; +const PRESENT_F64_ENV_NAME: &str = "GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT_F64"; +const PRESENT_F64_ENV_VALUE: &str = "42.5"; +const PRESENT_BOOL_ENV_NAME: &str = "GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT_BOOL"; +const PRESENT_BOOL_ENV_VALUE: &str = "true"; +const INVALID_ENV_NAME: &str = "GLAGOL_STD_IMPORT_ENV_ALPHA_INVALID"; +const INVALID_ENV_VALUE: &str = "bad"; + +const EXPECTED_STD_TIME_OUTPUT: &str = concat!( + "test \"explicit std time monotonic facade\" ... ok\n", + "test \"explicit std time sleep zero facade\" ... ok\n", + "test \"explicit std time facade all\" ... ok\n", + "3 test(s) passed\n", +); + +const EXPECTED_STD_RANDOM_OUTPUT: &str = concat!( + "test \"explicit std random i32 non-negative facade\" ... ok\n", + "test \"explicit std random facade all\" ... ok\n", + "2 test(s) passed\n", +); + +const EXPECTED_STD_ENV_OUTPUT: &str = concat!( + "test \"explicit std env get missing facade\" ... ok\n", + "test \"explicit std env get result missing facade\" ... ok\n", + "test \"explicit std env has missing facade\" ... ok\n", + "test \"explicit std env get or missing facade\" ... ok\n", + "test \"explicit std env get option missing facade\" ... ok\n", + "test \"explicit std env has present facade\" ... ok\n", + "test \"explicit std env get or present facade\" ... ok\n", + "test \"explicit std env get option present facade\" ... ok\n", + "test \"explicit std env get i32 result present facade\" ... ok\n", + "test \"explicit std env get i32 or zero invalid facade\" ... ok\n", + "test \"explicit std env get u32 result present facade\" ... ok\n", + "test \"explicit std env get u32 or zero invalid facade\" ... ok\n", + "test \"explicit std env get i64 result present facade\" ... ok\n", + "test \"explicit std env get i64 or zero missing facade\" ... ok\n", + "test \"explicit std env get u64 result present facade\" ... ok\n", + "test \"explicit std env get u64 or zero missing facade\" ... ok\n", + "test \"explicit std env get f64 result present facade\" ... ok\n", + "test \"explicit std env get f64 or zero invalid facade\" ... ok\n", + "test \"explicit std env get bool result present facade\" ... ok\n", + "test \"explicit std env get bool or false invalid facade\" ... ok\n", + "test \"explicit std env typed option facade\" ... ok\n", + "test \"explicit std env typed custom fallback facade\" ... ok\n", + "test \"explicit std env facade all\" ... ok\n", + "23 test(s) passed\n", +); + +const EXPECTED_STD_FS_OUTPUT: &str = concat!( + "test \"explicit std fs write text status facade\" ... ok\n", + "test \"explicit std fs read text facade\" ... ok\n", + "test \"explicit std fs write text result facade\" ... ok\n", + "test \"explicit std fs read text result facade\" ... ok\n", + "test \"explicit std fs read text option missing facade\" ... ok\n", + "test \"explicit std fs read text option present facade\" ... ok\n", + "test \"explicit std fs read text or missing facade\" ... ok\n", + "test \"explicit std fs read text or present facade\" ... ok\n", + "test \"explicit std fs write text ok facade\" ... ok\n", + "test \"explicit std fs read i32 result present facade\" ... ok\n", + "test \"explicit std fs read i32 or zero invalid facade\" ... ok\n", + "test \"explicit std fs read u32 result present facade\" ... ok\n", + "test \"explicit std fs read u32 or zero invalid facade\" ... ok\n", + "test \"explicit std fs read i64 result present facade\" ... ok\n", + "test \"explicit std fs read i64 or zero missing facade\" ... ok\n", + "test \"explicit std fs read u64 result present facade\" ... ok\n", + "test \"explicit std fs read u64 or zero missing facade\" ... ok\n", + "test \"explicit std fs read f64 result present facade\" ... ok\n", + "test \"explicit std fs read f64 or zero invalid facade\" ... ok\n", + "test \"explicit std fs read bool result present facade\" ... ok\n", + "test \"explicit std fs read bool or false invalid facade\" ... ok\n", + "test \"explicit std fs typed option facade\" ... ok\n", + "test \"explicit std fs typed custom fallback facade\" ... ok\n", + "test \"explicit std fs facade all\" ... ok\n", + "24 test(s) passed\n", +); + +const EXPECTED_STD_PROCESS_OUTPUT: &str = concat!( + "test \"explicit std process argc facade\" ... ok\n", + "test \"explicit std process arg result oob facade\" ... ok\n", + "test \"explicit std process arg option facade\" ... ok\n", + "test \"explicit std process has arg facade\" ... ok\n", + "test \"explicit std process arg fallback missing facade\" ... ok\n", + "test \"explicit std process arg fallback present facade\" ... ok\n", + "test \"explicit std process arg i32 missing facade\" ... ok\n", + "test \"explicit std process arg i32 invalid fallback facade\" ... ok\n", + "test \"explicit std process arg u32 missing facade\" ... ok\n", + "test \"explicit std process arg u32 invalid fallback facade\" ... ok\n", + "test \"explicit std process arg i64 missing facade\" ... ok\n", + "test \"explicit std process arg i64 invalid fallback facade\" ... ok\n", + "test \"explicit std process arg u64 missing facade\" ... ok\n", + "test \"explicit std process arg u64 invalid fallback facade\" ... ok\n", + "test \"explicit std process arg f64 missing facade\" ... ok\n", + "test \"explicit std process arg f64 invalid fallback facade\" ... ok\n", + "test \"explicit std process arg bool missing facade\" ... ok\n", + "test \"explicit std process arg bool invalid fallback facade\" ... ok\n", + "test \"explicit std process typed option facade\" ... ok\n", + "test \"explicit std process typed custom fallback facade\" ... ok\n", + "test \"explicit std process facade all\" ... ok\n", + "21 test(s) passed\n", +); + +#[test] +fn explicit_std_time_import_loads_repo_root_standard_source() { + assert_host_facade_project( + "time", + &["monotonic_ms", "sleep_ms_zero"], + EXPECTED_STD_TIME_OUTPUT, + |_| {}, + None, + ); +} + +#[test] +fn explicit_std_random_import_loads_repo_root_standard_source() { + assert_host_facade_project( + "random", + &["random_i32", "random_i32_non_negative"], + EXPECTED_STD_RANDOM_OUTPUT, + |_| {}, + None, + ); +} + +#[test] +fn explicit_std_env_import_loads_repo_root_standard_source() { + assert_host_facade_project( + "env", + &[ + "get", + "get_result", + "get_option", + "has", + "get_or", + "get_i32_result", + "get_i32_option", + "get_i32_or_zero", + "get_i32_or", + "get_u32_result", + "get_u32_option", + "get_u32_or_zero", + "get_u32_or", + "get_i64_result", + "get_i64_option", + "get_i64_or_zero", + "get_i64_or", + "get_u64_result", + "get_u64_option", + "get_u64_or_zero", + "get_u64_or", + "get_f64_result", + "get_f64_option", + "get_f64_or_zero", + "get_f64_or", + "get_bool_result", + "get_bool_option", + "get_bool_or_false", + "get_bool_or", + ], + EXPECTED_STD_ENV_OUTPUT, + |command| { + command.env_remove(MISSING_ENV_NAME); + command.env(PRESENT_ENV_NAME, PRESENT_ENV_VALUE); + command.env(PRESENT_I32_ENV_NAME, PRESENT_I32_ENV_VALUE); + command.env(PRESENT_U32_ENV_NAME, PRESENT_U32_ENV_VALUE); + command.env(PRESENT_I64_ENV_NAME, PRESENT_I64_ENV_VALUE); + command.env(PRESENT_U64_ENV_NAME, PRESENT_U64_ENV_VALUE); + command.env(PRESENT_F64_ENV_NAME, PRESENT_F64_ENV_VALUE); + command.env(PRESENT_BOOL_ENV_NAME, PRESENT_BOOL_ENV_VALUE); + command.env(INVALID_ENV_NAME, INVALID_ENV_VALUE); + }, + None, + ); +} + +#[test] +fn explicit_std_fs_import_loads_repo_root_standard_source() { + let cwd = temp_root("fs"); + assert_host_facade_project( + "fs", + &[ + "read_text", + "read_text_result", + "read_text_option", + "write_text_status", + "write_text_result", + "read_text_or", + "write_text_ok", + "read_i32_result", + "read_i32_option", + "read_i32_or_zero", + "read_i32_or", + "read_u32_result", + "read_u32_option", + "read_u32_or_zero", + "read_u32_or", + "read_i64_result", + "read_i64_option", + "read_i64_or_zero", + "read_i64_or", + "read_u64_result", + "read_u64_option", + "read_u64_or_zero", + "read_u64_or", + "read_f64_result", + "read_f64_option", + "read_f64_or_zero", + "read_f64_or", + "read_bool_result", + "read_bool_option", + "read_bool_or_false", + "read_bool_or", + ], + EXPECTED_STD_FS_OUTPUT, + |_| {}, + Some(&cwd), + ); +} + +#[test] +fn explicit_std_process_import_loads_repo_root_standard_source() { + assert_host_facade_project( + "process", + &[ + "argc", + "arg", + "arg_result", + "arg_option", + "has_arg", + "arg_or", + "arg_or_empty", + "arg_i32_result", + "arg_i32_option", + "arg_i32_or_zero", + "arg_i32_or", + "arg_u32_result", + "arg_u32_option", + "arg_u32_or_zero", + "arg_u32_or", + "arg_i64_result", + "arg_i64_option", + "arg_i64_or_zero", + "arg_i64_or", + "arg_u64_result", + "arg_u64_option", + "arg_u64_or_zero", + "arg_u64_or", + "arg_f64_result", + "arg_f64_option", + "arg_f64_or_zero", + "arg_f64_or", + "arg_bool_result", + "arg_bool_option", + "arg_bool_or_false", + "arg_bool_or", + ], + EXPECTED_STD_PROCESS_OUTPUT, + |_| {}, + None, + ); +} + +fn assert_host_facade_project( + module: &str, + helpers: &[&str], + expected: &str, + configure: F, + test_cwd: Option<&Path>, +) where + F: Fn(&mut Command), +{ + let compiler_root = Path::new(env!("CARGO_MANIFEST_DIR")); + let project = compiler_root.join(format!("../examples/projects/std-import-{}", module)); + let slovo_module = compiler_root.join(format!("../lib/std/{}.slo", module)); + + assert!( + !project.join(format!("src/{}.slo", module)).exists(), + "std-import-{} must not carry a local {} module copy", + module, + module + ); + assert!( + read(&project.join("src/main.slo")) + .starts_with(&format!("(module main)\n\n(import std.{} (", module)), + "std-import-{} must exercise explicit `std.{}` import syntax", + module, + module + ); + + let slovo_source = read(&slovo_module); + assert!( + slovo_source.starts_with(&format!("(module {} (export ", module)), + "repo-root Slovo std/{}.slo must export imported helpers directly", + module + ); + if matches!(module, "env" | "fs" | "process") { + assert!( + slovo_source + .contains("(import std.result (ok_or_none_string ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool))"), + "Slovo std/{}.slo must compose option helpers through the exp-109 result bridge helpers", + module + ); + } + for helper in helpers { + assert!( + slovo_source.contains(&format!("(fn {} ", helper)), + "Slovo std/{}.slo is missing helper `{}`", + module, + helper + ); + } + + let fmt = run_glagol_configured( + [ + OsStr::new("fmt"), + OsStr::new("--check"), + project.as_os_str(), + ], + Path::new(env!("CARGO_MANIFEST_DIR")), + |_| {}, + ); + assert_success("std host facade source search fmt --check", &fmt); + + let check = run_glagol_configured( + [OsStr::new("check"), project.as_os_str()], + Path::new(env!("CARGO_MANIFEST_DIR")), + |_| {}, + ); + assert_success_stdout(check, "", "std host facade source search check"); + + let test = run_glagol_configured( + [OsStr::new("test"), project.as_os_str()], + test_cwd.unwrap_or(Path::new(env!("CARGO_MANIFEST_DIR"))), + configure, + ); + assert_success_stdout(test, expected, "std host facade source search test"); +} + +fn run_glagol_configured(args: I, cwd: &Path, configure: F) -> Output +where + I: IntoIterator, + S: AsRef, + F: FnOnce(&mut Command), +{ + let mut command = Command::new(env!("CARGO_BIN_EXE_glagol")); + command.args(args).current_dir(cwd); + configure(&mut command); + command.output().expect("run glagol") +} + +fn temp_root(name: &str) -> PathBuf { + let path = env::temp_dir().join(format!( + "glagol-standard-host-facade-source-search-{}-{}-{}", + name, + std::process::id(), + NEXT_TEMP_ID.fetch_add(1, Ordering::Relaxed) + )); + let _ = fs::remove_dir_all(&path); + fs::create_dir_all(&path).unwrap_or_else(|err| panic!("create `{}`: {}", path.display(), err)); + path +} + +fn read(path: &Path) -> String { + fs::read_to_string(path).unwrap_or_else(|err| panic!("read `{}`: {}", path.display(), err)) +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstatus: {:?}\nstdout:\n{}\nstderr:\n{}", + context, + output.status.code(), + stdout, + stderr + ); +} + +fn assert_success_stdout(output: Output, expected: &str, context: &str) { + assert_success(context, &output); + let stdout = String::from_utf8_lossy(&output.stdout); + assert_eq!(stdout, expected, "{}", context); +} diff --git a/compiler/tests/standard_installed_stdlib_discovery_alpha.rs b/compiler/tests/standard_installed_stdlib_discovery_alpha.rs new file mode 100644 index 0000000..5d60fcd --- /dev/null +++ b/compiler/tests/standard_installed_stdlib_discovery_alpha.rs @@ -0,0 +1,143 @@ +use std::{ + env, + ffi::OsStr, + fs, + path::Path, + process::{Command, Output}, + sync::atomic::{AtomicUsize, Ordering}, +}; + +static NEXT_TEMP_ID: AtomicUsize = AtomicUsize::new(0); + +const EXPECTED_OUTPUT: &str = + "test \"installed stdlib source discovery\" ... ok\n1 test(s) passed\n"; + +#[test] +fn copied_binary_discovers_installed_share_slovo_std() { + let root = temp_root(); + let bin_dir = root.join("bin"); + let std_dir = root.join("share/slovo/std"); + let project = root.join("project"); + let source_dir = project.join("src"); + let installed_glagol = bin_dir.join("glagol"); + + create_dir(&bin_dir); + create_dir(&std_dir); + create_dir(&source_dir); + + fs::copy(env!("CARGO_BIN_EXE_glagol"), &installed_glagol).unwrap_or_else(|err| { + panic!( + "copy glagol binary to `{}` failed: {}", + installed_glagol.display(), + err + ) + }); + make_executable(&installed_glagol); + + write( + &std_dir.join("installed_probe.slo"), + "(module installed_probe (export value))\n\n(fn value () -> i32\n 42)\n", + ); + write( + &project.join("slovo.toml"), + concat!( + "[project]\n", + "name = \"installed-stdlib-discovery-alpha\"\n", + "source_root = \"src\"\n", + "entry = \"main\"\n", + ), + ); + write( + &source_dir.join("main.slo"), + concat!( + "(module main)\n\n", + "(import std.installed_probe (value))\n\n", + "(fn main () -> i32\n", + " (value))\n\n", + "(test \"installed stdlib source discovery\"\n", + " (= (value) 42))\n", + ), + ); + + assert!( + !project.join("src/installed_probe.slo").exists(), + "fixture must not carry a local installed_probe module copy" + ); + + let check = run_installed_glagol( + &installed_glagol, + [OsStr::new("check"), project.as_os_str()], + &root, + ); + assert_success_stdout(check, "", "installed stdlib discovery check"); + + let test = run_installed_glagol( + &installed_glagol, + [OsStr::new("test"), project.as_os_str()], + &root, + ); + assert_success_stdout(test, EXPECTED_OUTPUT, "installed stdlib discovery test"); +} + +fn run_installed_glagol(binary: &Path, args: I, cwd: &Path) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(binary) + .args(args) + .current_dir(cwd) + .env_remove("SLOVO_STD_PATH") + .output() + .expect("run installed glagol") +} + +fn write(path: &Path, content: &str) { + fs::write(path, content).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err)); +} + +fn create_dir(path: &Path) { + fs::create_dir_all(path).unwrap_or_else(|err| panic!("create `{}`: {}", path.display(), err)); +} + +#[cfg(unix)] +fn make_executable(path: &Path) { + use std::os::unix::fs::PermissionsExt; + + let mut permissions = fs::metadata(path) + .unwrap_or_else(|err| panic!("metadata `{}`: {}", path.display(), err)) + .permissions(); + permissions.set_mode(permissions.mode() | 0o755); + fs::set_permissions(path, permissions) + .unwrap_or_else(|err| panic!("chmod `{}`: {}", path.display(), err)); +} + +#[cfg(not(unix))] +fn make_executable(_path: &Path) {} + +fn temp_root() -> std::path::PathBuf { + let path = env::temp_dir().join(format!( + "glagol-installed-stdlib-discovery-alpha-{}-{}", + std::process::id(), + NEXT_TEMP_ID.fetch_add(1, Ordering::Relaxed) + )); + let _ = fs::remove_dir_all(&path); + fs::create_dir_all(&path).unwrap_or_else(|err| panic!("create `{}`: {}", path.display(), err)); + path +} + +fn assert_success_stdout(output: Output, expected: &str, context: &str) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstatus: {:?}\nstdout:\n{}\nstderr:\n{}", + context, + output.status.code(), + stdout, + stderr + ); + assert_eq!(stdout, expected, "{}", context); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} diff --git a/compiler/tests/standard_io_facade_source_search_alpha.rs b/compiler/tests/standard_io_facade_source_search_alpha.rs new file mode 100644 index 0000000..3397537 --- /dev/null +++ b/compiler/tests/standard_io_facade_source_search_alpha.rs @@ -0,0 +1,156 @@ +use std::{ + ffi::OsStr, + fs, + path::Path, + process::{Command, Output}, +}; + +const EXPECTED_STD_IO_OUTPUT: &str = concat!( + "test \"explicit std io i64 zero facade\" ... ok\n", + "test \"explicit std io u32 zero facade\" ... ok\n", + "test \"explicit std io u64 zero facade\" ... ok\n", + "test \"explicit std io f64 zero facade\" ... ok\n", + "test \"explicit std io value facade\" ... ok\n", + "test \"explicit std io stdin result facade\" ... ok\n", + "test \"explicit std io stdin option facade\" ... ok\n", + "test \"explicit std io stdin text fallback facade\" ... ok\n", + "test \"explicit std io stdin typed result facade\" ... ok\n", + "test \"explicit std io stdin typed option facade\" ... ok\n", + "test \"explicit std io stdin typed fallback facade\" ... ok\n", + "test \"explicit std io helpers all\" ... ok\n", + "12 test(s) passed\n", +); + +const STANDARD_IO_SOURCE_FACADE_ALPHA: &[&str] = &[ + "print_i32_zero", + "print_i64_zero", + "print_u32_zero", + "print_u64_zero", + "print_f64_zero", + "print_string_zero", + "print_bool_zero", + "print_i32_value", + "print_i64_value", + "print_u32_value", + "print_u64_value", + "print_f64_value", + "print_string_value", + "print_bool_value", + "read_stdin_result", + "read_stdin_option", + "read_stdin_or", + "read_stdin_i32_result", + "read_stdin_i32_option", + "read_stdin_i32_or_zero", + "read_stdin_i32_or", + "read_stdin_u32_result", + "read_stdin_u32_option", + "read_stdin_u32_or_zero", + "read_stdin_u32_or", + "read_stdin_i64_result", + "read_stdin_i64_option", + "read_stdin_i64_or_zero", + "read_stdin_i64_or", + "read_stdin_u64_result", + "read_stdin_u64_option", + "read_stdin_u64_or_zero", + "read_stdin_u64_or", + "read_stdin_f64_result", + "read_stdin_f64_option", + "read_stdin_f64_or_zero", + "read_stdin_f64_or", + "read_stdin_bool_result", + "read_stdin_bool_option", + "read_stdin_bool_or_false", + "read_stdin_bool_or", +]; + +#[test] +fn explicit_std_io_import_loads_repo_root_standard_source() { + let compiler_root = Path::new(env!("CARGO_MANIFEST_DIR")); + let project = compiler_root.join("../examples/projects/std-import-io"); + let slovo_io = compiler_root.join("../lib/std/io.slo"); + + assert!( + !project.join("src/io.slo").exists(), + "std-import-io must not carry a local io module copy" + ); + assert!( + read(&project.join("src/main.slo")).starts_with("(module main)\n\n(import std.io ("), + "std-import-io must exercise explicit `std.io` import syntax" + ); + + let slovo_source = read(&slovo_io); + assert!( + slovo_source.starts_with("(module io (export "), + "repo-root Slovo std/io.slo must export imported helpers directly" + ); + assert!( + slovo_source.contains( + "(import std.result (ok_or_none_string ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool))", + ) && slovo_source.contains( + "(import std.string (parse_i32_result parse_u32_result parse_i64_result parse_u64_result parse_f64_result parse_bool_result))", + ) && slovo_source.contains("(std.io.read_stdin_result)"), + "repo-root Slovo std/io.slo must use the promoted stdin result runtime plus standard source bridge imports" + ); + for helper in STANDARD_IO_SOURCE_FACADE_ALPHA { + assert!( + slovo_source.contains(&format!("(fn {} ", helper)), + "Slovo std/io.slo is missing helper `{}`", + helper + ); + } + + let fmt = run_glagol([ + OsStr::new("fmt"), + OsStr::new("--check"), + project.as_os_str(), + ]); + assert_success("std io facade source search fmt --check", &fmt); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std io facade source search check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + test, + EXPECTED_STD_IO_OUTPUT, + "std io facade source search test", + ); +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn read(path: &Path) -> String { + fs::read_to_string(path).unwrap_or_else(|err| panic!("read `{}`: {}", path.display(), err)) +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstatus: {:?}\nstdout:\n{}\nstderr:\n{}", + context, + output.status.code(), + stdout, + stderr + ); +} + +fn assert_success_stdout(output: Output, expected: &str, context: &str) { + assert_success(context, &output); + let stdout = String::from_utf8_lossy(&output.stdout); + assert_eq!(stdout, expected, "{}", context); +} diff --git a/compiler/tests/standard_io_host_env.rs b/compiler/tests/standard_io_host_env.rs new file mode 100644 index 0000000..9431c08 --- /dev/null +++ b/compiler/tests/standard_io_host_env.rs @@ -0,0 +1,341 @@ +use std::{ + env, + ffi::OsStr, + fs, + path::{Path, PathBuf}, + process::{Command, Output}, + sync::atomic::{AtomicUsize, Ordering}, +}; + +static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0); + +#[test] +fn standard_io_host_env_fixture_lowers_to_runtime_helpers() { + let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/standard-io-host-env.slo"); + let compile = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + + assert!( + compile.status.success(), + "compiler rejected exp-3 fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("declare void @__glagol_io_eprint(ptr)") + && stdout.contains("declare void @__glagol_process_init(i32, ptr)") + && stdout.contains("declare i32 @__glagol_process_argc()") + && stdout.contains("declare ptr @__glagol_process_arg(i32)") + && stdout.contains("declare ptr @__glagol_env_get(ptr)") + && stdout.contains("declare ptr @__glagol_fs_read_text(ptr)") + && stdout.contains("declare i32 @__glagol_fs_write_text(ptr, ptr)") + && stdout.contains("define i32 @main(i32 %__glagol_argc, ptr %__glagol_argv)") + && stdout.contains( + "call void @__glagol_process_init(i32 %__glagol_argc, ptr %__glagol_argv)" + ) + && stdout.contains("call void @__glagol_io_eprint(ptr @") + && stdout.contains("call i32 @__glagol_process_argc()") + && stdout.contains("call ptr @__glagol_process_arg(i32 0)") + && stdout.contains("call ptr @__glagol_env_get(ptr @") + && stdout.contains("call ptr @__glagol_fs_read_text(ptr @") + && stdout.contains("call i32 @__glagol_fs_write_text(ptr @") + && !stdout.contains("@std."), + "LLVM output did not contain expected exp-3 runtime shape\nstdout:\n{}", + stdout + ); + assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr); +} + +#[test] +fn standard_io_host_env_test_runner_interprets_host_calls() { + let root = temp_root("test-runner"); + let input = root.join("input.txt"); + let output = root.join("output.txt"); + fs::create_dir_all(&root).unwrap_or_else(|err| panic!("create `{}`: {}", root.display(), err)); + fs::write(&input, "hello").unwrap_or_else(|err| panic!("write `{}`: {}", input.display(), err)); + + let source = format!( + r#" +(module main) + +(test "env missing is empty" + (= (std.env.get "GLAGOL_EXP_3_MISSING") "")) + +(test "fs roundtrip" + (= (std.fs.write_text "{}" (std.fs.read_text "{}")) 0)) +"#, + slovo_path(&output), + slovo_path(&input) + ); + let fixture = write_fixture("host-test-runner", &source); + let run = run_glagol([OsStr::new("test"), fixture.as_os_str()]); + + assert_success_stdout( + run, + concat!( + "test \"env missing is empty\" ... ok\n", + "test \"fs roundtrip\" ... ok\n", + "2 test(s) passed\n", + ), + "exp-3 test runner output", + ); + assert_eq!( + fs::read_to_string(&output).expect("read roundtrip output"), + "hello" + ); +} + +#[test] +fn standard_io_host_env_runtime_smoke_when_clang_is_available() { + let Some(clang) = find_clang() else { + eprintln!("skipping exp-3 runtime smoke: set GLAGOL_CLANG or install clang"); + return; + }; + + let root = temp_root("runtime"); + let input = root.join("input.txt"); + let output = root.join("output.txt"); + fs::create_dir_all(&root).unwrap_or_else(|err| panic!("create `{}`: {}", root.display(), err)); + fs::write(&input, "runtime text") + .unwrap_or_else(|err| panic!("write `{}`: {}", input.display(), err)); + + let source = format!( + r#" +(module main) + +(fn main () -> i32 + (std.io.eprint "err-line\n") + (std.io.print_i32 (std.process.argc)) + (std.io.print_string (std.process.arg 1)) + (std.io.print_string (std.env.get "GLAGOL_EXP_3_PRESENT")) + (let text string (std.fs.read_text "{}")) + (std.io.print_string text) + (std.fs.write_text "{}" (std.string.concat text "-written"))) +"#, + slovo_path(&input), + slovo_path(&output) + ); + let fixture = write_fixture("host-runtime", &source); + let compile = run_glagol([fixture.as_os_str()]); + assert_success("compile exp-3 runtime smoke", &compile); + + let run = compile_and_run_with_runtime(&clang, "host-runtime", &compile.stdout, |command| { + command + .arg("argument-value") + .env("GLAGOL_EXP_3_PRESENT", "env-value"); + }); + assert_eq!( + run.status.code(), + Some(0), + "exp-3 runtime exit code drifted\nstdout:\n{}\nstderr:\n{}", + String::from_utf8_lossy(&run.stdout), + String::from_utf8_lossy(&run.stderr) + ); + assert_eq!( + String::from_utf8_lossy(&run.stdout), + "2\nargument-value\nenv-value\nruntime text\n", + "exp-3 runtime stdout drifted" + ); + assert_eq!( + String::from_utf8_lossy(&run.stderr), + "err-line\n", + "exp-3 runtime stderr drifted" + ); + assert_eq!( + fs::read_to_string(&output).expect("read exp-3 write output"), + "runtime text-written" + ); +} + +#[test] +fn standard_io_host_env_runtime_traps_when_clang_is_available() { + let Some(clang) = find_clang() else { + eprintln!("skipping exp-3 trap smoke: set GLAGOL_CLANG or install clang"); + return; + }; + + let cases = [ + ( + "process-arg-oob", + r#" +(module main) + +(fn main () -> i32 + (std.string.len (std.process.arg 99))) +"#, + "slovo runtime error: process argument index out of bounds\n", + ), + ( + "file-read-failed", + r#" +(module main) + +(fn main () -> i32 + (std.string.len (std.fs.read_text "/tmp/glagol-exp-3-missing-file-for-trap.txt"))) +"#, + "slovo runtime error: file read failed\n", + ), + ]; + + for (name, source, expected_stderr) in cases { + let fixture = write_fixture(name, source); + let compile = run_glagol([fixture.as_os_str()]); + assert_success("compile exp-3 trap smoke", &compile); + let run = compile_and_run_with_runtime(&clang, name, &compile.stdout, |_| {}); + + assert_eq!( + run.status.code(), + Some(1), + "exp-3 trap `{}` exit code drifted\nstdout:\n{}\nstderr:\n{}", + name, + String::from_utf8_lossy(&run.stdout), + String::from_utf8_lossy(&run.stderr) + ); + assert_eq!( + String::from_utf8_lossy(&run.stderr), + expected_stderr, + "exp-3 trap `{}` stderr drifted", + name + ); + assert!( + run.stdout.is_empty(), + "exp-3 trap `{}` wrote stdout:\n{}", + name, + String::from_utf8_lossy(&run.stdout) + ); + } +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn write_fixture(name: &str, source: &str) -> PathBuf { + let mut path = env::temp_dir(); + path.push(format!( + "glagol-standard-io-host-env-{}-{}-{}.slo", + name, + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + fs::write(&path, source).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err)); + path +} + +fn temp_root(name: &str) -> PathBuf { + env::temp_dir().join(format!( + "glagol-standard-io-host-env-{}-{}-{}", + name, + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )) +} + +fn slovo_path(path: &Path) -> String { + path.to_string_lossy() + .replace('\\', "\\\\") + .replace('"', "\\\"") +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); +} + +fn assert_success_stdout(output: Output, expected: &str, context: &str) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert_eq!(stdout, expected, "{} stdout drifted", context); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} + +fn compile_and_run_with_runtime(clang: &Path, name: &str, ir: &[u8], configure: F) -> Output +where + F: FnOnce(&mut Command), +{ + let manifest = Path::new(env!("CARGO_MANIFEST_DIR")); + let temp_dir = temp_root("clang"); + fs::create_dir_all(&temp_dir) + .unwrap_or_else(|err| panic!("create `{}`: {}", temp_dir.display(), err)); + + let ir_path = temp_dir.join(format!("{}.ll", name)); + let exe_path = temp_dir.join(name); + fs::write(&ir_path, ir).unwrap_or_else(|err| panic!("write `{}`: {}", ir_path.display(), err)); + + let runtime = manifest.join("../runtime/runtime.c"); + let mut clang_command = Command::new(clang); + clang_command + .arg(&runtime) + .arg(&ir_path) + .arg("-o") + .arg(&exe_path) + .current_dir(manifest); + configure_clang_runtime_env(&mut clang_command, clang); + let clang_output = clang_command + .output() + .unwrap_or_else(|err| panic!("run `{}`: {}", clang.display(), err)); + assert_success("clang exp-3 runtime smoke", &clang_output); + + let mut run = Command::new(&exe_path); + configure(&mut run); + run.output() + .unwrap_or_else(|err| panic!("run `{}`: {}", exe_path.display(), err)) +} + +fn find_clang() -> Option { + if let Some(path) = env::var_os("GLAGOL_CLANG").filter(|value| !value.is_empty()) { + return Some(PathBuf::from(path)); + } + + let hermetic_clang = PathBuf::from("/tmp/glagol-clang-root/usr/bin/clang"); + if hermetic_clang.is_file() { + return Some(hermetic_clang); + } + + find_on_path("clang") +} + +fn find_on_path(program: &str) -> Option { + let path = env::var_os("PATH")?; + env::split_paths(&path) + .map(|dir| dir.join(program)) + .find(|candidate| candidate.is_file()) +} + +fn configure_clang_runtime_env(command: &mut Command, clang: &Path) { + if !clang.starts_with("/tmp/glagol-clang-root") { + return; + } + + let root = Path::new("/tmp/glagol-clang-root"); + let lib64 = root.join("usr/lib64"); + let lib = root.join("usr/lib"); + let existing = env::var_os("LD_LIBRARY_PATH").unwrap_or_default(); + let mut paths = vec![lib64, lib]; + paths.extend(env::split_paths(&existing)); + let joined = env::join_paths(paths).expect("join LD_LIBRARY_PATH"); + command.env("LD_LIBRARY_PATH", joined); +} diff --git a/compiler/tests/standard_io_source_value_helpers_alpha.rs b/compiler/tests/standard_io_source_value_helpers_alpha.rs new file mode 100644 index 0000000..bc5e645 --- /dev/null +++ b/compiler/tests/standard_io_source_value_helpers_alpha.rs @@ -0,0 +1,243 @@ +use std::{ + ffi::OsStr, + fs, + path::Path, + process::{Command, Output}, +}; + +const EXPECTED_TEST_OUTPUT: &str = concat!( + "test \"explicit local io i64 zero facade\" ... ok\n", + "test \"explicit local io u32 zero facade\" ... ok\n", + "test \"explicit local io u64 zero facade\" ... ok\n", + "test \"explicit local io f64 zero facade\" ... ok\n", + "test \"explicit local io value facade\" ... ok\n", + "test \"explicit local io stdin result facade\" ... ok\n", + "test \"explicit local io stdin option facade\" ... ok\n", + "test \"explicit local io stdin text fallback facade\" ... ok\n", + "test \"explicit local io stdin typed result facade\" ... ok\n", + "test \"explicit local io stdin typed option facade\" ... ok\n", + "test \"explicit local io stdin typed fallback facade\" ... ok\n", + "test \"explicit local io helpers all\" ... ok\n", + "12 test(s) passed\n", +); + +const STANDARD_IO_SOURCE_VALUE_HELPERS_ALPHA: &[&str] = &[ + "print_i32_zero", + "print_i64_zero", + "print_u32_zero", + "print_u64_zero", + "print_f64_zero", + "print_string_zero", + "print_bool_zero", + "print_i32_value", + "print_i64_value", + "print_u32_value", + "print_u64_value", + "print_f64_value", + "print_string_value", + "print_bool_value", + "read_stdin_result", + "read_stdin_option", + "read_stdin_or", + "read_stdin_i32_result", + "read_stdin_i32_option", + "read_stdin_i32_or_zero", + "read_stdin_i32_or", + "read_stdin_u32_result", + "read_stdin_u32_option", + "read_stdin_u32_or_zero", + "read_stdin_u32_or", + "read_stdin_i64_result", + "read_stdin_i64_option", + "read_stdin_i64_or_zero", + "read_stdin_i64_or", + "read_stdin_u64_result", + "read_stdin_u64_option", + "read_stdin_u64_or_zero", + "read_stdin_u64_or", + "read_stdin_f64_result", + "read_stdin_f64_option", + "read_stdin_f64_or_zero", + "read_stdin_f64_or", + "read_stdin_bool_result", + "read_stdin_bool_option", + "read_stdin_bool_or_false", + "read_stdin_bool_or", +]; + +#[test] +fn standard_io_source_value_helper_project_checks_formats_and_tests() { + let project = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/projects/std-layout-local-io"); + + assert_local_io_fixture_is_source_authored(&project); + + let fmt = run_glagol([ + OsStr::new("fmt"), + OsStr::new("--check"), + project.as_os_str(), + ]); + assert_success("std layout local io fmt --check", &fmt); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std layout local io check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + test, + EXPECTED_TEST_OUTPUT, + "std layout local io test output", + ); +} + +fn assert_local_io_fixture_is_source_authored(project: &Path) { + let io_source = read(&project.join("src/io.slo")); + let result_source = read(&project.join("src/result.slo")); + let string_source = read(&project.join("src/string.slo")); + let main = read(&project.join("src/main.slo")); + + assert!( + io_source.starts_with("(module io (export "), + "io.slo must stay an explicit local module export" + ); + assert!( + result_source.starts_with("(module result (export "), + "result.slo must stay an explicit local module export" + ); + assert!( + string_source.starts_with("(module string (export "), + "string.slo must stay an explicit local module export" + ); + assert!( + main.starts_with("(module main)\n\n(import io ("), + "main.slo must stay an explicit local io import" + ); + assert!( + !main.contains("(import std") && !main.contains("(import slovo.std"), + "io fixture must not depend on automatic or package std imports" + ); + + let mut non_io_std = io_source.clone(); + for allowed in [ + "std.io.print_i32", + "std.io.print_i64", + "std.io.print_u32", + "std.io.print_u64", + "std.io.print_f64", + "std.io.print_string", + "std.io.print_bool", + "std.io.read_stdin_result", + ] { + non_io_std = non_io_std.replace(allowed, ""); + } + assert!( + !non_io_std.contains("std.") && !main.contains("std."), + "io fixture must use only the existing promoted std.io print and stdin-result runtime names" + ); + assert!( + io_source.contains( + "(import result (ok_or_none_string ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool))", + ) && io_source.contains( + "(import string (parse_i32_result parse_u32_result parse_i64_result parse_u64_result parse_f64_result parse_bool_result))", + ) && io_source.contains("(std.io.read_stdin_result)") + && !io_source.contains("eprint") + && !io_source.contains("read_line") + && !io_source.contains("stream") + && !io_source.contains("async") + && !main.contains("eprint") + && !main.contains("read_line") + && !main.contains("stream") + && !main.contains("async"), + "io fixture must stay local while using only the released stdin-result lane and must not claim deferred io helpers" + ); + + let mut non_string_std = string_source.clone(); + for allowed in [ + "std.string.len", + "std.string.concat", + "std.string.parse_i32_result", + "std.string.parse_u32_result", + "std.string.parse_i64_result", + "std.string.parse_u64_result", + "std.string.parse_f64_result", + "std.string.parse_bool_result", + ] { + non_string_std = non_string_std.replace(allowed, ""); + } + assert!( + !non_string_std.contains("std."), + "local io string fixture must use only the promoted std.string runtime names" + ); + + let mut non_result_std = result_source.clone(); + for allowed in [ + "std.result.is_ok", + "std.result.is_err", + "std.result.unwrap_ok", + "std.result.unwrap_err", + ] { + non_result_std = non_result_std.replace(allowed, ""); + } + assert!( + !non_result_std.contains("std."), + "local io result fixture must use only the promoted std.result runtime names" + ); + + for helper in STANDARD_IO_SOURCE_VALUE_HELPERS_ALPHA { + assert!( + io_source.contains(&format!("(fn {} ", helper)), + "io.slo is missing source helper `{}`", + helper + ); + assert!( + main.contains(helper), + "main.slo does not explicitly import/use `{}`", + helper + ); + } +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn read(path: &Path) -> String { + fs::read_to_string(path).unwrap_or_else(|err| panic!("read `{}`: {}", path.display(), err)) +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} + +fn assert_success_stdout(output: Output, expected: &str, context: &str) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert_eq!(stdout, expected, "{} stdout drifted", context); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} diff --git a/compiler/tests/standard_library_path_list_alpha.rs b/compiler/tests/standard_library_path_list_alpha.rs new file mode 100644 index 0000000..5d4c115 --- /dev/null +++ b/compiler/tests/standard_library_path_list_alpha.rs @@ -0,0 +1,108 @@ +use std::{ + env, + ffi::{OsStr, OsString}, + fs, + path::{Path, PathBuf}, + process::{Command, Output}, + sync::atomic::{AtomicUsize, Ordering}, +}; + +static NEXT_TEMP_ID: AtomicUsize = AtomicUsize::new(0); + +const EXPECTED_OUTPUT: &str = + "test \"standard library path list discovery\" ... ok\n1 test(s) passed\n"; + +#[test] +fn slovo_std_path_accepts_os_path_list() { + let root = temp_root(); + let first_std = root.join("first-empty-std"); + let second_std = root.join("second-std"); + let project = root.join("project"); + let source_dir = project.join("src"); + + create_dir(&first_std); + create_dir(&second_std); + create_dir(&source_dir); + + write( + &second_std.join("path_list_probe.slo"), + "(module path_list_probe (export value))\n\n(fn value () -> i32\n 51)\n", + ); + write( + &project.join("slovo.toml"), + concat!( + "[project]\n", + "name = \"standard-library-path-list-alpha\"\n", + "source_root = \"src\"\n", + "entry = \"main\"\n", + ), + ); + write( + &source_dir.join("main.slo"), + concat!( + "(module main)\n\n", + "(import std.path_list_probe (value))\n\n", + "(fn main () -> i32\n", + " (value))\n\n", + "(test \"standard library path list discovery\"\n", + " (= (value) 51))\n", + ), + ); + + let std_path = env::join_paths([first_std.as_os_str(), second_std.as_os_str()]) + .expect("join std path list"); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()], &root, &std_path); + assert_success_stdout(check, "", "standard library path list check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()], &root, &std_path); + assert_success_stdout(test, EXPECTED_OUTPUT, "standard library path list test"); +} + +fn run_glagol(args: I, cwd: &Path, std_path: &OsString) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(cwd) + .env("SLOVO_STD_PATH", std_path) + .output() + .expect("run glagol") +} + +fn write(path: &Path, content: &str) { + fs::write(path, content).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err)); +} + +fn create_dir(path: &Path) { + fs::create_dir_all(path).unwrap_or_else(|err| panic!("create `{}`: {}", path.display(), err)); +} + +fn temp_root() -> PathBuf { + let path = env::temp_dir().join(format!( + "glagol-standard-library-path-list-alpha-{}-{}", + std::process::id(), + NEXT_TEMP_ID.fetch_add(1, Ordering::Relaxed) + )); + let _ = fs::remove_dir_all(&path); + fs::create_dir_all(&path).unwrap_or_else(|err| panic!("create `{}`: {}", path.display(), err)); + path +} + +fn assert_success_stdout(output: Output, expected: &str, context: &str) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstatus: {:?}\nstdout:\n{}\nstderr:\n{}", + context, + output.status.code(), + stdout, + stderr + ); + assert_eq!(stdout, expected, "{}", context); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} diff --git a/compiler/tests/standard_library_source_search_alpha.rs b/compiler/tests/standard_library_source_search_alpha.rs new file mode 100644 index 0000000..9b173ff --- /dev/null +++ b/compiler/tests/standard_library_source_search_alpha.rs @@ -0,0 +1,83 @@ +use std::{ + ffi::OsStr, + fs, + path::Path, + process::{Command, Output}, +}; + +const EXPECTED_STD_MATH_OUTPUT: &str = concat!( + "test \"explicit std math import i32 helpers\" ... ok\n", + "test \"explicit std math import i64 helpers\" ... ok\n", + "test \"explicit std math import f64 helpers\" ... ok\n", + "test \"explicit std math import all helpers\" ... ok\n", + "4 test(s) passed\n", +); + +#[test] +fn explicit_std_math_import_loads_repo_root_standard_source() { + let compiler_root = Path::new(env!("CARGO_MANIFEST_DIR")); + let project = compiler_root.join("../examples/projects/std-import-math"); + let slovo_math = compiler_root.join("../lib/std/math.slo"); + + assert!( + !project.join("src/math.slo").exists(), + "std-import-math must not carry a local math module copy" + ); + assert!( + read(&project.join("src/main.slo")).starts_with("(module main)\n\n(import std.math ("), + "std-import-math must exercise explicit `std.math` import syntax" + ); + assert!( + read(&slovo_math).starts_with("(module math (export "), + "repo-root Slovo std/math.slo must export imported helpers directly" + ); + + let fmt = run_glagol([ + OsStr::new("fmt"), + OsStr::new("--check"), + project.as_os_str(), + ]); + assert_success("std import math fmt --check", &fmt); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std import math check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout(test, EXPECTED_STD_MATH_OUTPUT, "std import math test"); +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn read(path: &Path) -> String { + fs::read_to_string(path).unwrap_or_else(|err| panic!("read `{}`: {}", path.display(), err)) +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstatus: {:?}\nstdout:\n{}\nstderr:\n{}", + context, + output.status.code(), + stdout, + stderr + ); +} + +fn assert_success_stdout(output: Output, expected: &str, context: &str) { + assert_success(context, &output); + let stdout = String::from_utf8_lossy(&output.stdout); + assert_eq!(stdout, expected, "{}", context); +} diff --git a/compiler/tests/standard_math_source_helpers_alpha.rs b/compiler/tests/standard_math_source_helpers_alpha.rs new file mode 100644 index 0000000..0538d78 --- /dev/null +++ b/compiler/tests/standard_math_source_helpers_alpha.rs @@ -0,0 +1,176 @@ +use std::{ + ffi::OsStr, + fs, + path::Path, + process::{Command, Output}, +}; + +const EXPECTED_TEST_OUTPUT: &str = concat!( + "test \"local math i32 helpers\" ... ok\n", + "test \"local math i64 helpers\" ... ok\n", + "test \"local math f64 helpers\" ... ok\n", + "test \"explicit local math import i32 helpers\" ... ok\n", + "test \"explicit local math import i64 helpers\" ... ok\n", + "test \"explicit local math import f64 helpers\" ... ok\n", + "test \"explicit local math import all helpers\" ... ok\n", + "7 test(s) passed\n", +); + +const STANDARD_MATH_HELPERS_ALPHA: &[&str] = &[ + "abs_i32", + "neg_i32", + "rem_i32", + "bit_and_i32", + "bit_or_i32", + "bit_xor_i32", + "is_even_i32", + "is_odd_i32", + "min_i32", + "max_i32", + "clamp_i32", + "square_i32", + "cube_i32", + "is_zero_i32", + "is_positive_i32", + "is_negative_i32", + "in_range_i32", + "abs_i64", + "neg_i64", + "rem_i64", + "bit_and_i64", + "bit_or_i64", + "bit_xor_i64", + "is_even_i64", + "is_odd_i64", + "min_i64", + "max_i64", + "clamp_i64", + "square_i64", + "cube_i64", + "is_zero_i64", + "is_positive_i64", + "is_negative_i64", + "in_range_i64", + "abs_f64", + "neg_f64", + "min_f64", + "max_f64", + "clamp_f64", + "square_f64", + "cube_f64", + "is_zero_f64", + "is_positive_f64", + "is_negative_f64", + "in_range_f64", +]; + +#[test] +fn standard_math_source_helper_project_checks_formats_and_tests() { + let project = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/projects/std-layout-local-math"); + + assert_local_math_fixture_is_source_authored(&project); + + let fmt = run_glagol([ + OsStr::new("fmt"), + OsStr::new("--check"), + project.as_os_str(), + ]); + assert_success("std layout local math fmt --check", &fmt); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std layout local math check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + test, + EXPECTED_TEST_OUTPUT, + "std layout local math test output", + ); +} + +fn assert_local_math_fixture_is_source_authored(project: &Path) { + let math = read(&project.join("src/math.slo")); + let main = read(&project.join("src/main.slo")); + let math_export_line = math.lines().next().unwrap_or_default(); + + assert!( + math.starts_with("(module math (export "), + "math.slo must stay an explicit local module export" + ); + assert!( + main.starts_with("(module main)\n\n(import math ("), + "main.slo must stay an explicit local math import" + ); + assert!( + !math.contains("std.") && !main.contains("std."), + "exp-32 fixture must not use compiler-known std runtime names" + ); + assert!( + !main.contains("(import std") && !main.contains("(import slovo.std"), + "exp-32 fixture must not depend on automatic or package std imports" + ); + + for helper in STANDARD_MATH_HELPERS_ALPHA { + assert!( + math.contains(&format!("(fn {} ", helper)), + "math.slo is missing source helper `{}`", + helper + ); + assert!( + math_export_line.contains(helper), + "math.slo does not explicitly export `{}`", + helper + ); + assert!( + main.matches(helper).count() >= 2, + "main.slo does not explicitly import and use `{}`", + helper + ); + } +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn read(path: &Path) -> String { + fs::read_to_string(path).unwrap_or_else(|err| panic!("read `{}`: {}", path.display(), err)) +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} + +fn assert_success_stdout(output: Output, expected: &str, context: &str) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert_eq!(stdout, expected, "{} stdout drifted", context); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} diff --git a/compiler/tests/standard_num_source_fallback_helpers_alpha.rs b/compiler/tests/standard_num_source_fallback_helpers_alpha.rs new file mode 100644 index 0000000..1336991 --- /dev/null +++ b/compiler/tests/standard_num_source_fallback_helpers_alpha.rs @@ -0,0 +1,167 @@ +use std::{ + ffi::OsStr, + fs, + path::Path, + process::{Command, Output}, +}; + +const EXPECTED_TEST_OUTPUT: &str = concat!( + "test \"explicit local num widening\" ... ok\n", + "test \"explicit local num checked conversions\" ... ok\n", + "test \"explicit local num to string\" ... ok\n", + "test \"explicit local num checked fallbacks\" ... ok\n", + "test \"explicit local num helpers all\" ... ok\n", + "5 test(s) passed\n", +); + +const STANDARD_NUM_SOURCE_FACADE_ALPHA: &[&str] = &[ + "i32_to_i64", + "i32_to_f64", + "i64_to_f64", + "i64_to_i32_result", + "f64_to_i32_result", + "f64_to_i64_result", + "i32_to_string", + "u32_to_string", + "i64_to_string", + "u64_to_string", + "f64_to_string", + "i64_to_i32_or", + "f64_to_i32_or", + "f64_to_i64_or", +]; + +#[test] +fn standard_num_source_fallback_helper_project_checks_formats_and_tests() { + let project = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/projects/std-layout-local-num"); + + assert_local_num_fixture_is_source_authored(&project); + + let fmt = run_glagol([ + OsStr::new("fmt"), + OsStr::new("--check"), + project.as_os_str(), + ]); + assert_success("std layout local num fmt --check", &fmt); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std layout local num check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + test, + EXPECTED_TEST_OUTPUT, + "std layout local num test output", + ); +} + +fn assert_local_num_fixture_is_source_authored(project: &Path) { + let num_source = read(&project.join("src/num.slo")); + let main = read(&project.join("src/main.slo")); + + assert!( + num_source.starts_with("(module num (export "), + "num.slo must stay an explicit local module export" + ); + assert!( + main.starts_with("(module main)\n\n(import num ("), + "main.slo must stay an explicit local num import" + ); + assert!( + !main.contains("(import std") && !main.contains("(import slovo.std"), + "num fixture must not depend on automatic or package std imports" + ); + + let mut non_num_std = num_source.clone(); + for allowed in [ + "std.num.i32_to_i64", + "std.num.i32_to_f64", + "std.num.i64_to_f64", + "std.num.i64_to_i32_result", + "std.num.f64_to_i32_result", + "std.num.f64_to_i64_result", + "std.num.i32_to_string", + "std.num.u32_to_string", + "std.num.i64_to_string", + "std.num.u64_to_string", + "std.num.f64_to_string", + ] { + non_num_std = non_num_std.replace(allowed, ""); + } + assert!( + !non_num_std.contains("std.") && !main.contains("std."), + "num fixture must not introduce other compiler-known std names" + ); + assert!( + !num_source.contains("saturat") + && !num_source.contains("round") + && !num_source.contains("floor") + && !num_source.contains("ceil") + && !num_source.contains("generic") + && !main.contains("saturat") + && !main.contains("round") + && !main.contains("floor") + && !main.contains("ceil") + && !main.contains("generic"), + "num fixture must not claim deferred numeric conversion policies or generic abstractions" + ); + + for helper in STANDARD_NUM_SOURCE_FACADE_ALPHA { + assert!( + num_source.contains(&format!("(fn {} ", helper)), + "num.slo is missing source facade `{}`", + helper + ); + assert!( + main.contains(helper), + "main.slo does not explicitly import/use `{}`", + helper + ); + } +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn read(path: &Path) -> String { + fs::read_to_string(path).unwrap_or_else(|err| panic!("read `{}`: {}", path.display(), err)) +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} + +fn assert_success_stdout(output: Output, expected: &str, context: &str) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert_eq!(stdout, expected, "{} stdout drifted", context); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} diff --git a/compiler/tests/standard_option_source_helpers_alpha.rs b/compiler/tests/standard_option_source_helpers_alpha.rs new file mode 100644 index 0000000..7544195 --- /dev/null +++ b/compiler/tests/standard_option_source_helpers_alpha.rs @@ -0,0 +1,225 @@ +use std::{ + ffi::OsStr, + fs, + path::Path, + process::{Command, Output}, +}; + +const EXPECTED_TEST_OUTPUT: &str = concat!( + "test \"explicit local option i32 observation\" ... ok\n", + "test \"explicit local option i32 unwrap_some\" ... ok\n", + "test \"explicit local option i32 unwrap_or\" ... ok\n", + "test \"explicit local option i32 some_or_err ok\" ... ok\n", + "test \"explicit local option i32 some_or_err err\" ... ok\n", + "test \"explicit local option u32 observation\" ... ok\n", + "test \"explicit local option u32 unwrap_some\" ... ok\n", + "test \"explicit local option u32 unwrap_or\" ... ok\n", + "test \"explicit local option u32 some_or_err ok\" ... ok\n", + "test \"explicit local option u32 some_or_err err\" ... ok\n", + "test \"explicit local option i64 observation\" ... ok\n", + "test \"explicit local option i64 unwrap_some\" ... ok\n", + "test \"explicit local option i64 unwrap_or\" ... ok\n", + "test \"explicit local option i64 some_or_err ok\" ... ok\n", + "test \"explicit local option i64 some_or_err err\" ... ok\n", + "test \"explicit local option u64 observation\" ... ok\n", + "test \"explicit local option u64 unwrap_some\" ... ok\n", + "test \"explicit local option u64 unwrap_or\" ... ok\n", + "test \"explicit local option u64 some_or_err ok\" ... ok\n", + "test \"explicit local option u64 some_or_err err\" ... ok\n", + "test \"explicit local option f64 observation\" ... ok\n", + "test \"explicit local option f64 unwrap_some\" ... ok\n", + "test \"explicit local option f64 unwrap_or\" ... ok\n", + "test \"explicit local option f64 some_or_err ok\" ... ok\n", + "test \"explicit local option f64 some_or_err err\" ... ok\n", + "test \"explicit local option bool observation\" ... ok\n", + "test \"explicit local option bool unwrap_some\" ... ok\n", + "test \"explicit local option bool unwrap_or\" ... ok\n", + "test \"explicit local option bool some_or_err ok\" ... ok\n", + "test \"explicit local option bool some_or_err err\" ... ok\n", + "test \"explicit local option string observation\" ... ok\n", + "test \"explicit local option string unwrap_some\" ... ok\n", + "test \"explicit local option string unwrap_or\" ... ok\n", + "test \"explicit local option string some_or_err ok\" ... ok\n", + "test \"explicit local option string some_or_err err\" ... ok\n", + "test \"explicit local option helpers all\" ... ok\n", + "36 test(s) passed\n", +); + +const STANDARD_OPTION_SOURCE_HELPERS_ALPHA: &[&str] = &[ + "some_i32", + "none_i32", + "is_some_i32", + "is_none_i32", + "unwrap_some_i32", + "unwrap_or_i32", + "some_u32", + "none_u32", + "is_some_u32", + "is_none_u32", + "unwrap_some_u32", + "unwrap_or_u32", + "some_i64", + "none_i64", + "is_some_i64", + "is_none_i64", + "unwrap_some_i64", + "unwrap_or_i64", + "some_u64", + "none_u64", + "is_some_u64", + "is_none_u64", + "unwrap_some_u64", + "unwrap_or_u64", + "some_f64", + "none_f64", + "is_some_f64", + "is_none_f64", + "unwrap_some_f64", + "unwrap_or_f64", + "some_bool", + "none_bool", + "is_some_bool", + "is_none_bool", + "unwrap_some_bool", + "unwrap_or_bool", + "some_string", + "none_string", + "is_some_string", + "is_none_string", + "unwrap_some_string", + "unwrap_or_string", +]; + +const STANDARD_OPTION_RESULT_BRIDGE_HELPERS_ALPHA: &[&str] = &[ + "some_or_err_i32", + "some_or_err_u32", + "some_or_err_i64", + "some_or_err_u64", + "some_or_err_f64", + "some_or_err_bool", + "some_or_err_string", +]; + +#[test] +fn standard_option_source_helper_project_checks_formats_and_tests() { + let project = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/projects/std-layout-local-option"); + + assert_local_option_fixture_is_source_authored(&project); + + let fmt = run_glagol([ + OsStr::new("fmt"), + OsStr::new("--check"), + project.as_os_str(), + ]); + assert_success("std layout local option fmt --check", &fmt); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std layout local option check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + test, + EXPECTED_TEST_OUTPUT, + "std layout local option test output", + ); +} + +fn assert_local_option_fixture_is_source_authored(project: &Path) { + let option = read(&project.join("src/option.slo")); + let main = read(&project.join("src/main.slo")); + + assert!( + option.starts_with("(module option (export "), + "option.slo must stay an explicit local module export" + ); + assert!( + main.starts_with("(module main)\n\n(import option ("), + "main.slo must stay an explicit local option import" + ); + assert!( + !option.contains("std.") && !main.contains("std."), + "exp-36 fixture must not use compiler-known std runtime names" + ); + assert!( + !main.contains("(import std") && !main.contains("(import slovo.std"), + "exp-36 fixture must not depend on automatic or package std imports" + ); + assert!( + !option.contains("std.result.") + && !main.contains("std.result.") + && !main.contains("(import result"), + "exp-109 fixture must bridge through raw concrete result forms, not compiler-known std.result names or a separate local result module" + ); + + for helper in STANDARD_OPTION_SOURCE_HELPERS_ALPHA { + assert!( + option.contains(&format!("(fn {} ", helper)), + "option.slo is missing source helper `{}`", + helper + ); + assert!( + main.contains(helper), + "main.slo does not explicitly import/use `{}`", + helper + ); + } + + for helper in STANDARD_OPTION_RESULT_BRIDGE_HELPERS_ALPHA { + assert!( + option.contains(&format!("(fn {} ", helper)), + "option.slo is missing bridge helper `{}`", + helper + ); + assert!( + main.contains(helper), + "main.slo does not explicitly import/use bridge helper `{}`", + helper + ); + } +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn read(path: &Path) -> String { + fs::read_to_string(path).unwrap_or_else(|err| panic!("read `{}`: {}", path.display(), err)) +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} + +fn assert_success_stdout(output: Output, expected: &str, context: &str) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert_eq!(stdout, expected, "{} stdout drifted", context); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} diff --git a/compiler/tests/standard_process_source_fallback_helpers_alpha.rs b/compiler/tests/standard_process_source_fallback_helpers_alpha.rs new file mode 100644 index 0000000..baa816b --- /dev/null +++ b/compiler/tests/standard_process_source_fallback_helpers_alpha.rs @@ -0,0 +1,258 @@ +use std::{ + ffi::OsStr, + fs, + path::Path, + process::{Command, Output}, +}; + +const EXPECTED_TEST_OUTPUT: &str = concat!( + "test \"explicit local process argc facade\" ... ok\n", + "test \"explicit local process arg result oob facade\" ... ok\n", + "test \"explicit local process arg option facade\" ... ok\n", + "test \"explicit local process has arg facade\" ... ok\n", + "test \"explicit local process arg fallback missing facade\" ... ok\n", + "test \"explicit local process arg fallback present facade\" ... ok\n", + "test \"explicit local process arg i32 missing facade\" ... ok\n", + "test \"explicit local process arg i32 invalid fallback facade\" ... ok\n", + "test \"explicit local process arg u32 missing facade\" ... ok\n", + "test \"explicit local process arg u32 invalid fallback facade\" ... ok\n", + "test \"explicit local process arg i64 missing facade\" ... ok\n", + "test \"explicit local process arg i64 invalid fallback facade\" ... ok\n", + "test \"explicit local process arg u64 missing facade\" ... ok\n", + "test \"explicit local process arg u64 invalid fallback facade\" ... ok\n", + "test \"explicit local process arg f64 missing facade\" ... ok\n", + "test \"explicit local process arg f64 invalid fallback facade\" ... ok\n", + "test \"explicit local process arg bool missing facade\" ... ok\n", + "test \"explicit local process arg bool invalid fallback facade\" ... ok\n", + "test \"explicit local process typed option facade\" ... ok\n", + "test \"explicit local process typed custom fallback facade\" ... ok\n", + "test \"explicit local process facade all\" ... ok\n", + "21 test(s) passed\n", +); + +const STANDARD_PROCESS_SOURCE_FALLBACK_HELPERS_ALPHA: &[&str] = &[ + "argc", + "arg", + "arg_result", + "arg_option", + "has_arg", + "arg_or", + "arg_or_empty", + "arg_i32_result", + "arg_i32_option", + "arg_i32_or_zero", + "arg_i32_or", + "arg_u32_result", + "arg_u32_option", + "arg_u32_or_zero", + "arg_u32_or", + "arg_i64_result", + "arg_i64_option", + "arg_i64_or_zero", + "arg_i64_or", + "arg_u64_result", + "arg_u64_option", + "arg_u64_or_zero", + "arg_u64_or", + "arg_f64_result", + "arg_f64_option", + "arg_f64_or_zero", + "arg_f64_or", + "arg_bool_result", + "arg_bool_option", + "arg_bool_or_false", + "arg_bool_or", +]; + +const LOCAL_STRING_PARSE_HELPERS: &[&str] = &[ + "parse_i32_result", + "parse_u32_result", + "parse_i64_result", + "parse_u64_result", + "parse_f64_result", + "parse_bool_result", +]; + +#[test] +fn standard_process_source_fallback_helper_project_checks_formats_and_tests() { + let project = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/projects/std-layout-local-process"); + + assert_local_process_fixture_is_source_authored(&project); + + let fmt = run_glagol([ + OsStr::new("fmt"), + OsStr::new("--check"), + project.as_os_str(), + ]); + assert_success("std layout local process fmt --check", &fmt); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std layout local process check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + test, + EXPECTED_TEST_OUTPUT, + "std layout local process test output", + ); +} + +fn assert_local_process_fixture_is_source_authored(project: &Path) { + let process = read(&project.join("src/process.slo")); + let result = read(&project.join("src/result.slo")); + let string = read(&project.join("src/string.slo")); + let main = read(&project.join("src/main.slo")); + + assert!( + process.starts_with("(module process (export "), + "process.slo must stay an explicit local module export" + ); + assert!( + string.starts_with("(module string (export "), + "string.slo must stay an explicit local module export" + ); + assert!( + result.starts_with("(module result (export "), + "result.slo must stay an explicit local module export" + ); + assert!( + main.starts_with("(module main)\n\n(import process ("), + "main.slo must stay an explicit local process import" + ); + assert!( + !main.contains("(import std") && !main.contains("(import slovo.std"), + "process fixture must not depend on automatic or package std imports" + ); + assert!( + process.contains("(import result (ok_or_none_string ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool))") + && process.contains("(import string (parse_i32_result parse_u32_result parse_i64_result parse_u64_result parse_f64_result parse_bool_result))") + && !process.contains("std.string.") + && !process.contains("(import std.") + && !main.contains("std."), + "process fixture must stay local while using local result and parse helper imports" + ); + + let mut non_process_std = process.clone(); + for allowed in [ + "std.process.arg_result", + "std.process.argc", + "std.process.arg", + ] { + non_process_std = non_process_std.replace(allowed, ""); + } + assert!( + !non_process_std.contains("std."), + "process fixture must use only the existing promoted std.process runtime names directly" + ); + + let mut non_string_std = string.clone(); + for allowed in [ + "std.string.len", + "std.string.concat", + "std.string.parse_i32_result", + "std.string.parse_u32_result", + "std.string.parse_i64_result", + "std.string.parse_u64_result", + "std.string.parse_f64_result", + "std.string.parse_bool_result", + ] { + non_string_std = non_string_std.replace(allowed, ""); + } + assert!( + !non_string_std.contains("std."), + "local string fixture must use only the existing promoted std.string runtime names" + ); + + for helper in [ + "ok_or_none_string", + "ok_or_none_i32", + "ok_or_none_u32", + "ok_or_none_i64", + "ok_or_none_u64", + "ok_or_none_f64", + "ok_or_none_bool", + ] { + assert!( + result.contains(&format!("(fn {} ", helper)), + "result.slo is missing local result bridge helper `{}`", + helper + ); + } + + assert!( + !process.contains("spawn") + && !process.contains("exit") + && !process.contains("cwd") + && !process.contains("signal") + && !process.contains("shell") + && !process.contains("flag"), + "process fixture must not claim deferred process or CLI framework APIs" + ); + + for helper in STANDARD_PROCESS_SOURCE_FALLBACK_HELPERS_ALPHA { + assert!( + process.contains(&format!("(fn {} ", helper)), + "process.slo is missing source facade `{}`", + helper + ); + assert!( + main.contains(helper), + "main.slo does not explicitly import/use `{}`", + helper + ); + } + + for helper in LOCAL_STRING_PARSE_HELPERS { + assert!( + string.contains(&format!("(fn {} ", helper)), + "string.slo is missing local parse helper `{}`", + helper + ); + } +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn read(path: &Path) -> String { + fs::read_to_string(path).unwrap_or_else(|err| panic!("read `{}`: {}", path.display(), err)) +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} + +fn assert_success_stdout(output: Output, expected: &str, context: &str) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert_eq!(stdout, expected, "{} stdout drifted", context); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} diff --git a/compiler/tests/standard_random.rs b/compiler/tests/standard_random.rs new file mode 100644 index 0000000..1abcf73 --- /dev/null +++ b/compiler/tests/standard_random.rs @@ -0,0 +1,264 @@ +use std::{ + env, + ffi::OsStr, + fs, + path::{Path, PathBuf}, + process::{Command, Output}, + sync::atomic::{AtomicUsize, Ordering}, +}; + +static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0); + +#[test] +fn standard_random_lowers_to_private_runtime_helper() { + let fixture = write_fixture( + "lowering", + r#" +(module main) + +(fn main () -> i32 + (std.random.i32)) +"#, + ); + let output = run_glagol([fixture.as_os_str()]); + assert_success("compile standard random lowering", &output); + let stdout = String::from_utf8_lossy(&output.stdout); + + assert!( + stdout.contains("declare i32 @__glagol_random_i32()") + && stdout.contains("call i32 @__glagol_random_i32()") + && !stdout.contains("@std.random"), + "standard random LLVM shape drifted\nstdout:\n{}", + stdout + ); +} + +#[test] +fn test_runner_executes_random_i32_as_non_negative() { + let fixture = write_fixture( + "test-runner", + r#" +(module main) + +(test "random i32 is non-negative" + (if (< (std.random.i32) 0) + false + true)) +"#, + ); + let output = run_glagol([OsStr::new("test"), fixture.as_os_str()]); + assert_success("run standard random tests", &output); + assert_eq!( + String::from_utf8_lossy(&output.stdout), + concat!( + "test \"random i32 is non-negative\" ... ok\n", + "1 test(s) passed\n", + ), + "standard random test runner stdout drifted" + ); +} + +#[test] +fn standard_random_diagnostics_cover_promoted_and_deferred_names() { + let cases = [ + ( + "random-arity", + r#" +(module main) + +(fn main () -> i32 + (std.random.i32 1)) +"#, + "ArityMismatch", + ), + ( + "random-bool-context", + r#" +(module main) + +(fn main () -> i32 + (if (std.random.i32) 1 0)) +"#, + "IfConditionNotBool", + ), + ( + "unknown-random", + r#" +(module main) + +(fn main () -> i32 + (std.random.range 0 10)) +"#, + "UnsupportedStandardLibraryCall", + ), + ( + "promoted-shadow", + r#" +(module main) + +(fn std.random.i32 () -> i32 + 0) + +(fn main () -> i32 + (std.random.i32)) +"#, + "DuplicateFunction", + ), + ]; + + for (name, source, diagnostic) in cases { + let fixture = write_fixture(name, source); + let output = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + !output.status.success(), + "compiler unexpectedly accepted `{}`\nstdout:\n{}\nstderr:\n{}", + name, + stdout, + stderr + ); + assert!( + stdout.is_empty(), + "rejected compile wrote stdout:\n{}", + stdout + ); + assert!( + stderr.contains(diagnostic), + "diagnostic `{}` was not reported for `{}`\nstderr:\n{}", + diagnostic, + name, + stderr + ); + } +} + +#[test] +fn hosted_runtime_smoke_when_clang_is_available() { + let Some(clang) = find_clang() else { + eprintln!("skipping standard random runtime smoke: set GLAGOL_CLANG or install clang"); + return; + }; + + let smoke = write_fixture( + "runtime-smoke", + r#" +(module main) + +(fn main () -> i32 + (if (< (std.random.i32) 0) 1 0)) +"#, + ); + let compile = run_glagol([smoke.as_os_str()]); + assert_success("compile standard random runtime smoke", &compile); + let run = compile_and_run_with_runtime(&clang, "standard-random-smoke", &compile.stdout); + assert_success("run standard random runtime smoke", &run); +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn write_fixture(name: &str, source: &str) -> PathBuf { + let mut path = env::temp_dir(); + path.push(format!( + "glagol-standard-random-{}-{}-{}.slo", + name, + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + fs::write(&path, source).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err)); + path +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); +} + +fn compile_and_run_with_runtime(clang: &Path, name: &str, ir: &[u8]) -> Output { + let manifest = Path::new(env!("CARGO_MANIFEST_DIR")); + let temp_dir = env::temp_dir().join(format!( + "glagol-standard-random-{}-{}", + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + fs::create_dir_all(&temp_dir) + .unwrap_or_else(|err| panic!("create `{}`: {}", temp_dir.display(), err)); + + let ir_path = temp_dir.join(format!("{}.ll", name)); + let exe_path = temp_dir.join(name); + fs::write(&ir_path, ir).unwrap_or_else(|err| panic!("write `{}`: {}", ir_path.display(), err)); + + let runtime = manifest.join("../runtime/runtime.c"); + let mut clang_command = Command::new(clang); + clang_command + .arg(&runtime) + .arg(&ir_path) + .arg("-o") + .arg(&exe_path) + .current_dir(manifest); + configure_clang_runtime_env(&mut clang_command, clang); + let clang_output = clang_command + .output() + .unwrap_or_else(|err| panic!("run `{}`: {}", clang.display(), err)); + assert_success("clang standard random runtime smoke", &clang_output); + + Command::new(&exe_path) + .output() + .unwrap_or_else(|err| panic!("run `{}`: {}", exe_path.display(), err)) +} + +fn find_clang() -> Option { + if let Some(path) = env::var_os("GLAGOL_CLANG").filter(|value| !value.is_empty()) { + return Some(PathBuf::from(path)); + } + + let hermetic_clang = PathBuf::from("/tmp/glagol-clang-root/usr/bin/clang"); + if hermetic_clang.is_file() { + return Some(hermetic_clang); + } + + find_on_path("clang") +} + +fn find_on_path(program: &str) -> Option { + let path = env::var_os("PATH")?; + env::split_paths(&path) + .map(|dir| dir.join(program)) + .find(|candidate| candidate.is_file()) +} + +fn configure_clang_runtime_env(command: &mut Command, clang: &Path) { + if !clang.starts_with("/tmp/glagol-clang-root") { + return; + } + + let root = Path::new("/tmp/glagol-clang-root"); + let lib64 = root.join("usr/lib64"); + let lib = root.join("usr/lib"); + let mut paths = vec![lib64, lib]; + + if let Some(existing) = env::var_os("LD_LIBRARY_PATH") { + paths.extend(env::split_paths(&existing)); + } + + let joined = env::join_paths(paths).expect("join LD_LIBRARY_PATH"); + command.env("LD_LIBRARY_PATH", joined); +} diff --git a/compiler/tests/standard_random_source_facade_alpha.rs b/compiler/tests/standard_random_source_facade_alpha.rs new file mode 100644 index 0000000..85418cc --- /dev/null +++ b/compiler/tests/standard_random_source_facade_alpha.rs @@ -0,0 +1,142 @@ +use std::{ + ffi::OsStr, + fs, + path::Path, + process::{Command, Output}, +}; + +const EXPECTED_TEST_OUTPUT: &str = concat!( + "test \"local random i32 non-negative facade\" ... ok\n", + "test \"explicit local random i32 non-negative facade\" ... ok\n", + "test \"explicit local random facade all\" ... ok\n", + "3 test(s) passed\n", +); + +const STANDARD_RANDOM_SOURCE_FACADE_ALPHA: &[&str] = &["random_i32", "random_i32_non_negative"]; + +#[test] +fn standard_random_source_facade_project_checks_formats_and_tests() { + let project = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/projects/std-layout-local-random"); + + assert_local_random_fixture_is_source_authored(&project); + + let fmt = run_glagol([ + OsStr::new("fmt"), + OsStr::new("--check"), + project.as_os_str(), + ]); + assert_success("std layout local random fmt --check", &fmt); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std layout local random check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + test, + EXPECTED_TEST_OUTPUT, + "std layout local random test output", + ); +} + +fn assert_local_random_fixture_is_source_authored(project: &Path) { + let random = read(&project.join("src/random.slo")); + let main = read(&project.join("src/main.slo")); + + assert!( + random.starts_with("(module random (export "), + "random.slo must stay an explicit local module export" + ); + assert!( + main.starts_with("(module main)\n\n(import random ("), + "main.slo must stay an explicit local random import" + ); + assert!( + !main.contains("(import std") && !main.contains("(import slovo.std"), + "random source facade fixture must not depend on automatic or package std imports" + ); + assert!( + random.contains("(std.random.i32)"), + "random.slo must use the existing promoted std.random.i32 call" + ); + let non_random_std = random.replace("std.random.i32", ""); + assert!( + !non_random_std.contains("std.") && !main.contains("std."), + "random source facade fixture must not introduce other compiler-known std names" + ); + assert!( + !random.contains("seed") + && !random.contains("range") + && !random.contains("bytes") + && !random.contains("float") + && !random.contains("uuid") + && !random.contains("crypto"), + "random source facade fixture must not claim deferred random APIs" + ); + assert!( + random.contains("(fn random_i32 () -> i32\n (std.random.i32))"), + "random_i32 must stay a flat source facade over std.random.i32" + ); + assert!( + random.contains("(fn random_i32_non_negative () -> bool\n (>= (random_i32) 0))"), + "random_i32_non_negative must stay a deterministic non-negative check" + ); + + for helper in STANDARD_RANDOM_SOURCE_FACADE_ALPHA { + assert!( + random.contains(&format!("(fn {} ", helper)), + "random.slo is missing source facade `{}`", + helper + ); + assert!( + main.contains(helper), + "main.slo does not explicitly import/use `{}`", + helper + ); + } +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn read(path: &Path) -> String { + fs::read_to_string(path).unwrap_or_else(|err| panic!("read `{}`: {}", path.display(), err)) +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} + +fn assert_success_stdout(output: Output, expected: &str, context: &str) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert_eq!(stdout, expected, "{} stdout drifted", context); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} diff --git a/compiler/tests/standard_result_option_source_search_alpha.rs b/compiler/tests/standard_result_option_source_search_alpha.rs new file mode 100644 index 0000000..b73c298 --- /dev/null +++ b/compiler/tests/standard_result_option_source_search_alpha.rs @@ -0,0 +1,223 @@ +use std::{ + ffi::OsStr, + fs, + path::Path, + process::{Command, Output}, +}; + +const EXPECTED_STD_RESULT_OUTPUT: &str = concat!( + "test \"explicit std result i32 wrappers\" ... ok\n", + "test \"explicit std result err wrappers\" ... ok\n", + "test \"explicit std result unwrap_or i32\" ... ok\n", + "test \"explicit std result ok_or_none i32 ok\" ... ok\n", + "test \"explicit std result ok_or_none i32 none\" ... ok\n", + "test \"explicit std result u32 wrappers\" ... ok\n", + "test \"explicit std result unwrap_or u32\" ... ok\n", + "test \"explicit std result ok_or_none u32 ok\" ... ok\n", + "test \"explicit std result ok_or_none u32 none\" ... ok\n", + "test \"explicit std result unwrap_or i64\" ... ok\n", + "test \"explicit std result ok_or_none i64 ok\" ... ok\n", + "test \"explicit std result ok_or_none i64 none\" ... ok\n", + "test \"explicit std result u64 wrappers\" ... ok\n", + "test \"explicit std result unwrap_or u64\" ... ok\n", + "test \"explicit std result ok_or_none u64 ok\" ... ok\n", + "test \"explicit std result ok_or_none u64 none\" ... ok\n", + "test \"explicit std result unwrap_or string\" ... ok\n", + "test \"explicit std result ok_or_none string ok\" ... ok\n", + "test \"explicit std result ok_or_none string none\" ... ok\n", + "test \"explicit std result unwrap_or f64\" ... ok\n", + "test \"explicit std result ok_or_none f64 ok\" ... ok\n", + "test \"explicit std result ok_or_none f64 none\" ... ok\n", + "test \"explicit std result bool helpers\" ... ok\n", + "test \"explicit std result ok_or_none bool ok\" ... ok\n", + "test \"explicit std result ok_or_none bool none\" ... ok\n", + "test \"explicit std result helpers all\" ... ok\n", + "26 test(s) passed\n", +); + +const EXPECTED_STD_OPTION_OUTPUT: &str = concat!( + "test \"explicit std option i32 observation\" ... ok\n", + "test \"explicit std option i32 unwrap_some\" ... ok\n", + "test \"explicit std option i32 unwrap_or\" ... ok\n", + "test \"explicit std option i32 some_or_err ok\" ... ok\n", + "test \"explicit std option i32 some_or_err err\" ... ok\n", + "test \"explicit std option u32 observation\" ... ok\n", + "test \"explicit std option u32 unwrap_some\" ... ok\n", + "test \"explicit std option u32 unwrap_or\" ... ok\n", + "test \"explicit std option u32 some_or_err ok\" ... ok\n", + "test \"explicit std option u32 some_or_err err\" ... ok\n", + "test \"explicit std option i64 observation\" ... ok\n", + "test \"explicit std option i64 unwrap_some\" ... ok\n", + "test \"explicit std option i64 unwrap_or\" ... ok\n", + "test \"explicit std option i64 some_or_err ok\" ... ok\n", + "test \"explicit std option i64 some_or_err err\" ... ok\n", + "test \"explicit std option u64 observation\" ... ok\n", + "test \"explicit std option u64 unwrap_some\" ... ok\n", + "test \"explicit std option u64 unwrap_or\" ... ok\n", + "test \"explicit std option u64 some_or_err ok\" ... ok\n", + "test \"explicit std option u64 some_or_err err\" ... ok\n", + "test \"explicit std option f64 observation\" ... ok\n", + "test \"explicit std option f64 unwrap_some\" ... ok\n", + "test \"explicit std option f64 unwrap_or\" ... ok\n", + "test \"explicit std option f64 some_or_err ok\" ... ok\n", + "test \"explicit std option f64 some_or_err err\" ... ok\n", + "test \"explicit std option bool observation\" ... ok\n", + "test \"explicit std option bool unwrap_some\" ... ok\n", + "test \"explicit std option bool unwrap_or\" ... ok\n", + "test \"explicit std option bool some_or_err ok\" ... ok\n", + "test \"explicit std option bool some_or_err err\" ... ok\n", + "test \"explicit std option string observation\" ... ok\n", + "test \"explicit std option string unwrap_some\" ... ok\n", + "test \"explicit std option string unwrap_or\" ... ok\n", + "test \"explicit std option string some_or_err ok\" ... ok\n", + "test \"explicit std option string some_or_err err\" ... ok\n", + "test \"explicit std option helpers all\" ... ok\n", + "36 test(s) passed\n", +); + +const STANDARD_OPTION_RESULT_BRIDGE_HELPERS_ALPHA: &[&str] = &[ + "some_or_err_i32", + "some_or_err_u32", + "some_or_err_i64", + "some_or_err_u64", + "some_or_err_f64", + "some_or_err_bool", + "some_or_err_string", +]; + +const STANDARD_RESULT_OPTION_BRIDGE_HELPERS_ALPHA: &[&str] = &[ + "ok_or_none_i32", + "ok_or_none_u32", + "ok_or_none_i64", + "ok_or_none_u64", + "ok_or_none_string", + "ok_or_none_f64", + "ok_or_none_bool", +]; + +#[test] +fn explicit_std_result_import_loads_repo_root_standard_source() { + let compiler_root = Path::new(env!("CARGO_MANIFEST_DIR")); + let project = compiler_root.join("../examples/projects/std-import-result"); + let slovo_result = compiler_root.join("../lib/std/result.slo"); + + assert!( + !project.join("src/result.slo").exists(), + "std-import-result must not carry a local result module copy" + ); + assert!( + !project.join("src/bridge.slo").exists(), + "std-import-result must not depend on a local bridge shim" + ); + assert!( + read(&project.join("src/main.slo")).starts_with("(module main)\n\n(import std.result ("), + "std-import-result must exercise explicit `std.result` import syntax" + ); + assert!( + read(&slovo_result).starts_with("(module result (export "), + "repo-root Slovo std/result.slo must export imported helpers directly" + ); + for helper in STANDARD_RESULT_OPTION_BRIDGE_HELPERS_ALPHA { + assert!( + read(&slovo_result).contains(&format!("(fn {} ", helper)), + "repo-root Slovo std/result.slo is missing bridge helper `{}`", + helper + ); + assert!( + read(&project.join("src/main.slo")).contains(helper), + "std-import-result must import/use bridge helper `{}`", + helper + ); + } + + assert_project_formats_checks_and_tests(&project, EXPECTED_STD_RESULT_OUTPUT); +} + +#[test] +fn explicit_std_option_import_loads_repo_root_standard_source() { + let compiler_root = Path::new(env!("CARGO_MANIFEST_DIR")); + let project = compiler_root.join("../examples/projects/std-import-option"); + let slovo_option = compiler_root.join("../lib/std/option.slo"); + + assert!( + !project.join("src/option.slo").exists(), + "std-import-option must not carry a local option module copy" + ); + assert!( + !project.join("src/bridge.slo").exists(), + "std-import-option must not depend on a local bridge shim" + ); + assert!( + read(&project.join("src/main.slo")).starts_with("(module main)\n\n(import std.option ("), + "std-import-option must exercise explicit `std.option` import syntax" + ); + assert!( + read(&slovo_option).starts_with("(module option (export "), + "repo-root Slovo std/option.slo must export imported helpers directly" + ); + for helper in STANDARD_OPTION_RESULT_BRIDGE_HELPERS_ALPHA { + assert!( + read(&slovo_option).contains(&format!("(fn {} ", helper)), + "repo-root Slovo std/option.slo is missing bridge helper `{}`", + helper + ); + assert!( + read(&project.join("src/main.slo")).contains(helper), + "std-import-option must import/use bridge helper `{}`", + helper + ); + } + + assert_project_formats_checks_and_tests(&project, EXPECTED_STD_OPTION_OUTPUT); +} + +fn assert_project_formats_checks_and_tests(project: &Path, expected: &str) { + let fmt = run_glagol([ + OsStr::new("fmt"), + OsStr::new("--check"), + project.as_os_str(), + ]); + assert_success("std source search fmt --check", &fmt); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std source search check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout(test, expected, "std source search test"); +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn read(path: &Path) -> String { + fs::read_to_string(path).unwrap_or_else(|err| panic!("read `{}`: {}", path.display(), err)) +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstatus: {:?}\nstdout:\n{}\nstderr:\n{}", + context, + output.status.code(), + stdout, + stderr + ); +} + +fn assert_success_stdout(output: Output, expected: &str, context: &str) { + assert_success(context, &output); + let stdout = String::from_utf8_lossy(&output.stdout); + assert_eq!(stdout, expected, "{}", context); +} diff --git a/compiler/tests/standard_result_source_helpers_alpha.rs b/compiler/tests/standard_result_source_helpers_alpha.rs new file mode 100644 index 0000000..927a455 --- /dev/null +++ b/compiler/tests/standard_result_source_helpers_alpha.rs @@ -0,0 +1,235 @@ +use std::{ + ffi::OsStr, + fs, + path::Path, + process::{Command, Output}, +}; + +const EXPECTED_TEST_OUTPUT: &str = concat!( + "test \"explicit local result i32 wrappers\" ... ok\n", + "test \"explicit local result err wrappers\" ... ok\n", + "test \"explicit local result unwrap_or i32\" ... ok\n", + "test \"explicit local result ok_or_none i32 ok\" ... ok\n", + "test \"explicit local result ok_or_none i32 none\" ... ok\n", + "test \"explicit local result u32 wrappers\" ... ok\n", + "test \"explicit local result unwrap_or u32\" ... ok\n", + "test \"explicit local result ok_or_none u32 ok\" ... ok\n", + "test \"explicit local result ok_or_none u32 none\" ... ok\n", + "test \"explicit local result unwrap_or i64\" ... ok\n", + "test \"explicit local result ok_or_none i64 ok\" ... ok\n", + "test \"explicit local result ok_or_none i64 none\" ... ok\n", + "test \"explicit local result u64 wrappers\" ... ok\n", + "test \"explicit local result unwrap_or u64\" ... ok\n", + "test \"explicit local result ok_or_none u64 ok\" ... ok\n", + "test \"explicit local result ok_or_none u64 none\" ... ok\n", + "test \"explicit local result unwrap_or string\" ... ok\n", + "test \"explicit local result ok_or_none string ok\" ... ok\n", + "test \"explicit local result ok_or_none string none\" ... ok\n", + "test \"explicit local result unwrap_or f64\" ... ok\n", + "test \"explicit local result ok_or_none f64 ok\" ... ok\n", + "test \"explicit local result ok_or_none f64 none\" ... ok\n", + "test \"explicit local result bool helpers\" ... ok\n", + "test \"explicit local result ok_or_none bool ok\" ... ok\n", + "test \"explicit local result ok_or_none bool none\" ... ok\n", + "test \"explicit local result helpers all\" ... ok\n", + "26 test(s) passed\n", +); + +const STANDARD_RESULT_SOURCE_HELPERS_ALPHA: &[&str] = &[ + "ok_i32", + "err_i32", + "is_ok_i32", + "is_err_i32", + "unwrap_ok_i32", + "unwrap_err_i32", + "unwrap_or_i32", + "ok_u32", + "err_u32", + "is_ok_u32", + "is_err_u32", + "unwrap_ok_u32", + "unwrap_err_u32", + "unwrap_or_u32", + "ok_i64", + "err_i64", + "is_err_i64", + "unwrap_err_i64", + "unwrap_or_i64", + "ok_u64", + "err_u64", + "is_ok_u64", + "is_err_u64", + "unwrap_ok_u64", + "unwrap_err_u64", + "unwrap_or_u64", + "ok_string", + "err_string", + "is_err_string", + "unwrap_err_string", + "unwrap_or_string", + "ok_f64", + "err_f64", + "is_err_f64", + "unwrap_err_f64", + "unwrap_or_f64", + "ok_bool", + "err_bool", + "is_ok_bool", + "is_err_bool", + "unwrap_ok_bool", + "unwrap_err_bool", + "unwrap_or_bool", +]; + +const STANDARD_RESULT_OPTION_BRIDGE_HELPERS_ALPHA: &[&str] = &[ + "ok_or_none_i32", + "ok_or_none_u32", + "ok_or_none_i64", + "ok_or_none_u64", + "ok_or_none_string", + "ok_or_none_f64", + "ok_or_none_bool", +]; + +#[test] +fn standard_result_source_helper_project_checks_formats_and_tests() { + let project = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/projects/std-layout-local-result"); + + assert_local_result_fixture_is_source_authored(&project); + + let fmt = run_glagol([ + OsStr::new("fmt"), + OsStr::new("--check"), + project.as_os_str(), + ]); + assert_success("std layout local result fmt --check", &fmt); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std layout local result check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + test, + EXPECTED_TEST_OUTPUT, + "std layout local result test output", + ); +} + +fn assert_local_result_fixture_is_source_authored(project: &Path) { + let result = read(&project.join("src/result.slo")); + let main = read(&project.join("src/main.slo")); + + assert!( + result.starts_with("(module result (export "), + "result.slo must stay an explicit local module export" + ); + assert!( + main.starts_with("(module main)\n\n(import result ("), + "main.slo must stay an explicit local result import" + ); + assert!( + !main.contains("(import std") && !main.contains("(import slovo.std"), + "exp-33 fixture must not depend on automatic or package std imports" + ); + assert!( + !result.contains("std.result.unwrap_or") + && !result.contains("std.result.map") + && !result.contains("std.result.and_then") + && !main.contains("std.result.unwrap_or") + && !main.contains("std.result.map") + && !main.contains("std.result.and_then"), + "exp-33 fixture must keep deferred generic result helpers out of source" + ); + + let mut non_result_std = result.clone(); + for allowed in [ + "std.result.is_ok", + "std.result.is_err", + "std.result.unwrap_ok", + "std.result.unwrap_err", + ] { + non_result_std = non_result_std.replace(allowed, ""); + } + assert!( + !non_result_std.contains("std."), + "result.slo helpers must use only existing std.result compiler-known names" + ); + assert!( + !result.contains("std.option.") + && !main.contains("std.option.") + && !main.contains("(import option"), + "exp-109 fixture must bridge through raw concrete option forms, not compiler-known std.option names or a separate local option module" + ); + + for helper in STANDARD_RESULT_SOURCE_HELPERS_ALPHA { + assert!( + result.contains(&format!("(fn {} ", helper)), + "result.slo is missing source helper `{}`", + helper + ); + assert!( + main.contains(helper), + "main.slo does not explicitly import/use `{}`", + helper + ); + } + + for helper in STANDARD_RESULT_OPTION_BRIDGE_HELPERS_ALPHA { + assert!( + result.contains(&format!("(fn {} ", helper)), + "result.slo is missing bridge helper `{}`", + helper + ); + assert!( + main.contains(helper), + "main.slo does not explicitly import/use bridge helper `{}`", + helper + ); + } +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn read(path: &Path) -> String { + fs::read_to_string(path).unwrap_or_else(|err| panic!("read `{}`: {}", path.display(), err)) +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} + +fn assert_success_stdout(output: Output, expected: &str, context: &str) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert_eq!(stdout, expected, "{} stdout drifted", context); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} diff --git a/compiler/tests/standard_runtime_conformance_alignment.rs b/compiler/tests/standard_runtime_conformance_alignment.rs new file mode 100644 index 0000000..68fc526 --- /dev/null +++ b/compiler/tests/standard_runtime_conformance_alignment.rs @@ -0,0 +1,236 @@ +use std::{ + fs, + path::{Path, PathBuf}, + process::{Command, Output}, + sync::atomic::{AtomicUsize, Ordering}, +}; + +static NEXT_ID: AtomicUsize = AtomicUsize::new(0); + +#[test] +fn exp14_project_exercises_released_standard_runtime_surface() { + let project = write_conformance_project(); + + let fmt_check = run_glagol(["fmt".as_ref(), "--check".as_ref(), project.as_os_str()]); + assert_success("exp-14 project fmt --check", &fmt_check); + + let check = run_glagol(["check".as_ref(), project.as_os_str()]); + assert_success("exp-14 project check", &check); + + let test = run_glagol(["test".as_ref(), project.as_os_str()]); + assert_success("exp-14 project test", &test); + assert_eq!( + String::from_utf8_lossy(&test.stdout), + concat!( + "test \"provider imported base\" ... ok\n", + "test \"concat parse result match\" ... ok\n", + "test \"vec i32 imported module value\" ... ok\n", + "test \"vec i64 imported runtime value\" ... ok\n", + "test \"payloadless enum match\" ... ok\n", + "test \"time sleep zero preserves score\" ... ok\n", + "6 test(s) passed\n", + ) + ); + + let docs = unique_path("docs"); + let doc = run_glagol([ + "doc".as_ref(), + project.as_os_str(), + "-o".as_ref(), + docs.as_os_str(), + ]); + assert_success("exp-14 project docs", &doc); + let doc_index = fs::read_to_string(docs.join("index.md")).expect("read exp-14 docs"); + assert!(doc_index.contains("## Module math")); + assert!(doc_index.contains("## Module main")); + assert!(doc_index.contains("- `main")); + assert!(doc_index.contains("- `concat parse result match`")); + + let binary = unique_path("bin"); + let build = run_glagol([ + "build".as_ref(), + project.as_os_str(), + "-o".as_ref(), + binary.as_os_str(), + ]); + if build.status.success() { + let run = Command::new(&binary).output().expect("run exp-14 binary"); + assert_success("exp-14 project binary", &run); + assert_eq!(String::from_utf8_lossy(&run.stdout), "85\n"); + assert!( + run.stderr.is_empty(), + "exp-14 project binary wrote stderr:\n{}", + String::from_utf8_lossy(&run.stderr) + ); + } else { + assert!( + build.stdout.is_empty(), + "failed exp-14 build wrote stdout:\n{}", + String::from_utf8_lossy(&build.stdout) + ); + assert_stderr_contains("exp-14 project build", &build, "ToolchainUnavailable"); + } +} + +#[test] +fn conformance_gate_script_exists_and_names_required_commands() { + let script = Path::new("../scripts/conformance-gate.sh"); + let text = fs::read_to_string(script).expect("read conformance gate script"); + + assert!(text.starts_with("#!/usr/bin/env bash\nset -euo pipefail\n")); + assert!(text.contains("scripts/release-gate.sh remains the full")); + assert!(text.contains("git -C \"${repo_root}\" diff --check")); + assert!(text.contains("cargo fmt --check")); + assert!(text.contains("cargo test --test standard_runtime_conformance_alignment")); + assert!(text.contains("cargo test --test standard_time")); + assert!(text.contains("cargo test --test promotion_gate promotion_gate_artifacts_are_aligned")); +} + +fn write_conformance_project() -> PathBuf { + let project = unique_path("project"); + fs::create_dir_all(project.join("src")).expect("create exp-14 project src"); + fs::write( + project.join("slovo.toml"), + format!( + "[project]\nname = \"{}\"\nsource_root = \"src\"\nentry = \"main\"\n", + project.file_name().unwrap().to_string_lossy() + ), + ) + .expect("write exp-14 manifest"); + fs::write( + project.join("src/math.slo"), + concat!( + "(module math (export imported_base))\n", + "\n", + "(fn imported_base () -> i32\n", + " 20)\n", + "\n", + "(test \"provider imported base\"\n", + " (= (imported_base) 20))\n", + ), + ) + .expect("write exp-14 math module"); + fs::write( + project.join("src/main.slo"), + concat!( + "(module main)\n", + "\n", + "(import math (imported_base))\n", + "\n", + "(enum Signal Red Green)\n", + "\n", + "(fn concat_digits () -> string\n", + " (std.string.concat \"4\" \"2\"))\n", + "\n", + "(fn parse_digits () -> (result i32 i32)\n", + " (std.string.parse_i32_result (concat_digits)))\n", + "\n", + "(fn parsed_or_code () -> i32\n", + " (match (parse_digits)\n", + " ((ok value)\n", + " value)\n", + " ((err code)\n", + " code)))\n", + "\n", + "(fn pair () -> (vec i32)\n", + " (let values (vec i32) (std.vec.i32.empty))\n", + " (let first (vec i32) (std.vec.i32.append values (imported_base)))\n", + " (std.vec.i32.append first (parsed_or_code)))\n", + "\n", + "(fn pair_i64 () -> (vec i64)\n", + " (let values (vec i64) (std.vec.i64.empty))\n", + " (let first (vec i64) (std.vec.i64.append values 40i64))\n", + " (std.vec.i64.append first 41i64))\n", + "\n", + "(fn vec_i64_bonus () -> i32\n", + " (match (std.num.i64_to_i32_result (std.vec.i64.index (pair_i64) 1))\n", + " ((ok value)\n", + " value)\n", + " ((err code)\n", + " code)))\n", + "\n", + "(fn signal_code ((signal Signal)) -> i32\n", + " (match signal\n", + " ((Signal.Red)\n", + " 1)\n", + " ((Signal.Green)\n", + " 2)))\n", + "\n", + "(fn score () -> i32\n", + " (+ (+ (std.vec.i32.index (pair) 1) (signal_code (Signal.Green))) (vec_i64_bonus)))\n", + "\n", + "(fn sleep_zero_then_score () -> i32\n", + " (std.time.sleep_ms 0)\n", + " (score))\n", + "\n", + "(test \"concat parse result match\"\n", + " (= (parsed_or_code) 42))\n", + "\n", + "(test \"vec i32 imported module value\"\n", + " (= (std.vec.i32.index (pair) 0) 20))\n", + "\n", + "(test \"vec i64 imported runtime value\"\n", + " (= (vec_i64_bonus) 41))\n", + "\n", + "(test \"payloadless enum match\"\n", + " (= (signal_code (Signal.Green)) 2))\n", + "\n", + "(test \"time sleep zero preserves score\"\n", + " (= (sleep_zero_then_score) 85))\n", + "\n", + "(fn main () -> i32\n", + " (std.io.print_i32 (sleep_zero_then_score))\n", + " 0)\n", + ), + ) + .expect("write exp-14 main module"); + + project +} + +fn unique_path(name: &str) -> PathBuf { + let id = NEXT_ID.fetch_add(1, Ordering::Relaxed); + let nanos = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .expect("system clock before UNIX_EPOCH") + .as_nanos(); + std::env::temp_dir().join(format!( + "glagol-exp14-{}-{}-{}-{}", + std::process::id(), + nanos, + id, + name + )) +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .output() + .expect("run glagol") +} + +fn assert_success(context: &str, output: &Output) { + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); +} + +fn assert_stderr_contains(context: &str, output: &Output, needle: &str) { + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + stderr.contains(needle), + "{} stderr did not contain `{}`:\n{}", + context, + needle, + stderr + ); +} diff --git a/compiler/tests/standard_string_source_fallback_helpers_alpha.rs b/compiler/tests/standard_string_source_fallback_helpers_alpha.rs new file mode 100644 index 0000000..1289b9a --- /dev/null +++ b/compiler/tests/standard_string_source_fallback_helpers_alpha.rs @@ -0,0 +1,201 @@ +use std::{ + ffi::OsStr, + fs, + path::Path, + process::{Command, Output}, +}; + +const EXPECTED_TEST_OUTPUT: &str = concat!( + "test \"explicit local string len concat\" ... ok\n", + "test \"explicit local string parse result wrappers\" ... ok\n", + "test \"explicit local string parse option wrappers\" ... ok\n", + "test \"explicit local string parse integer fallbacks\" ... ok\n", + "test \"explicit local string parse float bool fallbacks\" ... ok\n", + "test \"explicit local string parse custom fallbacks\" ... ok\n", + "test \"explicit local string helpers all\" ... ok\n", + "7 test(s) passed\n", +); + +const STANDARD_STRING_SOURCE_FALLBACK_HELPERS_ALPHA: &[&str] = &[ + "len", + "concat", + "parse_i32_result", + "parse_i32_option", + "parse_u32_result", + "parse_u32_option", + "parse_i64_result", + "parse_i64_option", + "parse_u64_result", + "parse_u64_option", + "parse_f64_result", + "parse_f64_option", + "parse_bool_result", + "parse_bool_option", + "parse_i32_or_zero", + "parse_u32_or_zero", + "parse_i64_or_zero", + "parse_u64_or_zero", + "parse_f64_or_zero", + "parse_bool_or_false", + "parse_i32_or", + "parse_u32_or", + "parse_i64_or", + "parse_u64_or", + "parse_f64_or", + "parse_bool_or", +]; + +#[test] +fn standard_string_source_fallback_helper_project_checks_formats_and_tests() { + let project = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/projects/std-layout-local-string"); + + assert_local_string_fixture_is_source_authored(&project); + + let fmt = run_glagol([ + OsStr::new("fmt"), + OsStr::new("--check"), + project.as_os_str(), + ]); + assert_success("std layout local string fmt --check", &fmt); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std layout local string check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + test, + EXPECTED_TEST_OUTPUT, + "std layout local string test output", + ); +} + +fn assert_local_string_fixture_is_source_authored(project: &Path) { + let string = read(&project.join("src/string.slo")); + let result = read(&project.join("src/result.slo")); + let main = read(&project.join("src/main.slo")); + + assert!( + string.starts_with("(module string (export "), + "string.slo must stay an explicit local module export" + ); + assert!( + main.starts_with("(module main)\n\n(import string ("), + "main.slo must stay an explicit local string import" + ); + assert!( + result.starts_with("(module result (export "), + "result.slo must stay an explicit local result module export" + ); + assert!( + !main.contains("(import std") && !main.contains("(import slovo.std"), + "string fixture must not depend on automatic or package std imports" + ); + + let mut non_string_std = string.clone(); + for allowed in [ + "std.string.parse_bool_result", + "std.string.parse_f64_result", + "std.string.parse_i64_result", + "std.string.parse_u64_result", + "std.string.parse_i32_result", + "std.string.parse_u32_result", + "std.string.concat", + "std.string.len", + ] { + non_string_std = non_string_std.replace(allowed, ""); + } + assert!( + !non_string_std.contains("std.") && !main.contains("std."), + "string fixture must use only the existing promoted std.string runtime names" + ); + assert!( + !string.contains("trim") + && !string.contains("locale") + && !string.contains("unicode") + && !string.contains("bytes") + && !string.contains("case_insensitive") + && !string.contains("host_error"), + "string fixture must not claim deferred parsing or richer error APIs" + ); + + assert!( + string.contains( + "(import result (ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool))" + ), + "string fixture must explicitly import the local result bridge helpers" + ); + + for helper in [ + "ok_or_none_i32", + "ok_or_none_u32", + "ok_or_none_i64", + "ok_or_none_u64", + "ok_or_none_f64", + "ok_or_none_bool", + ] { + assert!( + result.contains(&format!("(fn {} ", helper)), + "result.slo is missing local result bridge helper `{}`", + helper + ); + } + + for helper in STANDARD_STRING_SOURCE_FALLBACK_HELPERS_ALPHA { + assert!( + string.contains(&format!("(fn {} ", helper)), + "string.slo is missing source facade `{}`", + helper + ); + assert!( + main.contains(helper), + "main.slo does not explicitly import/use `{}`", + helper + ); + } +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn read(path: &Path) -> String { + fs::read_to_string(path).unwrap_or_else(|err| panic!("read `{}`: {}", path.display(), err)) +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} + +fn assert_success_stdout(output: Output, expected: &str, context: &str) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert_eq!(stdout, expected, "{} stdout drifted", context); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} diff --git a/compiler/tests/standard_time.rs b/compiler/tests/standard_time.rs new file mode 100644 index 0000000..c23c56d --- /dev/null +++ b/compiler/tests/standard_time.rs @@ -0,0 +1,356 @@ +use std::{ + env, + ffi::OsStr, + fs, + path::{Path, PathBuf}, + process::{Command, Output}, + sync::atomic::{AtomicUsize, Ordering}, +}; + +static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0); + +#[test] +fn standard_time_lowers_to_private_runtime_helpers() { + let fixture = write_fixture( + "lowering", + r#" +(module main) + +(fn main () -> i32 + (std.time.sleep_ms 0) + (std.time.monotonic_ms)) +"#, + ); + let output = run_glagol([fixture.as_os_str()]); + assert_success("compile standard time lowering", &output); + let stdout = String::from_utf8_lossy(&output.stdout); + + assert!( + stdout.contains("declare i32 @__glagol_time_monotonic_ms()") + && stdout.contains("declare void @__glagol_time_sleep_ms(i32)") + && stdout.contains("call void @__glagol_time_sleep_ms(i32 0)") + && stdout.contains("call i32 @__glagol_time_monotonic_ms()") + && !stdout.contains("@std.time."), + "standard time LLVM shape drifted\nstdout:\n{}", + stdout + ); +} + +#[test] +fn test_runner_executes_monotonic_and_sleep_zero() { + let fixture = write_fixture( + "test-runner", + r#" +(module main) + +(fn sleep_zero_then_one () -> i32 + (std.time.sleep_ms 0) + 1) + +(test "monotonic is non-negative" + (>= (std.time.monotonic_ms) 0)) + +(test "sleep zero returns" + (= (sleep_zero_then_one) 1)) +"#, + ); + let output = run_glagol([OsStr::new("test"), fixture.as_os_str()]); + assert_success("run standard time tests", &output); + assert_eq!( + String::from_utf8_lossy(&output.stdout), + concat!( + "test \"monotonic is non-negative\" ... ok\n", + "test \"sleep zero returns\" ... ok\n", + "2 test(s) passed\n", + ), + "standard time test runner stdout drifted" + ); +} + +#[test] +fn test_runner_reports_negative_sleep_trap() { + let fixture = write_fixture( + "negative-sleep-test-runner", + r#" +(module main) + +(fn bad_sleep () -> i32 + (std.time.sleep_ms -1) + 0) + +(test "negative sleep traps" + (= (bad_sleep) 0)) +"#, + ); + let output = run_glagol([OsStr::new("test"), fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert_eq!( + output.status.code(), + Some(1), + "negative sleep trap exit code drifted\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.is_empty(), + "negative sleep trap wrote stdout:\n{}", + stdout + ); + assert!( + stderr.contains("TestRuntimeTrap") + && stderr.contains("slovo runtime error: sleep_ms negative duration"), + "negative sleep trap diagnostic drifted\nstderr:\n{}", + stderr + ); +} + +#[test] +fn standard_time_diagnostics_cover_promoted_and_unpromoted_names() { + let cases = [ + ( + "monotonic-arity", + r#" +(module main) + +(fn main () -> i32 + (std.time.monotonic_ms 1)) +"#, + "ArityMismatch", + ), + ( + "sleep-arity", + r#" +(module main) + +(fn main () -> i32 + (std.time.sleep_ms) + 0) +"#, + "ArityMismatch", + ), + ( + "sleep-type", + r#" +(module main) + +(fn main () -> i32 + (std.time.sleep_ms true) + 0) +"#, + "TypeMismatch", + ), + ( + "unknown-time", + r#" +(module main) + +(fn main () -> i32 + (std.time.now)) +"#, + "UnsupportedStandardLibraryCall", + ), + ( + "promoted-shadow", + r#" +(module main) + +(fn std.time.monotonic_ms () -> i32 + 0) + +(fn main () -> i32 + (std.time.monotonic_ms)) +"#, + "DuplicateFunction", + ), + ]; + + for (name, source, diagnostic) in cases { + let fixture = write_fixture(name, source); + let output = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + !output.status.success(), + "compiler unexpectedly accepted `{}`\nstdout:\n{}\nstderr:\n{}", + name, + stdout, + stderr + ); + assert!( + stdout.is_empty(), + "rejected compile wrote stdout:\n{}", + stdout + ); + assert!( + stderr.contains(diagnostic), + "diagnostic `{}` was not reported for `{}`\nstderr:\n{}", + diagnostic, + name, + stderr + ); + } +} + +#[test] +fn hosted_runtime_smoke_and_negative_sleep_trap_when_clang_is_available() { + let Some(clang) = find_clang() else { + eprintln!("skipping standard time runtime smoke: set GLAGOL_CLANG or install clang"); + return; + }; + + let smoke = write_fixture( + "runtime-smoke", + r#" +(module main) + +(fn main () -> i32 + (std.time.sleep_ms 0) + (if (>= (std.time.monotonic_ms) 0) 0 1)) +"#, + ); + let compile = run_glagol([smoke.as_os_str()]); + assert_success("compile standard time runtime smoke", &compile); + let run = compile_and_run_with_runtime(&clang, "standard-time-smoke", &compile.stdout); + assert_success("run standard time runtime smoke", &run); + + let trap = write_fixture( + "runtime-negative-sleep", + r#" +(module main) + +(fn main () -> i32 + (std.time.sleep_ms -1) + 0) +"#, + ); + let compile = run_glagol([trap.as_os_str()]); + assert_success("compile standard time negative sleep trap", &compile); + let run = compile_and_run_with_runtime(&clang, "standard-time-negative-sleep", &compile.stdout); + assert_eq!( + run.status.code(), + Some(1), + "negative sleep runtime exit code drifted\nstdout:\n{}\nstderr:\n{}", + String::from_utf8_lossy(&run.stdout), + String::from_utf8_lossy(&run.stderr) + ); + assert_eq!( + String::from_utf8_lossy(&run.stderr), + "slovo runtime error: sleep_ms negative duration\n", + "negative sleep runtime stderr drifted" + ); + assert!( + run.stdout.is_empty(), + "negative sleep runtime wrote stdout:\n{}", + String::from_utf8_lossy(&run.stdout) + ); +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn write_fixture(name: &str, source: &str) -> PathBuf { + let mut path = env::temp_dir(); + path.push(format!( + "glagol-standard-time-{}-{}-{}.slo", + name, + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + fs::write(&path, source).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err)); + path +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); +} + +fn compile_and_run_with_runtime(clang: &Path, name: &str, ir: &[u8]) -> Output { + let manifest = Path::new(env!("CARGO_MANIFEST_DIR")); + let temp_dir = env::temp_dir().join(format!( + "glagol-standard-time-{}-{}", + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + fs::create_dir_all(&temp_dir) + .unwrap_or_else(|err| panic!("create `{}`: {}", temp_dir.display(), err)); + + let ir_path = temp_dir.join(format!("{}.ll", name)); + let exe_path = temp_dir.join(name); + fs::write(&ir_path, ir).unwrap_or_else(|err| panic!("write `{}`: {}", ir_path.display(), err)); + + let runtime = manifest.join("../runtime/runtime.c"); + let mut clang_command = Command::new(clang); + clang_command + .arg(&runtime) + .arg(&ir_path) + .arg("-o") + .arg(&exe_path) + .current_dir(manifest); + configure_clang_runtime_env(&mut clang_command, clang); + let clang_output = clang_command + .output() + .unwrap_or_else(|err| panic!("run `{}`: {}", clang.display(), err)); + assert_success("clang standard time runtime smoke", &clang_output); + + Command::new(&exe_path) + .output() + .unwrap_or_else(|err| panic!("run `{}`: {}", exe_path.display(), err)) +} + +fn find_clang() -> Option { + if let Some(path) = env::var_os("GLAGOL_CLANG").filter(|value| !value.is_empty()) { + return Some(PathBuf::from(path)); + } + + let hermetic_clang = PathBuf::from("/tmp/glagol-clang-root/usr/bin/clang"); + if hermetic_clang.is_file() { + return Some(hermetic_clang); + } + + find_on_path("clang") +} + +fn find_on_path(program: &str) -> Option { + let path = env::var_os("PATH")?; + env::split_paths(&path) + .map(|dir| dir.join(program)) + .find(|candidate| candidate.is_file()) +} + +fn configure_clang_runtime_env(command: &mut Command, clang: &Path) { + if !clang.starts_with("/tmp/glagol-clang-root") { + return; + } + + let root = Path::new("/tmp/glagol-clang-root"); + let lib64 = root.join("usr/lib64"); + let lib = root.join("usr/lib"); + let mut paths = vec![lib64, lib]; + + if let Some(existing) = env::var_os("LD_LIBRARY_PATH") { + paths.extend(env::split_paths(&existing)); + } + + let joined = env::join_paths(paths).expect("join LD_LIBRARY_PATH"); + command.env("LD_LIBRARY_PATH", joined); +} diff --git a/compiler/tests/standard_time_source_facade_alpha.rs b/compiler/tests/standard_time_source_facade_alpha.rs new file mode 100644 index 0000000..feb4243 --- /dev/null +++ b/compiler/tests/standard_time_source_facade_alpha.rs @@ -0,0 +1,141 @@ +use std::{ + ffi::OsStr, + fs, + path::Path, + process::{Command, Output}, +}; + +const EXPECTED_TEST_OUTPUT: &str = concat!( + "test \"explicit local time monotonic facade\" ... ok\n", + "test \"explicit local time sleep zero facade\" ... ok\n", + "test \"explicit local time facade all\" ... ok\n", + "3 test(s) passed\n", +); + +const STANDARD_TIME_SOURCE_FACADE_ALPHA: &[&str] = &["monotonic_ms", "sleep_ms_zero"]; + +#[test] +fn standard_time_source_facade_project_checks_formats_and_tests() { + let project = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/projects/std-layout-local-time"); + + assert_local_time_fixture_is_source_authored(&project); + + let fmt = run_glagol([ + OsStr::new("fmt"), + OsStr::new("--check"), + project.as_os_str(), + ]); + assert_success("std layout local time fmt --check", &fmt); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std layout local time check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + test, + EXPECTED_TEST_OUTPUT, + "std layout local time test output", + ); +} + +fn assert_local_time_fixture_is_source_authored(project: &Path) { + let time = read(&project.join("src/time.slo")); + let main = read(&project.join("src/main.slo")); + + assert!( + time.starts_with("(module time (export "), + "time.slo must stay an explicit local module export" + ); + assert!( + main.starts_with("(module main)\n\n(import time ("), + "main.slo must stay an explicit local time import" + ); + assert!( + !main.contains("(import std") && !main.contains("(import slovo.std"), + "exp-37 fixture must not depend on automatic or package std imports" + ); + assert!( + time.contains("(std.time.monotonic_ms)") && time.contains("(std.time.sleep_ms 0)"), + "time.slo must use only the existing promoted std.time source calls" + ); + let mut non_time_std = time.clone(); + for allowed in ["std.time.monotonic_ms", "std.time.sleep_ms"] { + non_time_std = non_time_std.replace(allowed, ""); + } + assert!( + !non_time_std.contains("std.") && !main.contains("std."), + "exp-37 fixture must not introduce other compiler-known std names" + ); + assert!( + !time.contains("std.time.now") + && !time.contains("calendar") + && !time.contains("timezone") + && !time.contains("async") + && !time.contains("cancel") + && !time.contains("schedule"), + "exp-37 fixture must not claim deferred time APIs or scheduling semantics" + ); + assert!( + time.contains("(fn sleep_ms_zero () -> i32\n (std.time.sleep_ms 0)\n 0)"), + "sleep_ms_zero must return 0 after calling the unit-return sleep facade" + ); + + for helper in STANDARD_TIME_SOURCE_FACADE_ALPHA { + assert!( + time.contains(&format!("(fn {} ", helper)), + "time.slo is missing source facade `{}`", + helper + ); + assert!( + main.contains(helper), + "main.slo does not explicitly import/use `{}`", + helper + ); + } +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn read(path: &Path) -> String { + fs::read_to_string(path).unwrap_or_else(|err| panic!("read `{}`: {}", path.display(), err)) +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} + +fn assert_success_stdout(output: Output, expected: &str, context: &str) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert_eq!(stdout, expected, "{} stdout drifted", context); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} diff --git a/compiler/tests/standard_vec_bool_source_helpers_alpha.rs b/compiler/tests/standard_vec_bool_source_helpers_alpha.rs new file mode 100644 index 0000000..5f270a9 --- /dev/null +++ b/compiler/tests/standard_vec_bool_source_helpers_alpha.rs @@ -0,0 +1,242 @@ +use std::{ + ffi::OsStr, + fs, + path::Path, + process::{Command, Output}, +}; + +const EXPECTED_TEST_OUTPUT: &str = concat!( + "test \"explicit local vec_bool empty len facade\" ... ok\n", + "test \"explicit local vec_bool direct at facade\" ... ok\n", + "test \"explicit local vec_bool builder helpers\" ... ok\n", + "test \"explicit local vec_bool query helpers\" ... ok\n", + "test \"explicit local vec_bool option query helpers\" ... ok\n", + "test \"explicit local vec_bool starts_with helper\" ... ok\n", + "test \"explicit local vec_bool ends_with helper\" ... ok\n", + "test \"explicit local vec_bool without_suffix helper\" ... ok\n", + "test \"explicit local vec_bool without_prefix helper\" ... ok\n", + "test \"explicit local vec_bool transform helpers\" ... ok\n", + "test \"explicit local vec_bool subvec helper\" ... ok\n", + "test \"explicit local vec_bool insert helper\" ... ok\n", + "test \"explicit local vec_bool insert range helper\" ... ok\n", + "test \"explicit local vec_bool replace helper\" ... ok\n", + "test \"explicit local vec_bool replace range helper\" ... ok\n", + "test \"explicit local vec_bool remove helper\" ... ok\n", + "test \"explicit local vec_bool remove range helper\" ... ok\n", + "test \"explicit local vec_bool real program helpers\" ... ok\n", + "test \"explicit local vec_bool helpers all\" ... ok\n", + "19 test(s) passed\n", +); + +const STANDARD_VEC_BOOL_SOURCE_FACADE_ALPHA: &[&str] = &[ + "empty", + "append", + "len", + "at", + "singleton", + "append2", + "append3", + "pair", + "triple", + "is_empty", + "index_or", + "first_or", + "last_or", + "index_option", + "first_option", + "last_option", + "index_of_option", + "last_index_of_option", + "contains", + "count_of", + "concat", + "take", + "starts_with", + "without_prefix", + "ends_with", + "without_suffix", + "drop", + "reverse", + "subvec", + "insert_at", + "insert_range", + "replace_at", + "replace_range", + "remove_at", + "remove_range", +]; + +#[test] +fn standard_vec_bool_source_helper_project_checks_formats_and_tests() { + let project = Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../examples/projects/std-layout-local-vec_bool"); + + assert_local_vec_bool_fixture_is_source_authored(&project); + + let fmt = run_glagol([ + OsStr::new("fmt"), + OsStr::new("--check"), + project.as_os_str(), + ]); + assert_success("std layout local vec_bool fmt --check", &fmt); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std layout local vec_bool check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + test, + EXPECTED_TEST_OUTPUT, + "std layout local vec_bool test output", + ); +} + +fn assert_local_vec_bool_fixture_is_source_authored(project: &Path) { + let vec_bool = read(&project.join("src/vec_bool.slo")); + let main = read(&project.join("src/main.slo")); + + assert!( + vec_bool.starts_with("(module vec_bool (export "), + "vec_bool.slo must stay an explicit local module export" + ); + assert!( + main.starts_with("(module main)\n\n(import vec_bool ("), + "main.slo must stay an explicit local vec_bool import" + ); + assert!( + !vec_bool.contains("(import std") + && !main.contains("(import std.") + && !vec_bool.contains("(import slovo.std") + && !main.contains("(import slovo.std"), + "vec_bool fixture must not depend on automatic or package std imports" + ); + assert!( + !vec_bool.contains("(var ") + && !main.contains("(var ") + && !vec_bool.contains("(set ") + && !main.contains("(set "), + "vec_bool fixture must stay recursive and immutable without mutating locals" + ); + assert!( + !vec_bool.contains("(vec i32)") + && !main.contains("(vec i32)") + && !vec_bool.contains("(vec i64)") + && !main.contains("(vec i64)") + && !vec_bool.contains("(vec string)") + && !main.contains("(vec string)") + && !vec_bool.contains("(option i64)") + && !main.contains("(option i64)") + && !vec_bool.contains("(option f64)") + && !main.contains("(option f64)") + && !vec_bool.contains("(option string)") + && !main.contains("(option string)") + && !vec_bool.contains("(result i32") + && !main.contains("(result i32") + && !vec_bool.contains("(result i64") + && !main.contains("(result i64") + && !vec_bool.contains("(result f64") + && !main.contains("(result f64") + && !vec_bool.contains("(result string") + && !main.contains("(result string") + && !vec_bool.contains("(result bool") + && !main.contains("(result bool"), + "vec_bool fixture must stay limited to concrete vec bool plus option i32/bool helpers" + ); + + let mut non_vec_std = vec_bool.clone(); + for allowed in [ + "std.vec.bool.empty", + "std.vec.bool.append", + "std.vec.bool.len", + "std.vec.bool.index", + ] { + non_vec_std = non_vec_std.replace(allowed, ""); + } + assert!( + !non_vec_std.contains("std.") && !main.contains("std."), + "vec_bool fixture must use only the existing promoted std.vec.bool runtime names" + ); + assert!( + !vec_bool.contains("capacity") + && !vec_bool.contains("reserve") + && !vec_bool.contains("shrink") + && !vec_bool.contains("sort") + && !vec_bool.contains("map") + && !vec_bool.contains("filter"), + "vec_bool fixture must not claim deferred generic or mutation-heavy collection APIs" + ); + + for helper in STANDARD_VEC_BOOL_SOURCE_FACADE_ALPHA { + assert!( + vec_bool.contains(&format!("(fn {} ", helper)), + "vec_bool.slo is missing source helper `{}`", + helper + ); + assert!( + main.contains(helper), + "main.slo does not explicitly import/use `{}`", + helper + ); + } + for helper in [ + "index_of_option_loop", + "last_index_of_option_loop", + "contains_loop", + "count_of_loop", + "concat_loop", + "take_loop", + "drop_loop", + "reverse_loop", + ] { + assert!( + vec_bool.contains(&format!("(fn {} ", helper)), + "vec_bool.slo is missing recursive source helper `{}`", + helper + ); + } +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn read(path: &Path) -> String { + fs::read_to_string(path).unwrap_or_else(|err| panic!("read `{}`: {}", path.display(), err)) +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} + +fn assert_success_stdout(output: Output, expected: &str, context: &str) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert_eq!(stdout, expected, "{} stdout drifted", context); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} diff --git a/compiler/tests/standard_vec_bool_source_search_alpha.rs b/compiler/tests/standard_vec_bool_source_search_alpha.rs new file mode 100644 index 0000000..9cdfd0a --- /dev/null +++ b/compiler/tests/standard_vec_bool_source_search_alpha.rs @@ -0,0 +1,194 @@ +use std::{ + ffi::OsStr, + fs, + path::Path, + process::{Command, Output}, +}; + +const EXPECTED_STD_VEC_BOOL_OUTPUT: &str = concat!( + "test \"explicit std vec_bool empty len facade\" ... ok\n", + "test \"explicit std vec_bool direct at facade\" ... ok\n", + "test \"explicit std vec_bool builder helpers\" ... ok\n", + "test \"explicit std vec_bool query helpers\" ... ok\n", + "test \"explicit std vec_bool option query helpers\" ... ok\n", + "test \"explicit std vec_bool starts_with helper\" ... ok\n", + "test \"explicit std vec_bool ends_with helper\" ... ok\n", + "test \"explicit std vec_bool without_suffix helper\" ... ok\n", + "test \"explicit std vec_bool without_prefix helper\" ... ok\n", + "test \"explicit std vec_bool transform helpers\" ... ok\n", + "test \"explicit std vec_bool subvec helper\" ... ok\n", + "test \"explicit std vec_bool insert helper\" ... ok\n", + "test \"explicit std vec_bool insert range helper\" ... ok\n", + "test \"explicit std vec_bool replace helper\" ... ok\n", + "test \"explicit std vec_bool replace range helper\" ... ok\n", + "test \"explicit std vec_bool remove helper\" ... ok\n", + "test \"explicit std vec_bool remove range helper\" ... ok\n", + "test \"explicit std vec_bool real program helpers\" ... ok\n", + "test \"explicit std vec_bool helpers all\" ... ok\n", + "19 test(s) passed\n", +); + +const STANDARD_VEC_BOOL_SOURCE_FACADE_ALPHA: &[&str] = &[ + "empty", + "append", + "len", + "at", + "singleton", + "append2", + "append3", + "pair", + "triple", + "is_empty", + "index_or", + "first_or", + "last_or", + "index_option", + "first_option", + "last_option", + "index_of_option", + "last_index_of_option", + "contains", + "count_of", + "concat", + "take", + "starts_with", + "without_prefix", + "ends_with", + "without_suffix", + "drop", + "reverse", + "subvec", + "insert_at", + "insert_range", + "replace_at", + "replace_range", + "remove_at", + "remove_range", +]; + +#[test] +fn explicit_std_vec_bool_import_loads_repo_root_standard_source() { + let compiler_root = Path::new(env!("CARGO_MANIFEST_DIR")); + let project = compiler_root.join("../examples/projects/std-import-vec_bool"); + let slovo_vec_bool = compiler_root.join("../lib/std/vec_bool.slo"); + + assert!( + !project.join("src/vec_bool.slo").exists(), + "std-import-vec_bool must not carry a source-root vec_bool module copy" + ); + assert!( + !project.join("std/vec_bool.slo").exists(), + "std-import-vec_bool must not carry a project-local std/vec_bool.slo copy" + ); + let main = read(&project.join("src/main.slo")); + assert!( + main.starts_with("(module main)\n\n(import std.vec_bool ("), + "std-import-vec_bool must exercise explicit `std.vec_bool` import syntax" + ); + + let slovo_source = read(&slovo_vec_bool); + assert!( + slovo_source.starts_with("(module vec_bool (export "), + "repo-root Slovo std/vec_bool.slo must export imported helpers directly" + ); + assert!( + !slovo_source.contains("(vec i32)") + && !slovo_source.contains("(vec i64)") + && !slovo_source.contains("(vec string)") + && !slovo_source.contains("(option i64)") + && !slovo_source.contains("(option f64)") + && !slovo_source.contains("(option string)") + && !slovo_source.contains("(result i32") + && !slovo_source.contains("(result i64") + && !slovo_source.contains("(result f64") + && !slovo_source.contains("(result string") + && !slovo_source.contains("(result bool") + && !slovo_source.contains("capacity") + && !slovo_source.contains("reserve") + && !slovo_source.contains("shrink") + && !slovo_source.contains("sort") + && !slovo_source.contains("map") + && !slovo_source.contains("filter"), + "std/vec_bool.slo must stay concrete to vec bool plus option i32/bool helpers" + ); + + let mut non_vec_std = slovo_source.clone(); + for allowed in [ + "std.vec.bool.empty", + "std.vec.bool.append", + "std.vec.bool.len", + "std.vec.bool.index", + ] { + non_vec_std = non_vec_std.replace(allowed, ""); + } + assert!( + !non_vec_std.contains("std."), + "std/vec_bool.slo must use only the existing promoted std.vec.bool runtime names" + ); + + for helper in STANDARD_VEC_BOOL_SOURCE_FACADE_ALPHA { + assert!( + slovo_source.contains(&format!("(fn {} ", helper)), + "Slovo std/vec_bool.slo is missing helper `{}`", + helper + ); + assert!( + main.contains(helper), + "std-import-vec_bool main fixture import/use is missing helper `{}`", + helper + ); + } + + let fmt = run_glagol([ + OsStr::new("fmt"), + OsStr::new("--check"), + project.as_os_str(), + ]); + assert_success("std vec_bool facade source search fmt --check", &fmt); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std vec_bool facade source search check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + test, + EXPECTED_STD_VEC_BOOL_OUTPUT, + "std vec_bool facade source search test", + ); +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn read(path: &Path) -> String { + fs::read_to_string(path).unwrap_or_else(|err| panic!("read `{}`: {}", path.display(), err)) +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstatus: {:?}\nstdout:\n{}\nstderr:\n{}", + context, + output.status.code(), + stdout, + stderr + ); +} + +fn assert_success_stdout(output: Output, expected: &str, context: &str) { + assert_success(context, &output); + let stdout = String::from_utf8_lossy(&output.stdout); + assert_eq!(stdout, expected, "{}", context); +} diff --git a/compiler/tests/standard_vec_f64_source_helpers_alpha.rs b/compiler/tests/standard_vec_f64_source_helpers_alpha.rs new file mode 100644 index 0000000..a83f396 --- /dev/null +++ b/compiler/tests/standard_vec_f64_source_helpers_alpha.rs @@ -0,0 +1,244 @@ +use std::{ + ffi::OsStr, + fs, + path::Path, + process::{Command, Output}, +}; + +const EXPECTED_TEST_OUTPUT: &str = concat!( + "test \"explicit local vec_f64 empty len facade\" ... ok\n", + "test \"explicit local vec_f64 direct at facade\" ... ok\n", + "test \"explicit local vec_f64 builder helpers\" ... ok\n", + "test \"explicit local vec_f64 query helpers\" ... ok\n", + "test \"explicit local vec_f64 option query helpers\" ... ok\n", + "test \"explicit local vec_f64 starts_with helper\" ... ok\n", + "test \"explicit local vec_f64 ends_with helper\" ... ok\n", + "test \"explicit local vec_f64 without_suffix helper\" ... ok\n", + "test \"explicit local vec_f64 without_prefix helper\" ... ok\n", + "test \"explicit local vec_f64 transform helpers\" ... ok\n", + "test \"explicit local vec_f64 subvec helper\" ... ok\n", + "test \"explicit local vec_f64 insert helper\" ... ok\n", + "test \"explicit local vec_f64 insert range helper\" ... ok\n", + "test \"explicit local vec_f64 replace helper\" ... ok\n", + "test \"explicit local vec_f64 replace range helper\" ... ok\n", + "test \"explicit local vec_f64 remove helper\" ... ok\n", + "test \"explicit local vec_f64 remove range helper\" ... ok\n", + "test \"explicit local vec_f64 real program helpers\" ... ok\n", + "test \"explicit local vec_f64 helpers all\" ... ok\n", + "19 test(s) passed\n", +); + +const STANDARD_VEC_F64_SOURCE_FACADE_ALPHA: &[&str] = &[ + "empty", + "append", + "len", + "at", + "singleton", + "append2", + "append3", + "pair", + "triple", + "is_empty", + "index_or", + "first_or", + "last_or", + "index_option", + "first_option", + "last_option", + "index_of_option", + "last_index_of_option", + "contains", + "sum", + "concat", + "take", + "starts_with", + "without_prefix", + "ends_with", + "without_suffix", + "drop", + "reverse", + "subvec", + "insert_at", + "insert_range", + "replace_at", + "replace_range", + "remove_at", + "remove_range", +]; + +#[test] +fn standard_vec_f64_source_helper_project_checks_formats_and_tests() { + let project = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/projects/std-layout-local-vec_f64"); + + assert_local_vec_f64_fixture_is_source_authored(&project); + + let fmt = run_glagol([ + OsStr::new("fmt"), + OsStr::new("--check"), + project.as_os_str(), + ]); + assert_success("std layout local vec_f64 fmt --check", &fmt); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std layout local vec_f64 check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + test, + EXPECTED_TEST_OUTPUT, + "std layout local vec_f64 test output", + ); +} + +fn assert_local_vec_f64_fixture_is_source_authored(project: &Path) { + let vec_f64 = read(&project.join("src/vec_f64.slo")); + let main = read(&project.join("src/main.slo")); + + assert!( + vec_f64.starts_with("(module vec_f64 (export "), + "vec_f64.slo must stay an explicit local module export" + ); + assert!( + main.starts_with("(module main)\n\n(import vec_f64 ("), + "main.slo must stay an explicit local vec_f64 import" + ); + assert!( + !vec_f64.contains("(import std") + && !main.contains("(import std.") + && !vec_f64.contains("(import slovo.std") + && !main.contains("(import slovo.std"), + "vec_f64 fixture must not depend on automatic or package std imports" + ); + assert!( + !vec_f64.contains("(var ") + && !main.contains("(var ") + && !vec_f64.contains("(set ") + && !main.contains("(set "), + "vec_f64 fixture must stay recursive and immutable without mutating locals" + ); + assert!( + !vec_f64.contains("(vec i32)") + && !main.contains("(vec i32)") + && !vec_f64.contains("(vec i64)") + && !main.contains("(vec i64)") + && !vec_f64.contains("(vec string)") + && !main.contains("(vec string)") + && !vec_f64.contains("(vec bool)") + && !main.contains("(vec bool)") + && !vec_f64.contains("(option i64)") + && !main.contains("(option i64)") + && !vec_f64.contains("(option string)") + && !main.contains("(option string)") + && !vec_f64.contains("(option bool)") + && !main.contains("(option bool)") + && !vec_f64.contains("(result i32") + && !main.contains("(result i32") + && !vec_f64.contains("(result i64") + && !main.contains("(result i64") + && !vec_f64.contains("(result f64") + && !main.contains("(result f64") + && !vec_f64.contains("(result string") + && !main.contains("(result string") + && !vec_f64.contains("(result bool") + && !main.contains("(result bool"), + "vec_f64 fixture must stay limited to concrete vec f64 plus option i32/f64 helpers" + ); + + let mut non_vec_std = vec_f64.clone(); + for allowed in [ + "std.vec.f64.empty", + "std.vec.f64.append", + "std.vec.f64.len", + "std.vec.f64.index", + ] { + non_vec_std = non_vec_std.replace(allowed, ""); + } + assert!( + !non_vec_std.contains("std.") && !main.contains("std."), + "vec_f64 fixture must use only the existing promoted std.vec.f64 runtime names" + ); + assert!( + !vec_f64.contains("capacity") + && !vec_f64.contains("reserve") + && !vec_f64.contains("shrink") + && !vec_f64.contains("sort") + && !vec_f64.contains("map") + && !vec_f64.contains("filter"), + "vec_f64 fixture must not claim deferred generic or mutation-heavy collection APIs" + ); + + for helper in STANDARD_VEC_F64_SOURCE_FACADE_ALPHA { + assert!( + vec_f64.contains(&format!("(fn {} ", helper)), + "vec_f64.slo is missing source helper `{}`", + helper + ); + assert!( + main.contains(helper), + "main.slo does not explicitly import/use `{}`", + helper + ); + } + for helper in [ + "index_of_option_loop", + "last_index_of_option_loop", + "contains_loop", + "sum_loop", + "concat_loop", + "take_loop", + "drop_loop", + "reverse_loop", + ] { + assert!( + vec_f64.contains(&format!("(fn {} ", helper)), + "vec_f64.slo is missing recursive source helper `{}`", + helper + ); + } +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn read(path: &Path) -> String { + fs::read_to_string(path).unwrap_or_else(|err| panic!("read `{}`: {}", path.display(), err)) +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} + +fn assert_success_stdout(output: Output, expected: &str, context: &str) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert_eq!(stdout, expected, "{} stdout drifted", context); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} diff --git a/compiler/tests/standard_vec_f64_source_search_alpha.rs b/compiler/tests/standard_vec_f64_source_search_alpha.rs new file mode 100644 index 0000000..d33d250 --- /dev/null +++ b/compiler/tests/standard_vec_f64_source_search_alpha.rs @@ -0,0 +1,195 @@ +use std::{ + ffi::OsStr, + fs, + path::Path, + process::{Command, Output}, +}; + +const EXPECTED_STD_VEC_F64_OUTPUT: &str = concat!( + "test \"explicit std vec_f64 empty len facade\" ... ok\n", + "test \"explicit std vec_f64 direct at facade\" ... ok\n", + "test \"explicit std vec_f64 builder helpers\" ... ok\n", + "test \"explicit std vec_f64 query helpers\" ... ok\n", + "test \"explicit std vec_f64 option query helpers\" ... ok\n", + "test \"explicit std vec_f64 starts_with helper\" ... ok\n", + "test \"explicit std vec_f64 ends_with helper\" ... ok\n", + "test \"explicit std vec_f64 without_suffix helper\" ... ok\n", + "test \"explicit std vec_f64 without_prefix helper\" ... ok\n", + "test \"explicit std vec_f64 transform helpers\" ... ok\n", + "test \"explicit std vec_f64 subvec helper\" ... ok\n", + "test \"explicit std vec_f64 insert helper\" ... ok\n", + "test \"explicit std vec_f64 insert range helper\" ... ok\n", + "test \"explicit std vec_f64 replace helper\" ... ok\n", + "test \"explicit std vec_f64 replace range helper\" ... ok\n", + "test \"explicit std vec_f64 remove helper\" ... ok\n", + "test \"explicit std vec_f64 remove range helper\" ... ok\n", + "test \"explicit std vec_f64 real program helpers\" ... ok\n", + "test \"explicit std vec_f64 helpers all\" ... ok\n", + "19 test(s) passed\n", +); + +const STANDARD_VEC_F64_SOURCE_FACADE_ALPHA: &[&str] = &[ + "empty", + "append", + "len", + "at", + "singleton", + "append2", + "append3", + "pair", + "triple", + "is_empty", + "index_or", + "first_or", + "last_or", + "index_option", + "first_option", + "last_option", + "index_of_option", + "last_index_of_option", + "contains", + "sum", + "concat", + "take", + "starts_with", + "without_prefix", + "ends_with", + "without_suffix", + "drop", + "reverse", + "subvec", + "insert_at", + "insert_range", + "replace_at", + "replace_range", + "remove_at", + "remove_range", +]; + +#[test] +fn explicit_std_vec_f64_import_loads_repo_root_standard_source() { + let compiler_root = Path::new(env!("CARGO_MANIFEST_DIR")); + let project = compiler_root.join("../examples/projects/std-import-vec_f64"); + let slovo_vec_f64 = compiler_root.join("../lib/std/vec_f64.slo"); + + assert!( + !project.join("src/vec_f64.slo").exists(), + "std-import-vec_f64 must not carry a source-root vec_f64 module copy" + ); + assert!( + !project.join("std/vec_f64.slo").exists(), + "std-import-vec_f64 must not carry a project-local std/vec_f64.slo copy" + ); + let main = read(&project.join("src/main.slo")); + assert!( + main.starts_with("(module main)\n\n(import std.vec_f64 ("), + "std-import-vec_f64 must exercise explicit `std.vec_f64` import syntax" + ); + + let slovo_source = read(&slovo_vec_f64); + assert!( + slovo_source.starts_with("(module vec_f64 (export "), + "repo-root Slovo std/vec_f64.slo must export imported helpers directly" + ); + assert!( + !slovo_source.contains("(vec i32)") + && !slovo_source.contains("(vec i64)") + && !slovo_source.contains("(vec string)") + && !slovo_source.contains("(vec bool)") + && !slovo_source.contains("(option i64)") + && !slovo_source.contains("(option string)") + && !slovo_source.contains("(option bool)") + && !slovo_source.contains("(result i32") + && !slovo_source.contains("(result i64") + && !slovo_source.contains("(result f64") + && !slovo_source.contains("(result string") + && !slovo_source.contains("(result bool") + && !slovo_source.contains("capacity") + && !slovo_source.contains("reserve") + && !slovo_source.contains("shrink") + && !slovo_source.contains("sort") + && !slovo_source.contains("map") + && !slovo_source.contains("filter"), + "std/vec_f64.slo must stay concrete to vec f64 plus option i32/f64 helpers" + ); + + let mut non_vec_std = slovo_source.clone(); + for allowed in [ + "std.vec.f64.empty", + "std.vec.f64.append", + "std.vec.f64.len", + "std.vec.f64.index", + ] { + non_vec_std = non_vec_std.replace(allowed, ""); + } + assert!( + !non_vec_std.contains("std."), + "std/vec_f64.slo must use only the existing promoted std.vec.f64 runtime names" + ); + + for helper in STANDARD_VEC_F64_SOURCE_FACADE_ALPHA { + assert!( + slovo_source.contains(&format!("(fn {} ", helper)), + "Slovo std/vec_f64.slo is missing helper `{}`", + helper + ); + assert!( + main.contains(helper), + "std-import-vec_f64 main fixture import/use is missing helper `{}`", + helper + ); + } + + let fmt = run_glagol([ + OsStr::new("fmt"), + OsStr::new("--check"), + project.as_os_str(), + ]); + assert_success("std vec_f64 facade source search fmt --check", &fmt); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std vec_f64 facade source search check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + test, + EXPECTED_STD_VEC_F64_OUTPUT, + "std vec_f64 facade source search test", + ); +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn read(path: &Path) -> String { + fs::read_to_string(path).unwrap_or_else(|err| panic!("read `{}`: {}", path.display(), err)) +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstatus: {:?}\nstdout:\n{}\nstderr:\n{}", + context, + output.status.code(), + stdout, + stderr + ); +} + +fn assert_success_stdout(output: Output, expected: &str, context: &str) { + assert_success(context, &output); + let stdout = String::from_utf8_lossy(&output.stdout); + assert_eq!(stdout, expected, "{}", context); +} diff --git a/compiler/tests/standard_vec_i32_source_helpers_alpha.rs b/compiler/tests/standard_vec_i32_source_helpers_alpha.rs new file mode 100644 index 0000000..ac6b90d --- /dev/null +++ b/compiler/tests/standard_vec_i32_source_helpers_alpha.rs @@ -0,0 +1,262 @@ +use std::{ + ffi::OsStr, + fs, + path::Path, + process::{Command, Output}, +}; + +const EXPECTED_TEST_OUTPUT: &str = concat!( + "test \"explicit local vec_i32 empty len facade\" ... ok\n", + "test \"explicit local vec_i32 direct at facade\" ... ok\n", + "test \"explicit local vec_i32 builder helpers\" ... ok\n", + "test \"explicit local vec_i32 constructor helpers\" ... ok\n", + "test \"explicit local vec_i32 range helper\" ... ok\n", + "test \"explicit local vec_i32 query helpers\" ... ok\n", + "test \"explicit local vec_i32 option query helpers\" ... ok\n", + "test \"explicit local vec_i32 starts_with helper\" ... ok\n", + "test \"explicit local vec_i32 ends_with helper\" ... ok\n", + "test \"explicit local vec_i32 without_suffix helper\" ... ok\n", + "test \"explicit local vec_i32 without_prefix helper\" ... ok\n", + "test \"explicit local vec_i32 transform helpers\" ... ok\n", + "test \"explicit local vec_i32 subvec helper\" ... ok\n", + "test \"explicit local vec_i32 insert helper\" ... ok\n", + "test \"explicit local vec_i32 insert range helper\" ... ok\n", + "test \"explicit local vec_i32 replace helper\" ... ok\n", + "test \"explicit local vec_i32 replace range helper\" ... ok\n", + "test \"explicit local vec_i32 remove helper\" ... ok\n", + "test \"explicit local vec_i32 remove range helper\" ... ok\n", + "test \"explicit local vec_i32 count_of helper\" ... ok\n", + "test \"explicit local vec_i32 real program helpers\" ... ok\n", + "test \"explicit local vec_i32 helpers all\" ... ok\n", + "22 test(s) passed\n", +); + +const STANDARD_VEC_I32_SOURCE_FACADE_ALPHA: &[&str] = &[ + "empty", + "append", + "len", + "at", + "singleton", + "append2", + "append3", + "pair", + "triple", + "repeat", + "range", + "range_from_zero", + "is_empty", + "index_or", + "first_or", + "last_or", + "index_option", + "first_option", + "last_option", + "index_of_option", + "last_index_of_option", + "count_of", + "contains", + "sum", + "concat", + "take", + "starts_with", + "without_prefix", + "ends_with", + "without_suffix", + "drop", + "reverse", + "subvec", + "insert_at", + "insert_range", + "replace_at", + "replace_range", + "remove_at", + "remove_range", +]; + +#[test] +fn standard_vec_i32_source_helper_project_checks_formats_and_tests() { + let project = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/projects/std-layout-local-vec_i32"); + + assert_local_vec_i32_fixture_is_source_authored(&project); + + let fmt = run_glagol([ + OsStr::new("fmt"), + OsStr::new("--check"), + project.as_os_str(), + ]); + assert_success("std layout local vec_i32 fmt --check", &fmt); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std layout local vec_i32 check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + test, + EXPECTED_TEST_OUTPUT, + "std layout local vec_i32 test output", + ); +} + +fn assert_local_vec_i32_fixture_is_source_authored(project: &Path) { + let option = read(&project.join("src/option.slo")); + let vec_i32 = read(&project.join("src/vec_i32.slo")); + let main = read(&project.join("src/main.slo")); + + assert!( + option.starts_with("(module option (export "), + "option.slo must stay an explicit local module export" + ); + assert!( + vec_i32.starts_with("(module vec_i32 (export "), + "vec_i32.slo must stay an explicit local module export" + ); + assert!( + main.starts_with("(module main)\n\n(import vec_i32 ("), + "main.slo must stay an explicit local vec_i32 import" + ); + assert!( + !option.contains("(import std") + && !vec_i32.contains("(import std") + && !main.contains("(import std") + && !option.contains("(import slovo.std") + && !vec_i32.contains("(import slovo.std") + && !main.contains("(import slovo.std"), + "vec_i32 fixture must not depend on automatic or package std imports" + ); + assert!( + !option.contains("(result i32") + && !option.contains("(result i64") + && !option.contains("(result f64") + && !option.contains("(result string") + && !option.contains("(result bool") + && !option.contains("(option i64)") + && !option.contains("(option f64)") + && !option.contains("(option string)") + && !option.contains("(option bool)") + && !vec_i32.contains("(result i32") + && !vec_i32.contains("(result i64") + && !vec_i32.contains("(result f64") + && !vec_i32.contains("(result string") + && !vec_i32.contains("(result bool") + && !main.contains("(result i32") + && !main.contains("(result i64") + && !main.contains("(result f64") + && !main.contains("(result string") + && !main.contains("(result bool") + && !vec_i32.contains("(vec i64)") + && !main.contains("(vec i64)") + && !vec_i32.contains("(vec f64)") + && !main.contains("(vec f64)") + && !vec_i32.contains("(vec string)") + && !main.contains("(vec string)") + && !vec_i32.contains("(vec bool)") + && !main.contains("(vec bool)") + && !vec_i32.contains("(option i64)") + && !main.contains("(option i64)") + && !vec_i32.contains("(option f64)") + && !main.contains("(option f64)") + && !vec_i32.contains("(option string)") + && !main.contains("(option string)") + && !vec_i32.contains("(option bool)") + && !main.contains("(option bool)"), + "vec_i32 fixture must stay limited to concrete i32 vec and option helpers" + ); + + let mut non_vec_std = vec_i32.clone(); + for allowed in [ + "std.vec.i32.empty", + "std.vec.i32.append", + "std.vec.i32.len", + "std.vec.i32.index", + ] { + non_vec_std = non_vec_std.replace(allowed, ""); + } + assert!( + !option.contains("std.") && !non_vec_std.contains("std.") && !main.contains("std."), + "vec_i32 fixture must use only the existing promoted std.vec.i32 runtime names" + ); + assert!( + !vec_i32.contains("push") + && !vec_i32.contains("capacity") + && !vec_i32.contains("reserve") + && !vec_i32.contains("shrink") + && !vec_i32.contains("sort") + && !vec_i32.contains("map") + && !vec_i32.contains("filter"), + "vec_i32 fixture must not claim deferred generic or mutation-heavy collection APIs" + ); + + for helper in STANDARD_VEC_I32_SOURCE_FACADE_ALPHA { + assert!( + vec_i32.contains(&format!("(fn {} ", helper)), + "vec_i32.slo is missing source helper `{}`", + helper + ); + assert!( + main.contains(helper), + "main.slo does not explicitly import/use `{}`", + helper + ); + } + for helper in [ + "repeat_loop", + "range_from_zero_loop", + "range_loop", + "concat_loop", + "take_loop", + "drop_loop", + "reverse_loop", + ] { + assert!( + vec_i32.contains(&format!("(fn {} ", helper)), + "vec_i32.slo is missing recursive source helper `{}`", + helper + ); + } +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn read(path: &Path) -> String { + fs::read_to_string(path).unwrap_or_else(|err| panic!("read `{}`: {}", path.display(), err)) +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} + +fn assert_success_stdout(output: Output, expected: &str, context: &str) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert_eq!(stdout, expected, "{} stdout drifted", context); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} diff --git a/compiler/tests/standard_vec_i32_source_search_alpha.rs b/compiler/tests/standard_vec_i32_source_search_alpha.rs new file mode 100644 index 0000000..5bb1e09 --- /dev/null +++ b/compiler/tests/standard_vec_i32_source_search_alpha.rs @@ -0,0 +1,162 @@ +use std::{ + ffi::OsStr, + fs, + path::Path, + process::{Command, Output}, +}; + +const EXPECTED_STD_VEC_I32_OUTPUT: &str = concat!( + "test \"explicit std vec_i32 empty len facade\" ... ok\n", + "test \"explicit std vec_i32 direct at facade\" ... ok\n", + "test \"explicit std vec_i32 builder helpers\" ... ok\n", + "test \"explicit std vec_i32 constructor helpers\" ... ok\n", + "test \"explicit std vec_i32 range helper\" ... ok\n", + "test \"explicit std vec_i32 query helpers\" ... ok\n", + "test \"explicit std vec_i32 option query helpers\" ... ok\n", + "test \"explicit std vec_i32 starts_with helper\" ... ok\n", + "test \"explicit std vec_i32 ends_with helper\" ... ok\n", + "test \"explicit std vec_i32 without_suffix helper\" ... ok\n", + "test \"explicit std vec_i32 without_prefix helper\" ... ok\n", + "test \"explicit std vec_i32 transform helpers\" ... ok\n", + "test \"explicit std vec_i32 subvec helper\" ... ok\n", + "test \"explicit std vec_i32 insert helper\" ... ok\n", + "test \"explicit std vec_i32 insert range helper\" ... ok\n", + "test \"explicit std vec_i32 replace helper\" ... ok\n", + "test \"explicit std vec_i32 replace range helper\" ... ok\n", + "test \"explicit std vec_i32 remove helper\" ... ok\n", + "test \"explicit std vec_i32 remove range helper\" ... ok\n", + "test \"explicit std vec_i32 count_of helper\" ... ok\n", + "test \"explicit std vec_i32 real program helpers\" ... ok\n", + "test \"explicit std vec_i32 helpers all\" ... ok\n", + "22 test(s) passed\n", +); + +const STANDARD_VEC_I32_SOURCE_FACADE_ALPHA: &[&str] = &[ + "empty", + "append", + "len", + "at", + "singleton", + "append2", + "append3", + "pair", + "triple", + "repeat", + "range", + "range_from_zero", + "is_empty", + "index_or", + "first_or", + "last_or", + "index_option", + "first_option", + "last_option", + "index_of_option", + "last_index_of_option", + "count_of", + "contains", + "sum", + "concat", + "take", + "starts_with", + "without_prefix", + "ends_with", + "without_suffix", + "drop", + "reverse", + "subvec", + "insert_at", + "insert_range", + "replace_at", + "replace_range", + "remove_at", + "remove_range", +]; + +#[test] +fn explicit_std_vec_i32_import_loads_repo_root_standard_source() { + let compiler_root = Path::new(env!("CARGO_MANIFEST_DIR")); + let project = compiler_root.join("../examples/projects/std-import-vec_i32"); + let slovo_vec_i32 = compiler_root.join("../lib/std/vec_i32.slo"); + + assert!( + !project.join("src/vec_i32.slo").exists(), + "std-import-vec_i32 must not carry a local vec_i32 module copy" + ); + let main = read(&project.join("src/main.slo")); + assert!( + main.starts_with("(module main)\n\n(import std.vec_i32 ("), + "std-import-vec_i32 must exercise explicit `std.vec_i32` import syntax" + ); + + let slovo_source = read(&slovo_vec_i32); + assert!( + slovo_source.starts_with("(module vec_i32 (export "), + "repo-root Slovo std/vec_i32.slo must export imported helpers directly" + ); + for helper in STANDARD_VEC_I32_SOURCE_FACADE_ALPHA { + assert!( + slovo_source.contains(&format!("(fn {} ", helper)), + "Slovo std/vec_i32.slo is missing helper `{}`", + helper + ); + assert!( + main.contains(helper), + "std-import-vec_i32 main fixture import/use is missing helper `{}`", + helper + ); + } + + let fmt = run_glagol([ + OsStr::new("fmt"), + OsStr::new("--check"), + project.as_os_str(), + ]); + assert_success("std vec_i32 facade source search fmt --check", &fmt); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std vec_i32 facade source search check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + test, + EXPECTED_STD_VEC_I32_OUTPUT, + "std vec_i32 facade source search test", + ); +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn read(path: &Path) -> String { + fs::read_to_string(path).unwrap_or_else(|err| panic!("read `{}`: {}", path.display(), err)) +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstatus: {:?}\nstdout:\n{}\nstderr:\n{}", + context, + output.status.code(), + stdout, + stderr + ); +} + +fn assert_success_stdout(output: Output, expected: &str, context: &str) { + assert_success(context, &output); + let stdout = String::from_utf8_lossy(&output.stdout); + assert_eq!(stdout, expected, "{}", context); +} diff --git a/compiler/tests/standard_vec_i64_source_helpers_alpha.rs b/compiler/tests/standard_vec_i64_source_helpers_alpha.rs new file mode 100644 index 0000000..4b643e5 --- /dev/null +++ b/compiler/tests/standard_vec_i64_source_helpers_alpha.rs @@ -0,0 +1,236 @@ +use std::{ + ffi::OsStr, + fs, + path::Path, + process::{Command, Output}, +}; + +const EXPECTED_TEST_OUTPUT: &str = concat!( + "test \"explicit local vec_i64 empty len facade\" ... ok\n", + "test \"explicit local vec_i64 direct at facade\" ... ok\n", + "test \"explicit local vec_i64 builder helpers\" ... ok\n", + "test \"explicit local vec_i64 query helpers\" ... ok\n", + "test \"explicit local vec_i64 option query helpers\" ... ok\n", + "test \"explicit local vec_i64 transform helpers\" ... ok\n", + "test \"explicit local vec_i64 subvec helper\" ... ok\n", + "test \"explicit local vec_i64 insert helper\" ... ok\n", + "test \"explicit local vec_i64 insert range helper\" ... ok\n", + "test \"explicit local vec_i64 replace helper\" ... ok\n", + "test \"explicit local vec_i64 replace range helper\" ... ok\n", + "test \"explicit local vec_i64 remove helper\" ... ok\n", + "test \"explicit local vec_i64 remove range helper\" ... ok\n", + "test \"explicit local vec_i64 real program helpers\" ... ok\n", + "test \"explicit local vec_i64 helpers all\" ... ok\n", + "15 test(s) passed\n", +); + +const STANDARD_VEC_I64_SOURCE_FACADE_ALPHA: &[&str] = &[ + "empty", + "append", + "len", + "at", + "singleton", + "append2", + "append3", + "pair", + "triple", + "is_empty", + "index_or", + "first_or", + "last_or", + "index_option", + "first_option", + "last_option", + "index_of_option", + "last_index_of_option", + "contains", + "sum", + "concat", + "take", + "drop", + "reverse", + "subvec", + "insert_at", + "insert_range", + "replace_at", + "replace_range", + "remove_at", + "remove_range", +]; + +#[test] +fn standard_vec_i64_source_helper_project_checks_formats_and_tests() { + let project = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/projects/std-layout-local-vec_i64"); + + assert_local_vec_i64_fixture_is_source_authored(&project); + + let fmt = run_glagol([ + OsStr::new("fmt"), + OsStr::new("--check"), + project.as_os_str(), + ]); + assert_success("std layout local vec_i64 fmt --check", &fmt); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std layout local vec_i64 check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + test, + EXPECTED_TEST_OUTPUT, + "std layout local vec_i64 test output", + ); +} + +fn assert_local_vec_i64_fixture_is_source_authored(project: &Path) { + let vec_i64 = read(&project.join("src/vec_i64.slo")); + let main = read(&project.join("src/main.slo")); + + assert!( + vec_i64.starts_with("(module vec_i64 (export "), + "vec_i64.slo must stay an explicit local module export" + ); + assert!( + main.starts_with("(module main)\n\n(import vec_i64 ("), + "main.slo must stay an explicit local vec_i64 import" + ); + assert!( + !vec_i64.contains("(import std") + && !main.contains("(import std.") + && !vec_i64.contains("(import slovo.std") + && !main.contains("(import slovo.std"), + "vec_i64 fixture must not depend on automatic or package std imports" + ); + assert!( + !vec_i64.contains("(var ") + && !main.contains("(var ") + && !vec_i64.contains("(set ") + && !main.contains("(set "), + "vec_i64 fixture must stay recursive and immutable without mutating locals" + ); + assert!( + !vec_i64.contains("(vec i32)") + && !main.contains("(vec i32)") + && !vec_i64.contains("(vec f64)") + && !main.contains("(vec f64)") + && !vec_i64.contains("(vec string)") + && !main.contains("(vec string)") + && !vec_i64.contains("(vec bool)") + && !main.contains("(vec bool)") + && !vec_i64.contains("(option f64)") + && !main.contains("(option f64)") + && !vec_i64.contains("(option string)") + && !main.contains("(option string)") + && !vec_i64.contains("(option bool)") + && !main.contains("(option bool)") + && !vec_i64.contains("(result i32") + && !main.contains("(result i32") + && !vec_i64.contains("(result i64") + && !main.contains("(result i64") + && !vec_i64.contains("(result f64") + && !main.contains("(result f64") + && !vec_i64.contains("(result string") + && !main.contains("(result string") + && !vec_i64.contains("(result bool") + && !main.contains("(result bool"), + "vec_i64 fixture must stay limited to concrete vec i64 plus option i32/i64 helpers" + ); + + let mut non_vec_std = vec_i64.clone(); + for allowed in [ + "std.vec.i64.empty", + "std.vec.i64.append", + "std.vec.i64.len", + "std.vec.i64.index", + ] { + non_vec_std = non_vec_std.replace(allowed, ""); + } + assert!( + !non_vec_std.contains("std.") && !main.contains("std."), + "vec_i64 fixture must use only the existing promoted std.vec.i64 runtime names" + ); + assert!( + !vec_i64.contains("capacity") + && !vec_i64.contains("reserve") + && !vec_i64.contains("shrink") + && !vec_i64.contains("sort") + && !vec_i64.contains("map") + && !vec_i64.contains("filter"), + "vec_i64 fixture must not claim deferred generic or mutation-heavy collection APIs" + ); + + for helper in STANDARD_VEC_I64_SOURCE_FACADE_ALPHA { + assert!( + vec_i64.contains(&format!("(fn {} ", helper)), + "vec_i64.slo is missing source helper `{}`", + helper + ); + assert!( + main.contains(helper), + "main.slo does not explicitly import/use `{}`", + helper + ); + } + for helper in [ + "index_of_option_loop", + "last_index_of_option_loop", + "contains_loop", + "sum_loop", + "concat_loop", + "take_loop", + "drop_loop", + "reverse_loop", + ] { + assert!( + vec_i64.contains(&format!("(fn {} ", helper)), + "vec_i64.slo is missing recursive source helper `{}`", + helper + ); + } +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn read(path: &Path) -> String { + fs::read_to_string(path).unwrap_or_else(|err| panic!("read `{}`: {}", path.display(), err)) +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} + +fn assert_success_stdout(output: Output, expected: &str, context: &str) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert_eq!(stdout, expected, "{} stdout drifted", context); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} diff --git a/compiler/tests/standard_vec_i64_source_search_alpha.rs b/compiler/tests/standard_vec_i64_source_search_alpha.rs new file mode 100644 index 0000000..993945d --- /dev/null +++ b/compiler/tests/standard_vec_i64_source_search_alpha.rs @@ -0,0 +1,172 @@ +use std::{ + ffi::OsStr, + fs, + path::Path, + process::{Command, Output}, +}; + +const EXPECTED_STD_VEC_I64_OUTPUT: &str = concat!( + "test \"explicit std vec_i64 empty len facade\" ... ok\n", + "test \"explicit std vec_i64 direct at facade\" ... ok\n", + "test \"explicit std vec_i64 builder helpers\" ... ok\n", + "test \"explicit std vec_i64 query helpers\" ... ok\n", + "test \"explicit std vec_i64 option query helpers\" ... ok\n", + "test \"explicit std vec_i64 transform helpers\" ... ok\n", + "test \"explicit std vec_i64 subvec helper\" ... ok\n", + "test \"explicit std vec_i64 insert helper\" ... ok\n", + "test \"explicit std vec_i64 insert range helper\" ... ok\n", + "test \"explicit std vec_i64 replace helper\" ... ok\n", + "test \"explicit std vec_i64 replace range helper\" ... ok\n", + "test \"explicit std vec_i64 remove helper\" ... ok\n", + "test \"explicit std vec_i64 remove range helper\" ... ok\n", + "test \"explicit std vec_i64 real program helpers\" ... ok\n", + "test \"explicit std vec_i64 helpers all\" ... ok\n", + "15 test(s) passed\n", +); + +const STANDARD_VEC_I64_SOURCE_FACADE_ALPHA: &[&str] = &[ + "empty", + "append", + "len", + "at", + "singleton", + "append2", + "append3", + "pair", + "triple", + "is_empty", + "index_or", + "first_or", + "last_or", + "index_option", + "first_option", + "last_option", + "index_of_option", + "last_index_of_option", + "contains", + "sum", + "concat", + "take", + "drop", + "reverse", + "subvec", + "insert_at", + "insert_range", + "replace_at", + "replace_range", + "remove_at", + "remove_range", +]; + +#[test] +fn explicit_std_vec_i64_import_loads_repo_root_standard_source() { + let compiler_root = Path::new(env!("CARGO_MANIFEST_DIR")); + let project = compiler_root.join("../examples/projects/std-import-vec_i64"); + let slovo_vec_i64 = compiler_root.join("../lib/std/vec_i64.slo"); + + assert!( + !project.join("src/vec_i64.slo").exists(), + "std-import-vec_i64 must not carry a source-root vec_i64 module copy" + ); + assert!( + !project.join("std/vec_i64.slo").exists(), + "std-import-vec_i64 must not carry a project-local std/vec_i64.slo copy" + ); + let main = read(&project.join("src/main.slo")); + assert!( + main.starts_with("(module main)\n\n(import std.vec_i64 ("), + "std-import-vec_i64 must exercise explicit `std.vec_i64` import syntax" + ); + + let slovo_source = read(&slovo_vec_i64); + assert!( + slovo_source.starts_with("(module vec_i64 (export "), + "repo-root Slovo std/vec_i64.slo must export imported helpers directly" + ); + assert!( + !slovo_source.contains("capacity") + && !slovo_source.contains("reserve") + && !slovo_source.contains("shrink") + && !slovo_source.contains("sort") + && !slovo_source.contains("map") + && !slovo_source.contains("filter") + && !slovo_source.contains("(vec i32)") + && !slovo_source.contains("(vec f64)") + && !slovo_source.contains("(vec string)") + && !slovo_source.contains("(vec bool)") + && !slovo_source.contains("(option f64)") + && !slovo_source.contains("(option string)") + && !slovo_source.contains("(option bool)") + && !slovo_source.contains("(result i32") + && !slovo_source.contains("(result i64") + && !slovo_source.contains("(result f64") + && !slovo_source.contains("(result string") + && !slovo_source.contains("(result bool"), + "std/vec_i64.slo must stay narrow and explicit" + ); + for helper in STANDARD_VEC_I64_SOURCE_FACADE_ALPHA { + assert!( + slovo_source.contains(&format!("(fn {} ", helper)), + "Slovo std/vec_i64.slo is missing helper `{}`", + helper + ); + assert!( + main.contains(helper), + "std-import-vec_i64 main fixture import/use is missing helper `{}`", + helper + ); + } + + let fmt = run_glagol([ + OsStr::new("fmt"), + OsStr::new("--check"), + project.as_os_str(), + ]); + assert_success("std vec_i64 facade source search fmt --check", &fmt); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std vec_i64 facade source search check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + test, + EXPECTED_STD_VEC_I64_OUTPUT, + "std vec_i64 facade source search test", + ); +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn read(path: &Path) -> String { + fs::read_to_string(path).unwrap_or_else(|err| panic!("read `{}`: {}", path.display(), err)) +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstatus: {:?}\nstdout:\n{}\nstderr:\n{}", + context, + output.status.code(), + stdout, + stderr + ); +} + +fn assert_success_stdout(output: Output, expected: &str, context: &str) { + assert_success(context, &output); + let stdout = String::from_utf8_lossy(&output.stdout); + assert_eq!(stdout, expected, "{}", context); +} diff --git a/compiler/tests/standard_vec_string_source_helpers_alpha.rs b/compiler/tests/standard_vec_string_source_helpers_alpha.rs new file mode 100644 index 0000000..4dc1246 --- /dev/null +++ b/compiler/tests/standard_vec_string_source_helpers_alpha.rs @@ -0,0 +1,245 @@ +use std::{ + ffi::OsStr, + fs, + path::Path, + process::{Command, Output}, +}; + +const EXPECTED_TEST_OUTPUT: &str = concat!( + "test \"explicit local vec_string empty len facade\" ... ok\n", + "test \"explicit local vec_string direct at facade\" ... ok\n", + "test \"explicit local vec_string builder helpers\" ... ok\n", + "test \"explicit local vec_string query helpers\" ... ok\n", + "test \"explicit local vec_string option query helpers\" ... ok\n", + "test \"explicit local vec_string starts_with helper\" ... ok\n", + "test \"explicit local vec_string ends_with helper\" ... ok\n", + "test \"explicit local vec_string without_suffix helper\" ... ok\n", + "test \"explicit local vec_string without_prefix helper\" ... ok\n", + "test \"explicit local vec_string transform helpers\" ... ok\n", + "test \"explicit local vec_string subvec helper\" ... ok\n", + "test \"explicit local vec_string insert helper\" ... ok\n", + "test \"explicit local vec_string insert range helper\" ... ok\n", + "test \"explicit local vec_string replace helper\" ... ok\n", + "test \"explicit local vec_string replace range helper\" ... ok\n", + "test \"explicit local vec_string remove helper\" ... ok\n", + "test \"explicit local vec_string remove range helper\" ... ok\n", + "test \"explicit local vec_string real-program helpers\" ... ok\n", + "test \"explicit local vec_string helpers all\" ... ok\n", + "19 test(s) passed\n", +); + +const STANDARD_VEC_STRING_SOURCE_FACADE_ALPHA: &[&str] = &[ + "empty", + "append", + "len", + "at", + "singleton", + "append2", + "append3", + "pair", + "triple", + "is_empty", + "index_or", + "first_or", + "last_or", + "index_option", + "first_option", + "last_option", + "index_of_option", + "last_index_of_option", + "contains", + "count_of", + "concat", + "take", + "starts_with", + "without_prefix", + "ends_with", + "without_suffix", + "drop", + "reverse", + "subvec", + "insert_at", + "insert_range", + "replace_at", + "replace_range", + "remove_at", + "remove_range", +]; + +#[test] +fn standard_vec_string_source_helper_project_checks_formats_and_tests() { + let project = Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../examples/projects/std-layout-local-vec_string"); + + assert_local_vec_string_fixture_is_source_authored(&project); + + let fmt = run_glagol([ + OsStr::new("fmt"), + OsStr::new("--check"), + project.as_os_str(), + ]); + assert_success("std layout local vec_string fmt --check", &fmt); + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std layout local vec_string check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + test, + EXPECTED_TEST_OUTPUT, + "std layout local vec_string test output", + ); +} + +fn assert_local_vec_string_fixture_is_source_authored(project: &Path) { + let vec_string = read(&project.join("src/vec_string.slo")); + let main = read(&project.join("src/main.slo")); + + assert!( + vec_string.starts_with("(module vec_string (export "), + "vec_string.slo must stay an explicit local module export" + ); + assert!( + main.starts_with("(module main)\n\n(import vec_string ("), + "main.slo must stay an explicit local vec_string import" + ); + assert!( + !vec_string.contains("(import std") + && !main.contains("(import std.") + && !vec_string.contains("(import slovo.std") + && !main.contains("(import slovo.std"), + "vec_string fixture must not depend on automatic or package std imports" + ); + assert!( + !vec_string.contains("(var ") + && !main.contains("(var ") + && !vec_string.contains("(set ") + && !main.contains("(set "), + "vec_string fixture must stay recursive and immutable without mutating locals" + ); + assert!( + !vec_string.contains("(vec i32)") + && !main.contains("(vec i32)") + && !vec_string.contains("(vec i64)") + && !main.contains("(vec i64)") + && !vec_string.contains("(vec f64)") + && !main.contains("(vec f64)") + && !vec_string.contains("(vec bool)") + && !main.contains("(vec bool)") + && !vec_string.contains("(option i64)") + && !main.contains("(option i64)") + && !vec_string.contains("(option f64)") + && !main.contains("(option f64)") + && !vec_string.contains("(option bool)") + && !main.contains("(option bool)") + && !vec_string.contains("(result i32") + && !main.contains("(result i32") + && !vec_string.contains("(result i64") + && !main.contains("(result i64") + && !vec_string.contains("(result f64") + && !main.contains("(result f64") + && !vec_string.contains("(result string") + && !main.contains("(result string") + && !vec_string.contains("(result bool") + && !main.contains("(result bool"), + "vec_string fixture must stay limited to concrete vec string plus option i32/string helpers" + ); + + let mut non_vec_std = vec_string.clone(); + for allowed in [ + "std.vec.string.empty", + "std.vec.string.append", + "std.vec.string.len", + "std.vec.string.index", + ] { + non_vec_std = non_vec_std.replace(allowed, ""); + } + assert!( + !non_vec_std.contains("std.") && !main.contains("std."), + "vec_string fixture must use only the promoted std.vec.string runtime names" + ); + assert!( + !vec_string.contains("capacity") + && !vec_string.contains("reserve") + && !vec_string.contains("shrink") + && !vec_string.contains("sort") + && !vec_string.contains("map") + && !vec_string.contains("filter") + && !vec_string.contains("concat_all"), + "vec_string fixture must not claim deferred generic or mutation-heavy collection APIs" + ); + + for helper in STANDARD_VEC_STRING_SOURCE_FACADE_ALPHA { + assert!( + vec_string.contains(&format!("(fn {} ", helper)), + "vec_string.slo is missing source helper `{}`", + helper + ); + assert!( + main.contains(helper), + "main.slo does not explicitly import/use `{}`", + helper + ); + } + for helper in [ + "index_of_option_loop", + "last_index_of_option_loop", + "contains_loop", + "count_of_loop", + "concat_loop", + "take_loop", + "drop_loop", + "reverse_loop", + ] { + assert!( + vec_string.contains(&format!("(fn {} ", helper)), + "vec_string.slo is missing recursive source helper `{}`", + helper + ); + } +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn read(path: &Path) -> String { + fs::read_to_string(path).unwrap_or_else(|err| panic!("read `{}`: {}", path.display(), err)) +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} + +fn assert_success_stdout(output: Output, expected: &str, context: &str) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert_eq!(stdout, expected, "{} stdout drifted", context); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} diff --git a/compiler/tests/standard_vec_string_source_search_alpha.rs b/compiler/tests/standard_vec_string_source_search_alpha.rs new file mode 100644 index 0000000..5c352cb --- /dev/null +++ b/compiler/tests/standard_vec_string_source_search_alpha.rs @@ -0,0 +1,191 @@ +use std::{ + ffi::OsStr, + fs, + path::Path, + process::{Command, Output}, +}; + +const EXPECTED_STD_VEC_STRING_OUTPUT: &str = concat!( + "test \"explicit std vec_string empty len facade\" ... ok\n", + "test \"explicit std vec_string direct at facade\" ... ok\n", + "test \"explicit std vec_string builder helpers\" ... ok\n", + "test \"explicit std vec_string query helpers\" ... ok\n", + "test \"explicit std vec_string option query helpers\" ... ok\n", + "test \"explicit std vec_string starts_with helper\" ... ok\n", + "test \"explicit std vec_string ends_with helper\" ... ok\n", + "test \"explicit std vec_string without_suffix helper\" ... ok\n", + "test \"explicit std vec_string without_prefix helper\" ... ok\n", + "test \"explicit std vec_string transform helpers\" ... ok\n", + "test \"explicit std vec_string subvec helper\" ... ok\n", + "test \"explicit std vec_string insert helper\" ... ok\n", + "test \"explicit std vec_string insert range helper\" ... ok\n", + "test \"explicit std vec_string replace helper\" ... ok\n", + "test \"explicit std vec_string replace range helper\" ... ok\n", + "test \"explicit std vec_string remove helper\" ... ok\n", + "test \"explicit std vec_string remove range helper\" ... ok\n", + "test \"explicit std vec_string real-program helpers\" ... ok\n", + "test \"explicit std vec_string helpers all\" ... ok\n", + "19 test(s) passed\n", +); + +const STANDARD_VEC_STRING_SOURCE_FACADE_ALPHA: &[&str] = &[ + "empty", + "append", + "len", + "at", + "singleton", + "append2", + "append3", + "pair", + "triple", + "is_empty", + "index_or", + "first_or", + "last_or", + "index_option", + "first_option", + "last_option", + "index_of_option", + "last_index_of_option", + "contains", + "count_of", + "concat", + "take", + "starts_with", + "without_prefix", + "ends_with", + "without_suffix", + "drop", + "reverse", + "subvec", + "insert_at", + "insert_range", + "replace_at", + "replace_range", + "remove_at", + "remove_range", +]; + +#[test] +fn explicit_std_vec_string_import_loads_repo_root_standard_source_when_present() { + let compiler_root = Path::new(env!("CARGO_MANIFEST_DIR")); + let project = compiler_root.join("../examples/projects/std-import-vec_string"); + let slovo_vec_string = compiler_root.join("../lib/std/vec_string.slo"); + + assert!( + !project.join("src/vec_string.slo").exists(), + "std-import-vec_string must not carry a source-root vec_string module copy" + ); + assert!( + !project.join("std/vec_string.slo").exists(), + "std-import-vec_string must not carry a project-local std/vec_string.slo copy" + ); + let main = read(&project.join("src/main.slo")); + assert!( + main.starts_with("(module main)\n\n(import std.vec_string ("), + "std-import-vec_string must exercise explicit `std.vec_string` import syntax" + ); + for helper in STANDARD_VEC_STRING_SOURCE_FACADE_ALPHA { + assert!( + main.contains(helper), + "std-import-vec_string main fixture import/use is missing helper `{}`", + helper + ); + } + + let fmt = run_glagol([ + OsStr::new("fmt"), + OsStr::new("--check"), + project.as_os_str(), + ]); + assert_success("std vec_string facade source search fmt --check", &fmt); + + if !slovo_vec_string.exists() { + eprintln!( + "skipping std vec_string source-search check/test: missing `{}`", + slovo_vec_string.display() + ); + return; + } + + let slovo_source = read(&slovo_vec_string); + assert!( + slovo_source.starts_with("(module vec_string (export "), + "repo-root Slovo std/vec_string.slo must export imported helpers directly" + ); + assert!( + !slovo_source.contains("capacity") + && !slovo_source.contains("reserve") + && !slovo_source.contains("shrink") + && !slovo_source.contains("sort") + && !slovo_source.contains("map") + && !slovo_source.contains("filter") + && !slovo_source.contains("concat_all") + && !slovo_source.contains("(vec i32)") + && !slovo_source.contains("(vec i64)") + && !slovo_source.contains("(vec f64)") + && !slovo_source.contains("(vec bool)") + && !slovo_source.contains("(option i64)") + && !slovo_source.contains("(option f64)") + && !slovo_source.contains("(option bool)") + && !slovo_source.contains("(result i32") + && !slovo_source.contains("(result i64") + && !slovo_source.contains("(result f64") + && !slovo_source.contains("(result string") + && !slovo_source.contains("(result bool"), + "std/vec_string.slo must stay narrow and explicit within concrete vec string plus option i32/string helpers" + ); + for helper in STANDARD_VEC_STRING_SOURCE_FACADE_ALPHA { + assert!( + slovo_source.contains(&format!("(fn {} ", helper)), + "Slovo std/vec_string.slo is missing helper `{}`", + helper + ); + } + + let check = run_glagol([OsStr::new("check"), project.as_os_str()]); + assert_success_stdout(check, "", "std vec_string facade source search check"); + + let test = run_glagol([OsStr::new("test"), project.as_os_str()]); + assert_success_stdout( + test, + EXPECTED_STD_VEC_STRING_OUTPUT, + "std vec_string facade source search test", + ); +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn read(path: &Path) -> String { + fs::read_to_string(path).unwrap_or_else(|err| panic!("read `{}`: {}", path.display(), err)) +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstatus: {:?}\nstdout:\n{}\nstderr:\n{}", + context, + output.status.code(), + stdout, + stderr + ); +} + +fn assert_success_stdout(output: Output, expected: &str, context: &str) { + assert_success(context, &output); + let stdout = String::from_utf8_lossy(&output.stdout); + assert_eq!(stdout, expected, "{}", context); +} diff --git a/compiler/tests/standard_workspace_source_search_alpha.rs b/compiler/tests/standard_workspace_source_search_alpha.rs new file mode 100644 index 0000000..df5abd8 --- /dev/null +++ b/compiler/tests/standard_workspace_source_search_alpha.rs @@ -0,0 +1,97 @@ +use std::{ + ffi::OsStr, + fs, + path::Path, + process::{Command, Output}, +}; + +const STANDARD_OPTION_RESULT_BRIDGE_HELPERS_ALPHA: &[&str] = &[ + "some_or_err_i32", + "some_or_err_i64", + "some_or_err_f64", + "some_or_err_bool", + "some_or_err_string", +]; + +#[test] +fn workspace_package_imports_standard_source_module() { + let compiler_root = Path::new(env!("CARGO_MANIFEST_DIR")); + let workspace = compiler_root.join("../examples/workspaces/std-import-option"); + let main = workspace.join("packages/app/src/main.slo"); + let slovo_option = compiler_root.join("../lib/std/option.slo"); + + assert!( + !workspace.join("packages/app/src/option.slo").exists(), + "workspace std-import-option must not carry a local option module copy" + ); + assert!( + !workspace.join("packages/app/src/bridge.slo").exists(), + "workspace std-import-option must not depend on a local bridge shim" + ); + assert!( + read(&main).starts_with("(module main)\n\n(import std.option ("), + "workspace fixture must exercise explicit `std.option` import syntax" + ); + assert!( + read(&slovo_option).starts_with("(module option (export "), + "repo-root Slovo std/option.slo must export imported helpers directly" + ); + for helper in STANDARD_OPTION_RESULT_BRIDGE_HELPERS_ALPHA { + assert!( + read(&slovo_option).contains(&format!("(fn {} ", helper)), + "repo-root Slovo std/option.slo is missing bridge helper `{}`", + helper + ); + assert!( + read(&main).contains(helper), + "workspace std-import-option must import/use bridge helper `{}`", + helper + ); + } + + let check = run_glagol([OsStr::new("check"), workspace.as_os_str()]); + assert_success_stdout(check, "", "workspace std option check"); + + let test = run_glagol([OsStr::new("test"), workspace.as_os_str()]); + assert_success_stdout( + test, + "test \"workspace std option import\" ... ok\n1 test(s) passed\n", + "workspace std option test", + ); +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn read(path: &Path) -> String { + fs::read_to_string(path).unwrap_or_else(|err| panic!("read `{}`: {}", path.display(), err)) +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstatus: {:?}\nstdout:\n{}\nstderr:\n{}", + context, + output.status.code(), + stdout, + stderr + ); +} + +fn assert_success_stdout(output: Output, expected: &str, context: &str) { + assert_success(context, &output); + let stdout = String::from_utf8_lossy(&output.stdout); + assert_eq!(stdout, expected, "{}", context); +} diff --git a/compiler/tests/strict_v0_cli.rs b/compiler/tests/strict_v0_cli.rs new file mode 100644 index 0000000..c964d4b --- /dev/null +++ b/compiler/tests/strict_v0_cli.rs @@ -0,0 +1,1322 @@ +use std::{ + fs, + path::PathBuf, + process::{Command, Output}, + sync::atomic::{AtomicUsize, Ordering}, +}; + +static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0); + +#[test] +fn unknown_function_is_rejected_without_panic() { + assert_rejected( + "unknown-function", + r#" +(module main) + +(fn main () -> i32 + (missing 1)) +"#, + "UnknownFunction", + ); +} + +#[test] +fn arity_mismatch_is_rejected_without_panic() { + assert_rejected( + "arity-mismatch", + r#" +(module main) + +(fn add ((a i32) (b i32)) -> i32 + (+ a b)) + +(fn main () -> i32 + (add 1)) +"#, + "ArityMismatch", + ); +} + +#[test] +fn type_mismatch_is_rejected_without_panic() { + assert_rejected( + "type-mismatch", + r#" +(module main) + +(fn id ((value i32)) -> i32 + value) + +(fn main () -> i32 + (id true)) +"#, + "TypeMismatch", + ); +} + +#[test] +fn unclosed_list_is_rejected_without_panic() { + assert_rejected( + "unclosed-list", + r#" +(module main) + +(fn main () -> i32 + (+ 1 2) +"#, + "UnclosedList", + ); +} + +#[test] +fn unknown_top_level_form_is_rejected_without_panic() { + assert_rejected( + "unknown-top-level-form", + r#" +(module main) + +(bogus top level) +"#, + "UnknownTopLevelForm", + ); +} + +#[test] +fn checked_if_emits_llvm_without_panic() { + let output = run_compiler( + "checked-if", + r#" +(module main) + +(fn main () -> i32 + (if true 1 0)) +"#, + ); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "compiler rejected checked if\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr, + ); + assert!( + stdout.contains("br i1") && stdout.contains(" phi i32 "), + "checked if LLVM did not include branch and phi\nstdout:\n{}", + stdout, + ); + assert!( + !stderr.contains("panicked at") && !stderr.contains("thread 'main' panicked"), + "compiler panicked for checked if\nstderr:\n{}", + stderr, + ); +} + +#[test] +fn top_level_test_does_not_break_compile_to_llvm() { + let output = run_compiler( + "top-level-test-compile", + r#" +(module main) + +(fn add ((a i32) (b i32)) -> i32 + (+ a b)) + +(test "add works" + (= (add 2 3) 5)) + +(fn main () -> i32 + 0) +"#, + ); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "compiler rejected top-level test fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr, + ); + assert!( + stdout.contains("define i32 @add") && stdout.contains("define i32 @main"), + "compiler did not emit expected functions\nstdout:\n{}", + stdout, + ); + assert!( + !stdout.contains("add works"), + "compiler emitted test metadata into LLVM output\nstdout:\n{}", + stdout, + ); + assert!( + !stderr.contains("panicked at") && !stderr.contains("thread 'main' panicked"), + "compiler panicked for top-level test fixture\nstderr:\n{}", + stderr, + ); +} + +#[test] +fn explicit_emit_llvm_matches_default_compile_mode() { + let source = r#" +(module main) + +(fn main () -> i32 + 0) +"#; + + let default = run_compiler("default-compile-mode", source); + let explicit = run_compiler_with_args("explicit-emit-llvm", source, ["--emit=llvm"]); + + assert_success_contains_needle("default compile", default, "define i32 @main"); + assert_success_contains_needle("explicit --emit=llvm", explicit, "define i32 @main"); +} + +#[test] +fn output_file_receives_default_compile_output_without_stdout() { + let fixture = write_fixture( + "default-output-file", + r#" +(module main) + +(fn main () -> i32 + 0) +"#, + ); + let output_path = temp_path("default-output-file", "ll"); + + let output = Command::new(compiler_path()) + .arg("-o") + .arg(&output_path) + .arg(&fixture) + .output() + .unwrap_or_else(|err| panic!("run glagol on `{}`: {}", fixture.display(), err)); + + assert_success_empty_stdout("default compile with -o", output); + let output_file = fs::read_to_string(&output_path) + .unwrap_or_else(|err| panic!("read `{}`: {}", output_path.display(), err)); + assert!( + output_file.contains("define i32 @main"), + "output file did not contain LLVM IR\n{}", + output_file, + ); +} + +#[test] +fn output_file_receives_explicit_emit_llvm_output() { + let fixture = write_fixture( + "explicit-emit-output-file", + r#" +(module main) + +(fn main () -> i32 + 0) +"#, + ); + let output_path = temp_path("explicit-emit-output-file", "ll"); + + let output = Command::new(compiler_path()) + .arg("--emit=llvm") + .arg(&fixture) + .arg("-o") + .arg(&output_path) + .output() + .unwrap_or_else(|err| panic!("run glagol on `{}`: {}", fixture.display(), err)); + + assert_success_empty_stdout("explicit --emit=llvm with -o", output); + let output_file = fs::read_to_string(&output_path) + .unwrap_or_else(|err| panic!("read `{}`: {}", output_path.display(), err)); + assert!( + output_file.contains("define i32 @main"), + "output file did not contain LLVM IR\n{}", + output_file, + ); +} + +#[test] +fn manifest_records_successful_stdout_output() { + let fixture = write_fixture( + "manifest-stdout-success", + r#" +(module main) + +(fn main () -> i32 + 0) +"#, + ); + let manifest_path = temp_path("manifest-stdout-success", "manifest.slo"); + + let output = Command::new(compiler_path()) + .arg("--manifest") + .arg(&manifest_path) + .arg(&fixture) + .output() + .unwrap_or_else(|err| panic!("run glagol on `{}`: {}", fixture.display(), err)); + + assert_success_contains_needle("manifest stdout success", output, "define i32 @main"); + let manifest = read_manifest(&manifest_path); + assert!( + manifest.contains("(artifact-manifest\n") + && manifest.contains(" (schema slovo.artifact-manifest)\n") + && manifest.contains(" (version 1)\n") + && manifest.contains(" (mode emit-llvm)\n") + && manifest.contains(" (success true)\n") + && manifest.contains(" (diagnostics-schema-version 1)\n") + && manifest.contains(" (kind llvm-ir)\n") + && manifest.contains(" (stdout \"") + && manifest.contains("define i32 @main"), + "manifest did not record successful stdout LLVM output\n{}", + manifest, + ); +} + +#[test] +fn manifest_records_output_file_path_for_o() { + let fixture = write_fixture( + "manifest-output-file", + r#" +(module main) + +(fn main () -> i32 + 0) +"#, + ); + let output_path = temp_path("manifest-output-file", "ll"); + let manifest_path = temp_path("manifest-output-file", "manifest.slo"); + + let output = Command::new(compiler_path()) + .arg("--emit=llvm") + .arg("-o") + .arg(&output_path) + .arg("--manifest") + .arg(&manifest_path) + .arg(&fixture) + .output() + .unwrap_or_else(|err| panic!("run glagol on `{}`: {}", fixture.display(), err)); + + assert_success_empty_stdout("manifest with -o", output); + let output_file = fs::read_to_string(&output_path) + .unwrap_or_else(|err| panic!("read `{}`: {}", output_path.display(), err)); + assert!( + output_file.contains("define i32 @main"), + "output file did not contain LLVM IR\n{}", + output_file, + ); + + let manifest = read_manifest(&manifest_path); + assert!( + manifest.contains(" (success true)\n") + && manifest.contains(" (kind llvm-ir)\n") + && manifest.contains(&format!(" (path \"{}\")", output_path.display())) + && manifest.contains(" (artifacts\n") + && !manifest.contains(" (stdout \""), + "manifest did not record -o path as the primary output\n{}", + manifest, + ); +} + +#[test] +fn manifest_records_source_diagnostic_failure() { + let fixture = write_fixture( + "manifest-diagnostic-failure", + r#" +(module main) + +(fn id ((value i32)) -> i32 + value) + +(fn main () -> i32 + (id true)) +"#, + ); + let manifest_path = temp_path("manifest-diagnostic-failure", "manifest.slo"); + + let output = Command::new(compiler_path()) + .arg("--manifest") + .arg(&manifest_path) + .arg(&fixture) + .output() + .unwrap_or_else(|err| panic!("run glagol on `{}`: {}", fixture.display(), err)); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert_exit_code("manifest diagnostic failure", &output, 1); + assert!( + stdout.is_empty(), + "diagnostic failure wrote stdout:\n{}", + stdout + ); + assert!( + stderr.contains("(diagnostic\n") + && stderr.contains(" (schema slovo.diagnostic)\n") + && stderr.contains(" (version 1)\n") + && stderr.contains(" (code TypeMismatch)\n"), + "stderr did not contain v1 machine diagnostics\n{}", + stderr, + ); + + let manifest = read_manifest(&manifest_path); + assert!( + manifest.contains(" (success false)\n") + && manifest.contains(" (diagnostics-schema-version 1)\n") + && manifest.contains(" (kind diagnostics)\n") + && manifest.contains(" (stream stderr)\n") + && manifest.contains("slovo.diagnostic") + && manifest.contains("TypeMismatch"), + "manifest did not record diagnostic failure\n{}", + manifest, + ); +} + +#[test] +fn manifest_records_run_tests_summary() { + let fixture = write_fixture( + "manifest-run-tests", + r#" +(module main) + +(fn add ((a i32) (b i32)) -> i32 + (+ a b)) + +(test "add works" + (= (add 2 3) 5)) +"#, + ); + let manifest_path = temp_path("manifest-run-tests", "manifest.slo"); + + let output = Command::new(compiler_path()) + .arg("--run-tests") + .arg("--manifest") + .arg(&manifest_path) + .arg(&fixture) + .output() + .unwrap_or_else(|err| panic!("run glagol on `{}`: {}", fixture.display(), err)); + + assert_success_contains( + "manifest run tests", + output, + "test \"add works\" ... ok\n1 test(s) passed\n", + ); + let manifest = read_manifest(&manifest_path); + assert!( + manifest.contains(" (mode run-tests)\n") + && manifest.contains(" (test-report\n") + && manifest.contains(" (total 1)\n") + && manifest.contains(" (passed 1)\n") + && manifest.contains(" (failed 0)\n") + && manifest.contains(" (skipped 0)\n"), + "manifest did not include run-test summary\n{}", + manifest, + ); +} + +#[test] +fn run_tests_filter_selects_skips_and_records_manifest_counts() { + let fixture = write_fixture( + "run-tests-filter", + r#" +(module main) + +(test "alpha first" true) +(test "Alpha case" false) +(test "beta second" true) +"#, + ); + let manifest_path = temp_path("run-tests-filter", "manifest.slo"); + + let output = Command::new(compiler_path()) + .arg("test") + .arg(&fixture) + .arg("--filter") + .arg("alpha") + .arg("--manifest") + .arg(&manifest_path) + .output() + .unwrap_or_else(|err| panic!("run glagol on `{}`: {}", fixture.display(), err)); + + assert_success_contains( + "filtered canonical test", + output, + "test \"alpha first\" ... ok\n\ +test \"Alpha case\" ... skipped\n\ +test \"beta second\" ... skipped\n\ +1 test(s) passed (total_discovered 3, selected 1, passed 1, failed 0, skipped 2, filter \"alpha\")\n", + ); + let manifest = read_manifest(&manifest_path); + assert!( + manifest.contains(" (total 3)\n") + && manifest.contains(" (total_discovered 3)\n") + && manifest.contains(" (selected 1)\n") + && manifest.contains(" (passed 1)\n") + && manifest.contains(" (failed 0)\n") + && manifest.contains(" (skipped 2)\n") + && manifest.contains(" (filter \"alpha\")\n"), + "manifest did not include filtered run-test summary\n{}", + manifest, + ); +} + +#[test] +fn legacy_run_tests_filter_zero_match_is_success() { + let output = run_compiler_with_args( + "legacy-run-tests-filter-zero-match", + r#" +(module main) + +(test "one" true) +(test "two" true) +"#, + ["--run-tests", "--filter", "missing"], + ); + + assert_success_contains( + "legacy filtered zero match", + output, + "test \"one\" ... skipped\n\ +test \"two\" ... skipped\n\ +0 test(s) passed (total_discovered 2, selected 0, passed 0, failed 0, skipped 2, filter \"missing\")\n", + ); +} + +#[test] +fn run_tests_filter_selected_failure_records_counts() { + let fixture = write_fixture( + "run-tests-filter-selected-failure", + r#" +(module main) + +(test "passing skipped" true) +(test "target failure" false) +"#, + ); + let manifest_path = temp_path("run-tests-filter-selected-failure", "manifest.slo"); + + let output = Command::new(compiler_path()) + .arg("test") + .arg(&fixture) + .arg("--filter") + .arg("target") + .arg("--manifest") + .arg(&manifest_path) + .output() + .unwrap_or_else(|err| panic!("run glagol on `{}`: {}", fixture.display(), err)); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert_exit_code("filtered selected failure", &output, 1); + assert!( + output.stdout.is_empty() + && stderr.contains("TestFailed") + && stderr.contains("test summary: total_discovered 2, selected 1, passed 0, failed 1, skipped 1, filter \"target\"") + && !stderr.contains("passing skipped"), + "filtered failure had unexpected output\nstdout:\n{}\nstderr:\n{}", + String::from_utf8_lossy(&output.stdout), + stderr, + ); + let manifest = read_manifest(&manifest_path); + assert!( + manifest.contains(" (success false)\n") + && manifest.contains(" (total_discovered 2)\n") + && manifest.contains(" (selected 1)\n") + && manifest.contains(" (passed 0)\n") + && manifest.contains(" (failed 1)\n") + && manifest.contains(" (skipped 1)\n") + && manifest.contains(" (filter \"target\")\n"), + "manifest did not include filtered failure summary\n{}", + manifest, + ); +} + +#[test] +fn filter_cli_misuse_is_rejected() { + let fixture = write_fixture( + "filter-cli-misuse", + "(module main)\n\n(fn main () -> i32\n 0)\n", + ); + + let missing = Command::new(compiler_path()) + .arg("test") + .arg(&fixture) + .arg("--filter") + .output() + .unwrap_or_else(|err| panic!("run glagol on `{}`: {}", fixture.display(), err)); + assert_cli_rejected("missing filter value", missing, "`--filter` requires"); + + let duplicate = Command::new(compiler_path()) + .arg("test") + .arg(&fixture) + .arg("--filter") + .arg("one") + .arg("--filter") + .arg("two") + .output() + .unwrap_or_else(|err| panic!("run glagol on `{}`: {}", fixture.display(), err)); + assert_cli_rejected( + "duplicate filter", + duplicate, + "`--filter` was provided more than once", + ); + + let wrong_mode = Command::new(compiler_path()) + .arg("check") + .arg(&fixture) + .arg("--filter") + .arg("one") + .output() + .unwrap_or_else(|err| panic!("run glagol on `{}`: {}", fixture.display(), err)); + assert_cli_rejected( + "filter wrong mode", + wrong_mode, + "`--filter` is only supported", + ); +} + +#[test] +fn unreadable_source_file_writes_failure_manifest_after_manifest_path_is_parsed() { + let mut source_path = std::env::temp_dir(); + let id = NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed); + source_path.push(format!( + "glagol-strict-v0-{}-{}-manifest-missing.slo", + std::process::id(), + id + )); + let manifest_path = temp_path("manifest-unreadable-source", "manifest.slo"); + + let output = Command::new(compiler_path()) + .arg("--manifest") + .arg(&manifest_path) + .arg(&source_path) + .output() + .unwrap_or_else(|err| panic!("run glagol on `{}`: {}", source_path.display(), err)); + + assert_exit_code("unreadable source with manifest", &output, 1); + let manifest = read_manifest(&manifest_path); + assert!( + manifest.contains(" (success false)\n") + && manifest.contains(" (mode emit-llvm)\n") + && manifest.contains("InputReadFailed"), + "input/read failure manifest mismatch\n{}", + manifest + ); +} + +#[test] +fn manifest_path_must_not_match_output_path() { + let fixture = write_fixture( + "manifest-output-same-path", + r#" +(module main) + +(fn main () -> i32 + 0) +"#, + ); + let shared_path = temp_path("manifest-output-same-path", "out"); + + let output = Command::new(compiler_path()) + .arg("-o") + .arg(&shared_path) + .arg("--manifest") + .arg(&shared_path) + .arg(&fixture) + .output() + .unwrap_or_else(|err| panic!("run glagol on `{}`: {}", fixture.display(), err)); + + assert_cli_rejected( + "manifest output same path", + output, + "output path and manifest path must be different", + ); +} + +#[test] +fn manifest_path_must_not_alias_output_path() { + let fixture = write_fixture( + "manifest-output-alias-path", + r#" +(module main) + +(fn main () -> i32 + 0) +"#, + ); + let output_path = temp_path("manifest-output-alias-path", "out"); + let manifest_alias = output_path + .parent() + .expect("temp path has parent") + .join(".") + .join(output_path.file_name().expect("temp path has file name")); + + let output = Command::new(compiler_path()) + .arg("-o") + .arg(&output_path) + .arg("--manifest") + .arg(&manifest_alias) + .arg(&fixture) + .output() + .unwrap_or_else(|err| panic!("run glagol on `{}`: {}", fixture.display(), err)); + + assert_cli_rejected( + "manifest output alias path", + output, + "output path and manifest path must be different", + ); +} + +#[test] +fn output_file_receives_other_primary_mode_output() { + let fixture = write_fixture( + "format-output-file", + r#" +(module main) + +(fn main () -> i32 0) +"#, + ); + let output_path = temp_path("format-output-file", "slo"); + + let output = Command::new(compiler_path()) + .arg("--format") + .arg("-o") + .arg(&output_path) + .arg(&fixture) + .output() + .unwrap_or_else(|err| panic!("run glagol on `{}`: {}", fixture.display(), err)); + + assert_success_empty_stdout("--format with -o", output); + let output_file = fs::read_to_string(&output_path) + .unwrap_or_else(|err| panic!("read `{}`: {}", output_path.display(), err)); + assert_eq!( + output_file, "(module main)\n\n(fn main () -> i32\n 0)\n", + "formatted output file mismatch", + ); +} + +#[test] +fn top_level_tests_can_be_checked_and_run() { + let source = r#" +(module main) + +(fn add ((a i32) (b i32)) -> i32 + (+ a b)) + +(test "add works" + (= (add 2 3) 5)) +"#; + + let checked = run_compiler_with_args("top-level-test-check", source, ["--check-tests"]); + assert_success_contains( + "check top-level tests", + checked, + "test \"add works\" ... checked\n1 test(s) checked\n", + ); + + let run = run_compiler_with_args("top-level-test-run", source, ["--run-tests"]); + assert_success_contains( + "run top-level tests", + run, + "test \"add works\" ... ok\n1 test(s) passed\n", + ); +} + +#[test] +fn failing_top_level_test_is_reported_without_panic() { + assert_rejected_with_args( + "failing-top-level-test", + r#" +(module main) + +(test "false is false" + false) +"#, + ["--run-tests"], + "TestFailed", + ); +} + +#[test] +fn invalid_test_name_is_rejected_without_panic() { + assert_rejected( + "invalid-test-name", + r#" +(module main) + +(test not-a-string true) +"#, + "InvalidTestName", + ); +} + +#[test] +fn invalid_decoded_test_names_are_rejected_without_panic() { + let cases = [ + ( + "empty-test-name", + r#" +(module main) + +(test "" true) +"#, + ), + ( + "escaped-newline-test-name", + r#" +(module main) + +(test "bad\nname" true) +"#, + ), + ( + "escaped-quote-test-name", + r#" +(module main) + +(test "bad\"name" true) +"#, + ), + ( + "escaped-backslash-test-name", + r#" +(module main) + +(test "bad\\name" true) +"#, + ), + ]; + + for (name, source) in cases { + assert_rejected(name, source, "InvalidTestName"); + } +} + +#[test] +fn duplicate_test_name_is_rejected_without_panic() { + assert_rejected( + "duplicate-test-name", + r#" +(module main) + +(test "same" true) +(test "same" true) +"#, + "DuplicateTestName", + ); +} + +#[test] +fn unsupported_escape_in_test_name_is_rejected_without_panic() { + assert_rejected( + "unsupported-escape-test-name", + r#" +(module main) + +(test "same" true) +(test "sa\me" true) +"#, + "UnsupportedStringEscape", + ); +} + +#[test] +fn non_bool_test_expression_is_rejected_without_panic() { + assert_rejected( + "non-bool-test-expression", + r#" +(module main) + +(test "not bool" + 1) +"#, + "TestExpressionNotBool", + ); +} + +#[test] +fn malformed_test_form_is_rejected_without_panic() { + assert_rejected( + "malformed-test-form", + r#" +(module main) + +(test "too many" true false) +"#, + "MalformedTestForm", + ); +} + +#[test] +fn test_modes_are_mutually_exclusive_without_order_dependence() { + let source = r#" +(module main) + +(test "ok" true) +"#; + let cases = [ + ("check-run-tests", ["--check-tests", "--run-tests"]), + ("run-check-tests", ["--run-tests", "--check-tests"]), + ("format-run-tests", ["--format", "--run-tests"]), + ("emit-format", ["--emit=llvm", "--format"]), + ("format-emit", ["--format", "--emit=llvm"]), + ( + "checked-lowering-check-tests", + ["--inspect-lowering=checked", "--check-tests"], + ), + ]; + + for (name, args) in cases { + let output = run_compiler_with_args(name, source, args); + assert_cli_rejected(name, output, "mode flags are mutually exclusive"); + } +} + +#[test] +fn string_literal_backend_gap_is_diagnostic_not_panic() { + assert_rejected( + "string-if", + r#" +(module main) + +(fn id ((value (ptr i32))) -> i32 + 0) +"#, + "UnsupportedBackendFeature", + ); +} + +#[test] +fn integer_out_of_range_is_rejected_without_panic() { + assert_rejected( + "integer-out-of-range", + r#" +(module main) + +(fn main () -> i32 + 2147483648) +"#, + "IntegerOutOfRange", + ); +} + +#[test] +fn unsupported_signature_type_is_rejected_without_panic() { + assert_rejected( + "unsupported-signature-type", + r#" +(module main) + +(fn id ((value (ptr i32))) -> i32 + 0) +"#, + "UnsupportedBackendFeature", + ); +} + +#[test] +fn unsupported_unit_return_signature_is_rejected_without_panic() { + assert_rejected( + "unsupported-unit-return-signature", + r#" +(module main) + +(fn main () -> unit + (print_i32 1)) +"#, + "UnsupportedUnitSignatureType", + ); +} + +#[test] +fn unsupported_unit_parameter_signature_is_rejected_without_panic() { + assert_rejected( + "unsupported-unit-parameter-signature", + r#" +(module main) + +(fn ignore ((value unit)) -> i32 + 0) +"#, + "UnsupportedUnitSignatureType", + ); +} + +#[test] +fn unsupported_unit_signatures_are_rejected_before_surface_lowering() { + assert_rejected_with_args( + "unsupported-unit-return-signature-surface-lowering", + r#" +(module main) + +(fn main () -> unit + (print_i32 1)) +"#, + ["--inspect-lowering=surface"], + "UnsupportedUnitSignatureType", + ); + assert_rejected_with_args( + "unsupported-unit-parameter-signature-surface-lowering", + r#" +(module main) + +(fn ignore ((value unit)) -> i32 + 0) +"#, + ["--inspect-lowering=surface"], + "UnsupportedUnitSignatureType", + ); +} + +#[test] +fn help_is_a_successful_usage_request() { + let output = run_compiler_raw(["--help"]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert_exit_code("help", &output, 0); + assert!(stdout.is_empty(), "help wrote stdout:\n{}", stdout); + assert!( + stderr.contains("usage: glagol") + && stderr.contains("--emit=llvm") + && stderr.contains("--format") + && stderr.contains("--print-tree") + && stderr.contains("--inspect-lowering=surface") + && stderr.contains("--inspect-lowering=checked") + && stderr.contains("--check-tests") + && stderr.contains("--run-tests") + && stderr.contains("-o ") + && stderr.contains("--manifest ") + && stderr.contains("--version"), + "help output did not describe the v0 CLI modes\nstderr:\n{}", + stderr, + ); +} + +#[test] +fn version_is_a_successful_metadata_request() { + let output = run_compiler_raw(["--version"]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert_exit_code("version", &output, 0); + assert_eq!( + stdout, + format!("{} {}\n", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")), + "version stdout mismatch", + ); + assert!(stderr.is_empty(), "version wrote stderr:\n{}", stderr); +} + +#[test] +fn missing_source_file_is_usage_error() { + let output = run_compiler_raw([]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert_exit_code("missing source file", &output, 2); + assert!( + stdout.is_empty(), + "missing source wrote stdout:\n{}", + stdout + ); + assert!( + stderr.contains("usage: glagol"), + "missing source did not print usage\nstderr:\n{}", + stderr, + ); +} + +#[test] +fn missing_output_path_is_usage_error() { + let output = run_compiler_raw(["-o"]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert_exit_code("missing output path", &output, 2); + assert!( + stdout.is_empty(), + "missing output path wrote stdout:\n{}", + stdout + ); + assert!( + stderr.contains("`-o` requires a following path") && stderr.contains("usage: glagol"), + "missing output path did not report usage failure\nstderr:\n{}", + stderr, + ); +} + +#[test] +fn unreadable_source_file_is_input_error() { + let mut path = std::env::temp_dir(); + let id = NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed); + path.push(format!( + "glagol-strict-v0-{}-{}-missing.slo", + std::process::id(), + id + )); + + let output = Command::new(compiler_path()) + .arg(&path) + .output() + .unwrap_or_else(|err| panic!("run glagol on `{}`: {}", path.display(), err)); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert_exit_code("unreadable source file", &output, 1); + assert!( + stdout.is_empty(), + "unreadable source wrote stdout:\n{}", + stdout + ); + assert!( + stderr.contains("cannot read"), + "unreadable source did not report input failure\nstderr:\n{}", + stderr, + ); +} + +#[test] +fn extra_argument_is_usage_error() { + let fixture = write_fixture( + "extra-argument", + r#" +(module main) + +(fn main () -> i32 + 0) +"#, + ); + + let output = Command::new(compiler_path()) + .arg(&fixture) + .arg("extra") + .output() + .unwrap_or_else(|err| panic!("run glagol on `{}`: {}", fixture.display(), err)); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert_exit_code("extra argument", &output, 2); + assert!( + stdout.is_empty(), + "extra argument wrote stdout:\n{}", + stdout + ); + assert!( + stderr.contains("unexpected argument `extra`") && stderr.contains("usage: glagol"), + "extra argument did not report usage failure\nstderr:\n{}", + stderr, + ); +} + +#[test] +fn explicit_emit_llvm_extra_argument_is_usage_error() { + let fixture = write_fixture( + "explicit-emit-extra-argument", + r#" +(module main) + +(fn main () -> i32 + 0) +"#, + ); + + let output = Command::new(compiler_path()) + .arg("--emit=llvm") + .arg(&fixture) + .arg("extra") + .output() + .unwrap_or_else(|err| panic!("run glagol on `{}`: {}", fixture.display(), err)); + + assert_cli_rejected( + "explicit emit extra argument", + output, + "unexpected argument `extra`", + ); +} + +fn assert_rejected(name: &str, source: &str, code: &str) { + assert_rejected_with_args(name, source, [], code); +} + +fn assert_rejected_with_args( + name: &str, + source: &str, + args: [&str; N], + code: &str, +) { + let output = run_compiler_with_args(name, source, args); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + !output.status.success(), + "compiler unexpectedly accepted fixture `{}`\nstdout:\n{}\nstderr:\n{}", + name, + stdout, + stderr, + ); + assert_exit_code(name, &output, 1); + assert!( + stdout.is_empty(), + "compiler emitted LLVM/stdout for rejected fixture `{}`\nstdout:\n{}\nstderr:\n{}", + name, + stdout, + stderr, + ); + assert!( + stderr.contains(&format!("error[{}]", code)) + || stderr.contains(&format!("(code {})", code)), + "stderr for fixture `{}` did not contain diagnostic code `{}`\nstderr:\n{}", + name, + code, + stderr, + ); + assert!( + !stderr.contains("panicked at") && !stderr.contains("thread 'main' panicked"), + "compiler panicked for fixture `{}`\nstderr:\n{}", + name, + stderr, + ); +} + +fn run_compiler(name: &str, source: &str) -> Output { + run_compiler_with_args(name, source, []) +} + +fn run_compiler_with_args(name: &str, source: &str, args: [&str; N]) -> Output { + let fixture = write_fixture(name, source); + + Command::new(compiler_path()) + .args(args) + .arg(&fixture) + .output() + .unwrap_or_else(|err| panic!("run glagol on `{}`: {}", fixture.display(), err)) +} + +fn run_compiler_raw(args: [&str; N]) -> Output { + Command::new(compiler_path()) + .args(args) + .output() + .unwrap_or_else(|err| panic!("run glagol: {}", err)) +} + +fn compiler_path() -> &'static str { + env!("CARGO_BIN_EXE_glagol") +} + +fn assert_success_contains(name: &str, output: Output, expected: &str) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + name, + stdout, + stderr, + ); + assert_eq!(stdout, expected, "{} stdout mismatch", name); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", name, stderr,); +} + +fn assert_success_contains_needle(name: &str, output: Output, expected: &str) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + name, + stdout, + stderr, + ); + assert!( + stdout.contains(expected), + "{} stdout did not contain `{}`\nstdout:\n{}", + name, + expected, + stdout, + ); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", name, stderr,); +} + +fn assert_success_empty_stdout(name: &str, output: Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + name, + stdout, + stderr, + ); + assert!(stdout.is_empty(), "{} wrote stdout:\n{}", name, stdout); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", name, stderr); +} + +fn assert_cli_rejected(name: &str, output: Output, expected: &str) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + !output.status.success(), + "compiler unexpectedly accepted CLI fixture `{}`\nstdout:\n{}\nstderr:\n{}", + name, + stdout, + stderr, + ); + assert_exit_code(name, &output, 2); + assert!( + stdout.is_empty(), + "compiler emitted stdout for rejected CLI fixture `{}`\nstdout:\n{}\nstderr:\n{}", + name, + stdout, + stderr, + ); + assert!( + stderr.contains(expected), + "stderr for CLI fixture `{}` did not contain `{}`\nstderr:\n{}", + name, + expected, + stderr, + ); + assert!( + !stderr.contains("panicked at") && !stderr.contains("thread 'main' panicked"), + "compiler panicked for CLI fixture `{}`\nstderr:\n{}", + name, + stderr, + ); +} + +fn write_fixture(name: &str, source: &str) -> PathBuf { + let path = temp_path(name, "slo"); + fs::write(&path, source).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err)); + path +} + +fn read_manifest(path: &PathBuf) -> String { + fs::read_to_string(path).unwrap_or_else(|err| panic!("read `{}`: {}", path.display(), err)) +} + +fn temp_path(name: &str, extension: &str) -> PathBuf { + let mut path = std::env::temp_dir(); + let id = NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed); + path.push(format!( + "glagol-strict-v0-{}-{}-{}.{}", + std::process::id(), + id, + name, + extension, + )); + path +} + +fn assert_exit_code(name: &str, output: &Output, expected: i32) { + assert_eq!( + output.status.code(), + Some(expected), + "{} exit code mismatch\nstdout:\n{}\nstderr:\n{}", + name, + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr), + ); +} diff --git a/compiler/tests/string_parse_bool_result_alpha.rs b/compiler/tests/string_parse_bool_result_alpha.rs new file mode 100644 index 0000000..4551d78 --- /dev/null +++ b/compiler/tests/string_parse_bool_result_alpha.rs @@ -0,0 +1,319 @@ +use std::{ + env, + ffi::OsStr, + fs, + path::{Path, PathBuf}, + process::{Command, Output}, + sync::atomic::{AtomicUsize, Ordering}, +}; + +static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0); + +#[test] +fn string_parse_bool_result_fixture_lowers_and_runs_tests() { + let fixture = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/string-parse-bool-result.slo"); + + let llvm = run_glagol([fixture.as_os_str()]); + assert_success("compile string-parse-bool-result fixture", &llvm); + let stdout = String::from_utf8_lossy(&llvm.stdout); + assert!( + stdout.contains("declare i32 @__glagol_string_parse_bool_result(ptr, ptr)") + && stdout.contains("define { i1, i1, i32 } @parse_bool(ptr %text)") + && stdout.contains("alloca i1") + && stdout.contains("store i1 0, ptr %") + && stdout.contains("call i32 @__glagol_string_parse_bool_result(ptr %text, ptr %") + && stdout.contains("load i1, ptr %") + && stdout.contains("insertvalue { i1, i1, i32 }") + && stdout.contains("extractvalue { i1, i1, i32 }") + && !stdout.contains("@std.string.parse_bool_result"), + "string-parse-bool-result LLVM shape drifted\nstdout:\n{}", + stdout + ); + + let tests = run_glagol([OsStr::new("test"), fixture.as_os_str()]); + assert_success("run string-parse-bool-result fixture tests", &tests); + assert_eq!( + String::from_utf8_lossy(&tests.stdout), + concat!( + "test \"parse bool true ok\" ... ok\n", + "test \"parse bool false ok\" ... ok\n", + "test \"parse bool uppercase err\" ... ok\n", + "test \"parse bool empty err\" ... ok\n", + "test \"parse bool whitespace err\" ... ok\n", + "test \"parse bool helper flow\" ... ok\n", + "6 test(s) passed\n", + ), + "string-parse-bool-result test runner stdout drifted" + ); +} + +#[test] +fn string_parse_bool_result_test_runner_covers_exact_ascii_contract() { + let source = r#" +(module main) + +(test "parse true ok" + (std.result.unwrap_ok (std.string.parse_bool_result "true"))) + +(test "parse false ok" + (if (std.result.unwrap_ok (std.string.parse_bool_result "false")) + false + true)) + +(test "parse empty err one" + (= (std.result.unwrap_err (std.string.parse_bool_result "")) 1)) + +(test "parse uppercase err one" + (= (std.result.unwrap_err (std.string.parse_bool_result "TRUE")) 1)) + +(test "parse mixed case err one" + (= (std.result.unwrap_err (std.string.parse_bool_result "False")) 1)) + +(test "parse leading whitespace err one" + (= (std.result.unwrap_err (std.string.parse_bool_result " true")) 1)) + +(test "parse trailing whitespace err one" + (= (std.result.unwrap_err (std.string.parse_bool_result "false ")) 1)) + +(test "parse numeric err one" + (= (std.result.unwrap_err (std.string.parse_bool_result "1")) 1)) + +(test "parse other text err one" + (= (std.result.unwrap_err (std.string.parse_bool_result "truth")) 1)) +"#; + let fixture = write_fixture("boundaries", source); + let output = run_glagol([OsStr::new("test"), fixture.as_os_str()]); + assert_success("run string parse bool boundary tests", &output); + assert_eq!( + String::from_utf8_lossy(&output.stdout), + concat!( + "test \"parse true ok\" ... ok\n", + "test \"parse false ok\" ... ok\n", + "test \"parse empty err one\" ... ok\n", + "test \"parse uppercase err one\" ... ok\n", + "test \"parse mixed case err one\" ... ok\n", + "test \"parse leading whitespace err one\" ... ok\n", + "test \"parse trailing whitespace err one\" ... ok\n", + "test \"parse numeric err one\" ... ok\n", + "test \"parse other text err one\" ... ok\n", + "9 test(s) passed\n", + ), + "string parse bool boundary test stdout drifted" + ); +} + +#[test] +fn string_parse_bool_result_hosted_runtime_parses_exact_text_when_clang_is_available() { + let fixture = write_fixture( + "runtime-smoke", + r#" +(module main) + +(fn main () -> i32 + (std.io.print_bool (std.result.unwrap_ok (std.string.parse_bool_result "true"))) + (std.io.print_bool (std.result.unwrap_ok (std.string.parse_bool_result "false"))) + (std.result.unwrap_err (std.string.parse_bool_result "TRUE"))) +"#, + ); + let binary = unique_path("string-parse-bool-result-bin"); + + let build = run_glagol([ + OsStr::new("build"), + fixture.as_os_str(), + OsStr::new("-o"), + binary.as_os_str(), + ]); + if !build.status.success() { + let stdout = String::from_utf8_lossy(&build.stdout); + let stderr = String::from_utf8_lossy(&build.stderr); + assert!( + stdout.is_empty(), + "failed string-parse-bool-result build wrote stdout:\n{}", + stdout + ); + assert!( + stderr.contains("ToolchainUnavailable"), + "string-parse-bool-result build failed unexpectedly\nstderr:\n{}", + stderr + ); + return; + } + + let run = Command::new(&binary) + .output() + .expect("run string-parse-bool-result binary"); + assert_eq!( + run.status.code(), + Some(1), + "string-parse-bool-result binary exit code drifted\nstdout:\n{}\nstderr:\n{}", + String::from_utf8_lossy(&run.stdout), + String::from_utf8_lossy(&run.stderr) + ); + assert_eq!( + String::from_utf8_lossy(&run.stdout), + "true\nfalse\n", + "string-parse-bool-result binary stdout drifted" + ); + assert!( + run.stderr.is_empty(), + "string-parse-bool-result binary wrote stderr:\n{}", + String::from_utf8_lossy(&run.stderr) + ); +} + +#[test] +fn string_parse_bool_result_formatter_and_lowering_are_visible() { + let fixture = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/string-parse-bool-result.slo"); + + let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]); + assert_success("format string-parse-bool-result fixture", &formatted); + let formatted_stdout = String::from_utf8_lossy(&formatted.stdout); + assert!( + formatted_stdout.contains("(std.string.parse_bool_result text)") + && formatted_stdout.contains("(std.result.unwrap_ok value)") + && formatted_stdout.contains("(std.result.is_err value)"), + "string-parse-bool-result formatter output omitted expected calls\nstdout:\n{}", + formatted_stdout + ); + + let surface = run_glagol([ + OsStr::new("--inspect-lowering=surface"), + fixture.as_os_str(), + ]); + assert_success( + "inspect string-parse-bool-result surface lowering", + &surface, + ); + assert_eq!( + String::from_utf8_lossy(&surface.stdout), + fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../tests/string-parse-bool-result.surface.lower") + ) + .expect("read string-parse-bool-result surface snapshot"), + "string-parse-bool-result surface lowering snapshot drifted" + ); + + let checked = run_glagol([ + OsStr::new("--inspect-lowering=checked"), + fixture.as_os_str(), + ]); + assert_success( + "inspect string-parse-bool-result checked lowering", + &checked, + ); + assert_eq!( + String::from_utf8_lossy(&checked.stdout), + fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../tests/string-parse-bool-result.checked.lower") + ) + .expect("read string-parse-bool-result checked snapshot"), + "string-parse-bool-result checked lowering snapshot drifted" + ); +} + +#[test] +fn string_parse_bool_result_rejections_are_explicit() { + for (name, source, expected) in [ + ( + "arity", + "(module main)\n\n(fn main () -> (result bool i32)\n (std.string.parse_bool_result))\n", + "wrong number of arguments", + ), + ( + "type", + "(module main)\n\n(fn main () -> (result bool i32)\n (std.string.parse_bool_result 1))\n", + "cannot call `std.string.parse_bool_result` with argument of wrong type", + ), + ( + "trap-parse-bool", + "(module main)\n\n(fn main () -> i32\n (std.string.parse_bool \"true\")\n 0)\n", + "standard library call `std.string.parse_bool` is not supported", + ), + ( + "generic-parse", + "(module main)\n\n(fn main () -> i32\n (std.string.parse_result \"true\")\n 0)\n", + "standard library call `std.string.parse_result` is not supported", + ), + ( + "deferred-code", + "(module main)\n\n(fn main () -> i32\n (std.string.parse_bool_code_result \"true\")\n 0)\n", + "standard library call `std.string.parse_bool_code_result` is not supported", + ), + ] { + let fixture = write_fixture(name, source); + let output = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + !output.status.success(), + "compiler unexpectedly accepted string-parse-bool rejection fixture `{}`\nstdout:\n{}\nstderr:\n{}", + name, + stdout, + stderr + ); + assert!( + stdout.is_empty(), + "rejected compile wrote stdout for `{}`:\n{}", + name, + stdout + ); + assert!( + stderr.contains(expected), + "string-parse-bool diagnostic drifted for `{}`\nstderr:\n{}", + name, + stderr + ); + } +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn write_fixture(name: &str, source: &str) -> PathBuf { + let mut path = env::temp_dir(); + path.push(format!( + "glagol-string-parse-bool-result-alpha-{}-{}-{}.slo", + name, + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + fs::write(&path, source).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err)); + path +} + +fn unique_path(name: &str) -> PathBuf { + let mut path = env::temp_dir(); + path.push(format!( + "glagol-{}-{}-{}", + name, + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + path +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); +} diff --git a/compiler/tests/string_parse_f64_result_alpha.rs b/compiler/tests/string_parse_f64_result_alpha.rs new file mode 100644 index 0000000..3b06acf --- /dev/null +++ b/compiler/tests/string_parse_f64_result_alpha.rs @@ -0,0 +1,312 @@ +use std::{ + env, + ffi::OsStr, + fs, + path::{Path, PathBuf}, + process::{Command, Output}, + sync::atomic::{AtomicUsize, Ordering}, +}; + +static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0); + +#[test] +fn string_parse_f64_result_fixture_lowers_and_runs_tests() { + let fixture = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/string-parse-f64-result.slo"); + + let llvm = run_glagol([fixture.as_os_str()]); + assert_success("compile string-parse-f64-result fixture", &llvm); + let stdout = String::from_utf8_lossy(&llvm.stdout); + assert!( + stdout.contains("declare i32 @__glagol_string_parse_f64_result(ptr, ptr)") + && stdout.contains("define { i1, double, i32 } @parse_f64(ptr %text)") + && stdout.contains("alloca double") + && stdout.contains("store double 0.0, ptr %") + && stdout.contains("call i32 @__glagol_string_parse_f64_result(ptr %text, ptr %") + && stdout.contains("load double, ptr %") + && stdout.contains("insertvalue { i1, double, i32 }") + && stdout.contains("extractvalue { i1, double, i32 }") + && !stdout.contains("@std.string.parse_f64_result"), + "string-parse-f64-result LLVM shape drifted\nstdout:\n{}", + stdout + ); + + let tests = run_glagol([OsStr::new("test"), fixture.as_os_str()]); + assert_success("run string-parse-f64-result fixture tests", &tests); + assert_eq!( + String::from_utf8_lossy(&tests.stdout), + concat!( + "test \"parse f64 decimal ok\" ... ok\n", + "test \"parse f64 negative decimal ok\" ... ok\n", + "test \"parse f64 text err\" ... ok\n", + "test \"parse f64 nan err\" ... ok\n", + "4 test(s) passed\n", + ), + "string-parse-f64-result test runner stdout drifted" + ); +} + +#[test] +fn string_parse_f64_result_test_runner_covers_decimal_boundaries() { + let source = r#" +(module main) + +(test "parse f64 decimal ok" + (= (std.result.unwrap_ok (std.string.parse_f64_result "3.5")) 3.5)) + +(test "parse f64 negative decimal ok" + (= (std.result.unwrap_ok (std.string.parse_f64_result "-3.5")) (- 0.0 3.5))) + +(test "parse f64 signed plus err one" + (= (std.result.unwrap_err (std.string.parse_f64_result "+3.5")) 1)) + +(test "parse f64 leading dot err one" + (= (std.result.unwrap_err (std.string.parse_f64_result ".5")) 1)) + +(test "parse f64 trailing dot err one" + (= (std.result.unwrap_err (std.string.parse_f64_result "5.")) 1)) + +(test "parse f64 exponent err one" + (= (std.result.unwrap_err (std.string.parse_f64_result "1e2")) 1)) + +(test "parse f64 nan err one" + (= (std.result.unwrap_err (std.string.parse_f64_result "nan")) 1)) + +(test "parse f64 infinity err one" + (= (std.result.unwrap_err (std.string.parse_f64_result "inf")) 1)) + +(test "parse f64 leading whitespace err one" + (= (std.result.unwrap_err (std.string.parse_f64_result " 1.0")) 1)) + +(test "parse f64 underscore err one" + (= (std.result.unwrap_err (std.string.parse_f64_result "1_0")) 1)) + +(test "parse f64 hex err one" + (= (std.result.unwrap_err (std.string.parse_f64_result "0x1.0p0")) 1)) +"#; + let fixture = write_fixture("boundaries", source); + let output = run_glagol([OsStr::new("test"), fixture.as_os_str()]); + assert_success("run string parse f64 boundary tests", &output); + assert_eq!( + String::from_utf8_lossy(&output.stdout), + concat!( + "test \"parse f64 decimal ok\" ... ok\n", + "test \"parse f64 negative decimal ok\" ... ok\n", + "test \"parse f64 signed plus err one\" ... ok\n", + "test \"parse f64 leading dot err one\" ... ok\n", + "test \"parse f64 trailing dot err one\" ... ok\n", + "test \"parse f64 exponent err one\" ... ok\n", + "test \"parse f64 nan err one\" ... ok\n", + "test \"parse f64 infinity err one\" ... ok\n", + "test \"parse f64 leading whitespace err one\" ... ok\n", + "test \"parse f64 underscore err one\" ... ok\n", + "test \"parse f64 hex err one\" ... ok\n", + "11 test(s) passed\n", + ), + "string parse f64 boundary test stdout drifted" + ); +} + +#[test] +fn string_parse_f64_result_hosted_runtime_parses_decimal_when_clang_is_available() { + let fixture = write_fixture( + "runtime-smoke", + r#" +(module main) + +(fn main () -> i32 + (std.io.print_string (std.num.f64_to_string (std.result.unwrap_ok (std.string.parse_f64_result "-0.5")))) + (std.io.print_string (std.num.f64_to_string (std.result.unwrap_ok (std.string.parse_f64_result "12.5")))) + (std.result.unwrap_err (std.string.parse_f64_result "inf"))) +"#, + ); + let binary = unique_path("string-parse-f64-result-bin"); + + let build = run_glagol([ + OsStr::new("build"), + fixture.as_os_str(), + OsStr::new("-o"), + binary.as_os_str(), + ]); + if !build.status.success() { + let stdout = String::from_utf8_lossy(&build.stdout); + let stderr = String::from_utf8_lossy(&build.stderr); + assert!( + stdout.is_empty(), + "failed string-parse-f64-result build wrote stdout:\n{}", + stdout + ); + assert!( + stderr.contains("ToolchainUnavailable"), + "string-parse-f64-result build failed unexpectedly\nstderr:\n{}", + stderr + ); + return; + } + + let run = Command::new(&binary) + .output() + .expect("run string-parse-f64-result binary"); + assert_eq!( + run.status.code(), + Some(1), + "string-parse-f64-result binary exit code drifted\nstdout:\n{}\nstderr:\n{}", + String::from_utf8_lossy(&run.stdout), + String::from_utf8_lossy(&run.stderr) + ); + assert_eq!( + String::from_utf8_lossy(&run.stdout), + "-0.5\n12.5\n", + "string-parse-f64-result binary stdout drifted" + ); + assert!( + run.stderr.is_empty(), + "string-parse-f64-result binary wrote stderr:\n{}", + String::from_utf8_lossy(&run.stderr) + ); +} + +#[test] +fn string_parse_f64_result_formatter_and_lowering_are_visible() { + let fixture = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/string-parse-f64-result.slo"); + + let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]); + assert_success("format string-parse-f64-result fixture", &formatted); + let formatted_stdout = String::from_utf8_lossy(&formatted.stdout); + assert!( + formatted_stdout.contains("(std.string.parse_f64_result text)") + && formatted_stdout.contains("(std.result.unwrap_ok value)") + && formatted_stdout.contains("(std.result.is_err value)"), + "string-parse-f64-result formatter output omitted expected calls\nstdout:\n{}", + formatted_stdout + ); + + let surface = run_glagol([ + OsStr::new("--inspect-lowering=surface"), + fixture.as_os_str(), + ]); + assert_success("inspect string-parse-f64-result surface lowering", &surface); + assert_eq!( + String::from_utf8_lossy(&surface.stdout), + fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../tests/string-parse-f64-result.surface.lower") + ) + .expect("read string-parse-f64-result surface snapshot"), + "string-parse-f64-result surface lowering snapshot drifted" + ); + + let checked = run_glagol([ + OsStr::new("--inspect-lowering=checked"), + fixture.as_os_str(), + ]); + assert_success("inspect string-parse-f64-result checked lowering", &checked); + assert_eq!( + String::from_utf8_lossy(&checked.stdout), + fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../tests/string-parse-f64-result.checked.lower") + ) + .expect("read string-parse-f64-result checked snapshot"), + "string-parse-f64-result checked lowering snapshot drifted" + ); +} + +#[test] +fn string_parse_f64_result_rejections_are_explicit() { + for (name, source, expected) in [ + ( + "arity", + "(module main)\n\n(fn main () -> (result f64 i32)\n (std.string.parse_f64_result))\n", + "wrong number of arguments", + ), + ( + "type", + "(module main)\n\n(fn main () -> (result f64 i32)\n (std.string.parse_f64_result 1))\n", + "cannot call `std.string.parse_f64_result` with argument of wrong type", + ), + ( + "trap-parse-f64", + "(module main)\n\n(fn main () -> i32\n (std.string.parse_f64 \"1.0\")\n 0)\n", + "standard library call `std.string.parse_f64` is not supported", + ), + ( + "generic-parse", + "(module main)\n\n(fn main () -> i32\n (std.string.parse_result \"1.0\")\n 0)\n", + "standard library call `std.string.parse_result` is not supported", + ), + ] { + let fixture = write_fixture(name, source); + let output = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + !output.status.success(), + "compiler unexpectedly accepted string-parse-f64 rejection fixture `{}`\nstdout:\n{}\nstderr:\n{}", + name, + stdout, + stderr + ); + assert!( + stdout.is_empty(), + "rejected compile wrote stdout for `{}`:\n{}", + name, + stdout + ); + assert!( + stderr.contains(expected), + "string-parse-f64 diagnostic drifted for `{}`\nstderr:\n{}", + name, + stderr + ); + } +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn write_fixture(name: &str, source: &str) -> PathBuf { + let mut path = env::temp_dir(); + path.push(format!( + "glagol-string-parse-f64-result-alpha-{}-{}-{}.slo", + name, + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + fs::write(&path, source).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err)); + path +} + +fn unique_path(name: &str) -> PathBuf { + let mut path = env::temp_dir(); + path.push(format!( + "glagol-{}-{}-{}", + name, + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + path +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); +} diff --git a/compiler/tests/string_parse_i64_result_alpha.rs b/compiler/tests/string_parse_i64_result_alpha.rs new file mode 100644 index 0000000..ed67eea --- /dev/null +++ b/compiler/tests/string_parse_i64_result_alpha.rs @@ -0,0 +1,308 @@ +use std::{ + env, + ffi::OsStr, + fs, + path::{Path, PathBuf}, + process::{Command, Output}, + sync::atomic::{AtomicUsize, Ordering}, +}; + +static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0); + +#[test] +fn string_parse_i64_result_fixture_lowers_and_runs_tests() { + let fixture = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/string-parse-i64-result.slo"); + + let llvm = run_glagol([fixture.as_os_str()]); + assert_success("compile string-parse-i64-result fixture", &llvm); + let stdout = String::from_utf8_lossy(&llvm.stdout); + assert!( + stdout.contains("declare i32 @__glagol_string_parse_i64_result(ptr, ptr)") + && stdout.contains("define { i1, i64, i32 } @parse_i64(ptr %text)") + && stdout.contains("alloca i64") + && stdout.contains("store i64 0, ptr %") + && stdout.contains("call i32 @__glagol_string_parse_i64_result(ptr %text, ptr %") + && stdout.contains("load i64, ptr %") + && stdout.contains("insertvalue { i1, i64, i32 }") + && stdout.contains("extractvalue { i1, i64, i32 }") + && !stdout.contains("@std.string.parse_i64_result"), + "string-parse-i64-result LLVM shape drifted\nstdout:\n{}", + stdout + ); + + let tests = run_glagol([OsStr::new("test"), fixture.as_os_str()]); + assert_success("run string-parse-i64-result fixture tests", &tests); + assert_eq!( + String::from_utf8_lossy(&tests.stdout), + concat!( + "test \"parse i64 zero ok\" ... ok\n", + "test \"parse i64 negative ok\" ... ok\n", + "test \"parse i64 low ok\" ... ok\n", + "test \"parse i64 high ok\" ... ok\n", + "test \"parse i64 empty err\" ... ok\n", + "test \"parse i64 plus err\" ... ok\n", + "test \"parse i64 above range err\" ... ok\n", + "7 test(s) passed\n", + ), + "string-parse-i64-result test runner stdout drifted" + ); +} + +#[test] +fn string_parse_i64_result_test_runner_covers_boundaries() { + let source = r#" +(module main) + +(test "parse ok min i64" + (= (std.result.unwrap_ok (std.string.parse_i64_result "-9223372036854775808")) -9223372036854775808i64)) + +(test "parse ok max i64" + (= (std.result.unwrap_ok (std.string.parse_i64_result "9223372036854775807")) 9223372036854775807i64)) + +(test "parse lone minus err one" + (= (std.result.unwrap_err (std.string.parse_i64_result "-")) 1)) + +(test "parse leading whitespace err one" + (= (std.result.unwrap_err (std.string.parse_i64_result " 1")) 1)) + +(test "parse trailing byte err one" + (= (std.result.unwrap_err (std.string.parse_i64_result "42x")) 1)) + +(test "parse underscore err one" + (= (std.result.unwrap_err (std.string.parse_i64_result "1_0")) 1)) + +(test "parse prefix err one" + (= (std.result.unwrap_err (std.string.parse_i64_result "0x10")) 1)) + +(test "parse underflow err one" + (= (std.result.unwrap_err (std.string.parse_i64_result "-9223372036854775809")) 1)) +"#; + let fixture = write_fixture("boundaries", source); + let output = run_glagol([OsStr::new("test"), fixture.as_os_str()]); + assert_success("run string parse i64 boundary tests", &output); + assert_eq!( + String::from_utf8_lossy(&output.stdout), + concat!( + "test \"parse ok min i64\" ... ok\n", + "test \"parse ok max i64\" ... ok\n", + "test \"parse lone minus err one\" ... ok\n", + "test \"parse leading whitespace err one\" ... ok\n", + "test \"parse trailing byte err one\" ... ok\n", + "test \"parse underscore err one\" ... ok\n", + "test \"parse prefix err one\" ... ok\n", + "test \"parse underflow err one\" ... ok\n", + "8 test(s) passed\n", + ), + "string parse i64 boundary test stdout drifted" + ); +} + +#[test] +fn string_parse_i64_result_hosted_runtime_parses_bounds_when_clang_is_available() { + let fixture = write_fixture( + "runtime-smoke", + r#" +(module main) + +(fn main () -> i32 + (std.io.print_i64 (std.result.unwrap_ok (std.string.parse_i64_result "-9223372036854775808"))) + (std.io.print_i64 (std.result.unwrap_ok (std.string.parse_i64_result "9223372036854775807"))) + (std.result.unwrap_err (std.string.parse_i64_result "+1"))) +"#, + ); + let binary = unique_path("string-parse-i64-result-bin"); + + let build = run_glagol([ + OsStr::new("build"), + fixture.as_os_str(), + OsStr::new("-o"), + binary.as_os_str(), + ]); + if !build.status.success() { + let stdout = String::from_utf8_lossy(&build.stdout); + let stderr = String::from_utf8_lossy(&build.stderr); + assert!( + stdout.is_empty(), + "failed string-parse-i64-result build wrote stdout:\n{}", + stdout + ); + assert!( + stderr.contains("ToolchainUnavailable"), + "string-parse-i64-result build failed unexpectedly\nstderr:\n{}", + stderr + ); + return; + } + + let run = Command::new(&binary) + .output() + .expect("run string-parse-i64-result binary"); + assert_eq!( + run.status.code(), + Some(1), + "string-parse-i64-result binary exit code drifted\nstdout:\n{}\nstderr:\n{}", + String::from_utf8_lossy(&run.stdout), + String::from_utf8_lossy(&run.stderr) + ); + assert_eq!( + String::from_utf8_lossy(&run.stdout), + "-9223372036854775808\n9223372036854775807\n", + "string-parse-i64-result binary stdout drifted" + ); + assert!( + run.stderr.is_empty(), + "string-parse-i64-result binary wrote stderr:\n{}", + String::from_utf8_lossy(&run.stderr) + ); +} + +#[test] +fn string_parse_i64_result_formatter_and_lowering_are_visible() { + let fixture = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/string-parse-i64-result.slo"); + + let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]); + assert_success("format string-parse-i64-result fixture", &formatted); + let formatted_stdout = String::from_utf8_lossy(&formatted.stdout); + assert!( + formatted_stdout.contains("(std.string.parse_i64_result text)") + && formatted_stdout.contains("(std.result.unwrap_ok (parse_i64 text))") + && formatted_stdout.contains("(std.result.is_err value)"), + "string-parse-i64-result formatter output omitted expected calls\nstdout:\n{}", + formatted_stdout + ); + + let surface = run_glagol([ + OsStr::new("--inspect-lowering=surface"), + fixture.as_os_str(), + ]); + assert_success("inspect string-parse-i64-result surface lowering", &surface); + assert_eq!( + String::from_utf8_lossy(&surface.stdout), + fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../tests/string-parse-i64-result.surface.lower") + ) + .expect("read string-parse-i64-result surface snapshot"), + "string-parse-i64-result surface lowering snapshot drifted" + ); + + let checked = run_glagol([ + OsStr::new("--inspect-lowering=checked"), + fixture.as_os_str(), + ]); + assert_success("inspect string-parse-i64-result checked lowering", &checked); + assert_eq!( + String::from_utf8_lossy(&checked.stdout), + fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../tests/string-parse-i64-result.checked.lower") + ) + .expect("read string-parse-i64-result checked snapshot"), + "string-parse-i64-result checked lowering snapshot drifted" + ); +} + +#[test] +fn string_parse_i64_result_rejections_are_explicit() { + for (name, source, expected) in [ + ( + "arity", + "(module main)\n\n(fn main () -> (result i64 i32)\n (std.string.parse_i64_result))\n", + "wrong number of arguments", + ), + ( + "type", + "(module main)\n\n(fn main () -> (result i64 i32)\n (std.string.parse_i64_result 1))\n", + "cannot call `std.string.parse_i64_result` with argument of wrong type", + ), + ( + "trap-parse-i64", + "(module main)\n\n(fn main () -> i32\n (std.string.parse_i64 \"42\")\n 0)\n", + "standard library call `std.string.parse_i64` is not supported", + ), + ( + "generic-parse", + "(module main)\n\n(fn main () -> i32\n (std.string.parse_result \"42\")\n 0)\n", + "standard library call `std.string.parse_result` is not supported", + ), + ( + "f64-parse", + "(module main)\n\n(fn main () -> i32\n (std.string.parse_f64 \"1.0\")\n 0)\n", + "standard library call `std.string.parse_f64` is not supported", + ), + ] { + let fixture = write_fixture(name, source); + let output = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + !output.status.success(), + "compiler unexpectedly accepted string-parse-i64 rejection fixture `{}`\nstdout:\n{}\nstderr:\n{}", + name, + stdout, + stderr + ); + assert!( + stdout.is_empty(), + "rejected compile wrote stdout for `{}`:\n{}", + name, + stdout + ); + assert!( + stderr.contains(expected), + "string-parse-i64 diagnostic drifted for `{}`\nstderr:\n{}", + name, + stderr + ); + } +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn write_fixture(name: &str, source: &str) -> PathBuf { + let mut path = env::temp_dir(); + path.push(format!( + "glagol-string-parse-i64-result-alpha-{}-{}-{}.slo", + name, + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + fs::write(&path, source).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err)); + path +} + +fn unique_path(name: &str) -> PathBuf { + let mut path = env::temp_dir(); + path.push(format!( + "glagol-{}-{}-{}", + name, + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + path +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); +} diff --git a/compiler/tests/string_parse_u32_result_alpha.rs b/compiler/tests/string_parse_u32_result_alpha.rs new file mode 100644 index 0000000..d603b31 --- /dev/null +++ b/compiler/tests/string_parse_u32_result_alpha.rs @@ -0,0 +1,286 @@ +use std::{ + env, + ffi::OsStr, + fs, + path::{Path, PathBuf}, + process::{Command, Output}, + sync::atomic::{AtomicUsize, Ordering}, +}; + +static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0); + +#[test] +fn string_parse_u32_result_fixture_lowers_and_runs_tests() { + let fixture = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/string-parse-u32-result.slo"); + + let llvm = run_glagol([fixture.as_os_str()]); + assert_success("compile string-parse-u32-result fixture", &llvm); + let stdout = String::from_utf8_lossy(&llvm.stdout); + assert!( + stdout.contains("declare i64 @__glagol_string_parse_u32_result(ptr)") + && stdout.contains("define { i1, i32 } @parse_u32(ptr %text)") + && stdout.contains("call i64 @__glagol_string_parse_u32_result(ptr %text)") + && stdout.contains("lshr i64") + && stdout.contains("insertvalue { i1, i32 }") + && stdout.contains("extractvalue { i1, i32 }") + && !stdout.contains("@std.string.parse_u32_result"), + "string-parse-u32-result LLVM shape drifted\nstdout:\n{}", + stdout + ); + + let tests = run_glagol([OsStr::new("test"), fixture.as_os_str()]); + assert_success("run string-parse-u32-result fixture tests", &tests); + assert_eq!( + String::from_utf8_lossy(&tests.stdout), + concat!( + "test \"parse u32 zero ok\" ... ok\n", + "test \"parse u32 high ok\" ... ok\n", + "test \"parse u32 empty err\" ... ok\n", + "test \"parse u32 plus err\" ... ok\n", + "test \"parse u32 negative err\" ... ok\n", + "test \"parse u32 above range err\" ... ok\n", + "6 test(s) passed\n", + ), + "string-parse-u32-result test runner stdout drifted" + ); +} + +#[test] +fn string_parse_u32_result_test_runner_covers_boundaries() { + let source = r#" +(module main) + +(test "parse ok max u32" + (= (std.result.unwrap_ok (std.string.parse_u32_result "4294967295")) 4294967295u32)) + +(test "parse lone minus err one" + (= (std.result.unwrap_err (std.string.parse_u32_result "-")) 1)) + +(test "parse leading whitespace err one" + (= (std.result.unwrap_err (std.string.parse_u32_result " 1")) 1)) + +(test "parse trailing byte err one" + (= (std.result.unwrap_err (std.string.parse_u32_result "42x")) 1)) + +(test "parse underscore err one" + (= (std.result.unwrap_err (std.string.parse_u32_result "1_0")) 1)) + +(test "parse prefix err one" + (= (std.result.unwrap_err (std.string.parse_u32_result "0x10")) 1)) +"#; + let fixture = write_fixture("boundaries", source); + let output = run_glagol([OsStr::new("test"), fixture.as_os_str()]); + assert_success("run string parse u32 boundary tests", &output); + assert_eq!( + String::from_utf8_lossy(&output.stdout), + concat!( + "test \"parse ok max u32\" ... ok\n", + "test \"parse lone minus err one\" ... ok\n", + "test \"parse leading whitespace err one\" ... ok\n", + "test \"parse trailing byte err one\" ... ok\n", + "test \"parse underscore err one\" ... ok\n", + "test \"parse prefix err one\" ... ok\n", + "6 test(s) passed\n", + ), + "string parse u32 boundary test stdout drifted" + ); +} + +#[test] +fn string_parse_u32_result_hosted_runtime_parses_bounds_when_toolchain_is_available() { + let fixture = write_fixture( + "runtime-smoke", + r#" +(module main) + +(fn main () -> i32 + (std.io.print_u32 (std.result.unwrap_ok (std.string.parse_u32_result "4294967295"))) + (std.result.unwrap_err (std.string.parse_u32_result "+1"))) +"#, + ); + let binary = unique_path("string-parse-u32-result-bin"); + + let build = run_glagol([ + OsStr::new("build"), + fixture.as_os_str(), + OsStr::new("-o"), + binary.as_os_str(), + ]); + if !build.status.success() { + let stdout = String::from_utf8_lossy(&build.stdout); + let stderr = String::from_utf8_lossy(&build.stderr); + assert!( + stdout.is_empty(), + "failed string-parse-u32-result build wrote stdout:\n{}", + stdout + ); + assert!( + stderr.contains("ToolchainUnavailable"), + "string-parse-u32-result build failed unexpectedly\nstderr:\n{}", + stderr + ); + return; + } + + let run = Command::new(&binary) + .output() + .expect("run string-parse-u32-result binary"); + assert_eq!( + run.status.code(), + Some(1), + "string-parse-u32-result binary exit code drifted\nstdout:\n{}\nstderr:\n{}", + String::from_utf8_lossy(&run.stdout), + String::from_utf8_lossy(&run.stderr) + ); + assert_eq!( + String::from_utf8_lossy(&run.stdout), + "4294967295\n", + "string-parse-u32-result binary stdout drifted" + ); + assert!( + run.stderr.is_empty(), + "string-parse-u32-result binary wrote stderr:\n{}", + String::from_utf8_lossy(&run.stderr) + ); +} + +#[test] +fn string_parse_u32_result_formatter_and_lowering_are_visible() { + let fixture = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/string-parse-u32-result.slo"); + + let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]); + assert_success("format string-parse-u32-result fixture", &formatted); + let formatted_stdout = String::from_utf8_lossy(&formatted.stdout); + assert!( + formatted_stdout.contains("(std.string.parse_u32_result text)") + && formatted_stdout + .contains("(std.num.u32_to_string (std.result.unwrap_ok (parse_u32 text)))"), + "string-parse-u32-result formatter output omitted expected calls\nstdout:\n{}", + formatted_stdout + ); + + let surface = run_glagol([ + OsStr::new("--inspect-lowering=surface"), + fixture.as_os_str(), + ]); + assert_success("inspect string-parse-u32-result surface lowering", &surface); + assert_eq!( + String::from_utf8_lossy(&surface.stdout), + fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../tests/string-parse-u32-result.surface.lower"), + ) + .expect("read string-parse-u32-result surface snapshot"), + "string-parse-u32-result surface lowering snapshot drifted" + ); + + let checked = run_glagol([ + OsStr::new("--inspect-lowering=checked"), + fixture.as_os_str(), + ]); + assert_success("inspect string-parse-u32-result checked lowering", &checked); + assert_eq!( + String::from_utf8_lossy(&checked.stdout), + fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../tests/string-parse-u32-result.checked.lower"), + ) + .expect("read string-parse-u32-result checked snapshot"), + "string-parse-u32-result checked lowering snapshot drifted" + ); +} + +#[test] +fn string_parse_u32_result_rejections_are_explicit() { + for (name, source, expected) in [ + ( + "arity", + "(module main)\n\n(fn main () -> (result u32 i32)\n (std.string.parse_u32_result))\n", + "wrong number of arguments", + ), + ( + "type", + "(module main)\n\n(fn main () -> (result u32 i32)\n (std.string.parse_u32_result 1u32))\n", + "cannot call `std.string.parse_u32_result` with argument of wrong type", + ), + ( + "trap-parse-u32", + "(module main)\n\n(fn main () -> i32\n (std.string.parse_u32 \"42\")\n 0)\n", + "standard library call `std.string.parse_u32` is not supported", + ), + ] { + let fixture = write_fixture(name, source); + let output = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + !output.status.success(), + "compiler unexpectedly accepted string-parse-u32 rejection fixture `{}`\nstdout:\n{}\nstderr:\n{}", + name, + stdout, + stderr + ); + assert!( + stdout.is_empty(), + "rejected compile wrote stdout for `{}`:\n{}", + name, + stdout + ); + assert!( + stderr.contains(expected), + "string-parse-u32 diagnostic drifted for `{}`\nstderr:\n{}", + name, + stderr + ); + } +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn write_fixture(name: &str, source: &str) -> PathBuf { + let mut path = env::temp_dir(); + path.push(format!( + "glagol-string-parse-u32-result-alpha-{}-{}-{}.slo", + name, + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + fs::write(&path, source).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err)); + path +} + +fn unique_path(name: &str) -> PathBuf { + let mut path = env::temp_dir(); + path.push(format!( + "glagol-{}-{}-{}", + name, + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + path +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); +} diff --git a/compiler/tests/string_parse_u64_result_alpha.rs b/compiler/tests/string_parse_u64_result_alpha.rs new file mode 100644 index 0000000..a1025ba --- /dev/null +++ b/compiler/tests/string_parse_u64_result_alpha.rs @@ -0,0 +1,287 @@ +use std::{ + env, + ffi::OsStr, + fs, + path::{Path, PathBuf}, + process::{Command, Output}, + sync::atomic::{AtomicUsize, Ordering}, +}; + +static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0); + +#[test] +fn string_parse_u64_result_fixture_lowers_and_runs_tests() { + let fixture = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/string-parse-u64-result.slo"); + + let llvm = run_glagol([fixture.as_os_str()]); + assert_success("compile string-parse-u64-result fixture", &llvm); + let stdout = String::from_utf8_lossy(&llvm.stdout); + assert!( + stdout.contains("declare i32 @__glagol_string_parse_u64_result(ptr, ptr)") + && stdout.contains("define { i1, i64, i32 } @parse_u64(ptr %text)") + && stdout.contains("alloca i64") + && stdout.contains("call i32 @__glagol_string_parse_u64_result(ptr %text, ptr %") + && stdout.contains("load i64, ptr %") + && stdout.contains("insertvalue { i1, i64, i32 }") + && stdout.contains("extractvalue { i1, i64, i32 }") + && !stdout.contains("@std.string.parse_u64_result"), + "string-parse-u64-result LLVM shape drifted\nstdout:\n{}", + stdout + ); + + let tests = run_glagol([OsStr::new("test"), fixture.as_os_str()]); + assert_success("run string-parse-u64-result fixture tests", &tests); + assert_eq!( + String::from_utf8_lossy(&tests.stdout), + concat!( + "test \"parse u64 zero ok\" ... ok\n", + "test \"parse u64 high ok\" ... ok\n", + "test \"parse u64 empty err\" ... ok\n", + "test \"parse u64 plus err\" ... ok\n", + "test \"parse u64 negative err\" ... ok\n", + "test \"parse u64 above range err\" ... ok\n", + "6 test(s) passed\n", + ), + "string-parse-u64-result test runner stdout drifted" + ); +} + +#[test] +fn string_parse_u64_result_test_runner_covers_boundaries() { + let source = r#" +(module main) + +(test "parse ok max u64" + (= (std.result.unwrap_ok (std.string.parse_u64_result "18446744073709551615")) 18446744073709551615u64)) + +(test "parse lone minus err one" + (= (std.result.unwrap_err (std.string.parse_u64_result "-")) 1)) + +(test "parse leading whitespace err one" + (= (std.result.unwrap_err (std.string.parse_u64_result " 1")) 1)) + +(test "parse trailing byte err one" + (= (std.result.unwrap_err (std.string.parse_u64_result "42x")) 1)) + +(test "parse underscore err one" + (= (std.result.unwrap_err (std.string.parse_u64_result "1_0")) 1)) + +(test "parse prefix err one" + (= (std.result.unwrap_err (std.string.parse_u64_result "0x10")) 1)) +"#; + let fixture = write_fixture("boundaries", source); + let output = run_glagol([OsStr::new("test"), fixture.as_os_str()]); + assert_success("run string parse u64 boundary tests", &output); + assert_eq!( + String::from_utf8_lossy(&output.stdout), + concat!( + "test \"parse ok max u64\" ... ok\n", + "test \"parse lone minus err one\" ... ok\n", + "test \"parse leading whitespace err one\" ... ok\n", + "test \"parse trailing byte err one\" ... ok\n", + "test \"parse underscore err one\" ... ok\n", + "test \"parse prefix err one\" ... ok\n", + "6 test(s) passed\n", + ), + "string parse u64 boundary test stdout drifted" + ); +} + +#[test] +fn string_parse_u64_result_hosted_runtime_parses_bounds_when_toolchain_is_available() { + let fixture = write_fixture( + "runtime-smoke", + r#" +(module main) + +(fn main () -> i32 + (std.io.print_u64 (std.result.unwrap_ok (std.string.parse_u64_result "18446744073709551615"))) + (std.result.unwrap_err (std.string.parse_u64_result "+1"))) +"#, + ); + let binary = unique_path("string-parse-u64-result-bin"); + + let build = run_glagol([ + OsStr::new("build"), + fixture.as_os_str(), + OsStr::new("-o"), + binary.as_os_str(), + ]); + if !build.status.success() { + let stdout = String::from_utf8_lossy(&build.stdout); + let stderr = String::from_utf8_lossy(&build.stderr); + assert!( + stdout.is_empty(), + "failed string-parse-u64-result build wrote stdout:\n{}", + stdout + ); + assert!( + stderr.contains("ToolchainUnavailable"), + "string-parse-u64-result build failed unexpectedly\nstderr:\n{}", + stderr + ); + return; + } + + let run = Command::new(&binary) + .output() + .expect("run string-parse-u64-result binary"); + assert_eq!( + run.status.code(), + Some(1), + "string-parse-u64-result binary exit code drifted\nstdout:\n{}\nstderr:\n{}", + String::from_utf8_lossy(&run.stdout), + String::from_utf8_lossy(&run.stderr) + ); + assert_eq!( + String::from_utf8_lossy(&run.stdout), + "18446744073709551615\n", + "string-parse-u64-result binary stdout drifted" + ); + assert!( + run.stderr.is_empty(), + "string-parse-u64-result binary wrote stderr:\n{}", + String::from_utf8_lossy(&run.stderr) + ); +} + +#[test] +fn string_parse_u64_result_formatter_and_lowering_are_visible() { + let fixture = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/string-parse-u64-result.slo"); + + let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]); + assert_success("format string-parse-u64-result fixture", &formatted); + let formatted_stdout = String::from_utf8_lossy(&formatted.stdout); + assert!( + formatted_stdout.contains("(std.string.parse_u64_result text)") + && formatted_stdout + .contains("(std.num.u64_to_string (std.result.unwrap_ok (parse_u64 text)))"), + "string-parse-u64-result formatter output omitted expected calls\nstdout:\n{}", + formatted_stdout + ); + + let surface = run_glagol([ + OsStr::new("--inspect-lowering=surface"), + fixture.as_os_str(), + ]); + assert_success("inspect string-parse-u64-result surface lowering", &surface); + assert_eq!( + String::from_utf8_lossy(&surface.stdout), + fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../tests/string-parse-u64-result.surface.lower"), + ) + .expect("read string-parse-u64-result surface snapshot"), + "string-parse-u64-result surface lowering snapshot drifted" + ); + + let checked = run_glagol([ + OsStr::new("--inspect-lowering=checked"), + fixture.as_os_str(), + ]); + assert_success("inspect string-parse-u64-result checked lowering", &checked); + assert_eq!( + String::from_utf8_lossy(&checked.stdout), + fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../tests/string-parse-u64-result.checked.lower"), + ) + .expect("read string-parse-u64-result checked snapshot"), + "string-parse-u64-result checked lowering snapshot drifted" + ); +} + +#[test] +fn string_parse_u64_result_rejections_are_explicit() { + for (name, source, expected) in [ + ( + "arity", + "(module main)\n\n(fn main () -> (result u64 i32)\n (std.string.parse_u64_result))\n", + "wrong number of arguments", + ), + ( + "type", + "(module main)\n\n(fn main () -> (result u64 i32)\n (std.string.parse_u64_result 1u64))\n", + "cannot call `std.string.parse_u64_result` with argument of wrong type", + ), + ( + "trap-parse-u64", + "(module main)\n\n(fn main () -> i32\n (std.string.parse_u64 \"42\")\n 0)\n", + "standard library call `std.string.parse_u64` is not supported", + ), + ] { + let fixture = write_fixture(name, source); + let output = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + !output.status.success(), + "compiler unexpectedly accepted string-parse-u64 rejection fixture `{}`\nstdout:\n{}\nstderr:\n{}", + name, + stdout, + stderr + ); + assert!( + stdout.is_empty(), + "rejected compile wrote stdout for `{}`:\n{}", + name, + stdout + ); + assert!( + stderr.contains(expected), + "string-parse-u64 diagnostic drifted for `{}`\nstderr:\n{}", + name, + stderr + ); + } +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn write_fixture(name: &str, source: &str) -> PathBuf { + let mut path = env::temp_dir(); + path.push(format!( + "glagol-string-parse-u64-result-alpha-{}-{}-{}.slo", + name, + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + fs::write(&path, source).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err)); + path +} + +fn unique_path(name: &str) -> PathBuf { + let mut path = env::temp_dir(); + path.push(format!( + "glagol-{}-{}-{}", + name, + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + path +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); +} diff --git a/compiler/tests/string_runtime.rs b/compiler/tests/string_runtime.rs new file mode 100644 index 0000000..df88a1f --- /dev/null +++ b/compiler/tests/string_runtime.rs @@ -0,0 +1,1320 @@ +use std::{ + env, + ffi::OsStr, + fs, + path::{Path, PathBuf}, + process::{Command, Output}, + sync::atomic::{AtomicUsize, Ordering}, +}; + +static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0); + +#[test] +fn print_string_literal_emits_static_global_and_call() { + let fixture = write_fixture( + "print-string-literal", + r#" +(module main) + +(fn main () -> i32 + (print_string "hello") + 0) +"#, + ); + let output = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "compiler rejected print_string literal fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("declare void @print_string(ptr)") + && stdout.contains( + "@.str.0 = private unnamed_addr constant [6 x i8] c\"hello\\00\", align 1" + ) + && stdout.contains("call void @print_string(ptr @.str.0)"), + "LLVM output did not contain expected string runtime shape\nstdout:\n{}", + stdout + ); + assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr); +} + +#[test] +fn print_string_literal_escapes_llvm_global_bytes() { + let fixture = write_fixture( + "print-string-escapes", + r#" +(module main) + +(fn main () -> i32 + (print_string "line\nquote\"slash\\tab\t") + 0) +"#, + ); + let output = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "compiler rejected escaped print_string literal fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains( + "@.str.0 = private unnamed_addr constant [22 x i8] c\"line\\0Aquote\\22slash\\5Ctab\\09\\00\", align 1" + ), + "LLVM string global did not preserve expected escapes\nstdout:\n{}", + stdout + ); + assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr); +} + +#[test] +fn promoted_string_print_fixture_emits_static_globals_and_calls() { + let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/string-print.slo"); + let output = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "compiler rejected promoted string-print fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("@.str.0 = private unnamed_addr constant [6 x i8] c\"hello\\00\", align 1") + && stdout.contains( + "@.str.1 = private unnamed_addr constant [22 x i8] c\"line\\0Aquote\\22slash\\5Ctab\\09\\00\", align 1" + ) + && stdout.contains("call void @print_string(ptr @.str.0)") + && stdout.contains("call void @print_string(ptr @.str.1)"), + "promoted fixture LLVM output did not contain expected string globals and calls\nstdout:\n{}", + stdout + ); + assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr); +} + +#[test] +fn print_string_requires_exactly_one_string_argument() { + let cases = [ + ( + "print-string-wrong-type", + r#" +(module main) + +(fn main () -> i32 + (print_string 1) + 0) +"#, + "TypeMismatch", + ), + ( + "print-string-wrong-arity", + r#" +(module main) + +(fn main () -> i32 + (print_string "a" "b") + 0) +"#, + "ArityMismatch", + ), + ]; + + for (name, source, diagnostic) in cases { + let fixture = write_fixture(name, source); + let output = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + !output.status.success(), + "compiler unexpectedly accepted invalid print_string call `{}`\nstdout:\n{}\nstderr:\n{}", + name, + stdout, + stderr + ); + assert!( + stderr.contains(diagnostic), + "diagnostic `{}` was not reported for `{}`\nstderr:\n{}", + diagnostic, + name, + stderr + ); + assert!( + stdout.is_empty(), + "rejected compile wrote stdout:\n{}", + stdout + ); + } +} + +#[test] +fn print_string_literal_rejects_non_ascii_payloads() { + let source = format!( + r#" +(module main) + +(fn main () -> i32 + (print_string "zdravo {}") + 0) +"#, + '\u{017E}' + ); + let fixture = write_fixture("print-string-non-ascii", &source); + let output = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + !output.status.success(), + "compiler unexpectedly accepted non-ASCII print_string literal\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stderr.contains("UnsupportedStringLiteral") + && stderr.contains("printable ASCII string literal"), + "non-ASCII string literal did not report the expected diagnostic\nstderr:\n{}", + stderr + ); + assert!( + stdout.is_empty(), + "rejected compile wrote stdout:\n{}", + stdout + ); +} + +#[test] +fn print_string_rejects_unsupported_escape_spellings() { + for escape in ["\\0", "\\r", "\\x"] { + let source = format!( + r#" +(module main) + +(fn main () -> i32 + (print_string "{}") + 0) +"#, + escape + ); + let fixture = write_fixture("print-string-unsupported-escape", &source); + let output = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + !output.status.success(), + "compiler unexpectedly accepted unsupported escape `{}`\nstdout:\n{}\nstderr:\n{}", + escape, + stdout, + stderr + ); + assert!( + stderr.contains("UnsupportedStringEscape"), + "unsupported escape `{}` did not report the expected diagnostic\nstderr:\n{}", + escape, + stderr + ); + assert!( + stdout.is_empty(), + "rejected compile wrote stdout:\n{}", + stdout + ); + } +} + +#[test] +fn formatter_rejects_strings_outside_promoted_domain() { + let non_ascii_source = format!( + r#" +(module main) + +(fn main () -> i32 + (print_string "zdravo {}") + 0) +"#, + '\u{017E}' + ); + let cases: [(&str, String); 2] = [ + ("format-non-ascii", non_ascii_source), + ( + "format-unsupported-escape", + r#" +(module main) + +(fn main () -> i32 + (print_string "\0") + 0) +"# + .to_string(), + ), + ]; + + for (name, source) in cases { + let fixture = write_fixture(name, &source); + let output = run_glagol([OsStr::new("--format"), fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + !output.status.success(), + "formatter unexpectedly accepted `{}`\nstdout:\n{}\nstderr:\n{}", + name, + stdout, + stderr + ); + assert!( + stderr.contains("UnsupportedString"), + "formatter rejection for `{}` did not report a string-domain diagnostic\nstderr:\n{}", + name, + stderr + ); + assert!( + stdout.is_empty(), + "rejected format wrote stdout:\n{}", + stdout + ); + } +} + +#[test] +fn string_values_flow_through_returns_locals_calls_and_print_string() { + let fixture = write_fixture( + "string-value-flow", + r#" +(module main) + +(fn label () -> string + "hello") + +(fn echo ((value string)) -> string + value) + +(fn main () -> i32 + (let value string (echo (label))) + (print_string value) + (string_len value)) +"#, + ); + let output = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "compiler rejected string value-flow fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define ptr @label()") + && stdout.contains("define ptr @echo(ptr %value)") + && stdout.contains("%value.addr = alloca ptr") + && stdout.contains("call void @print_string(ptr %") + && stdout.contains("call i32 @string_len(ptr %"), + "LLVM output did not contain expected string value-flow shape\nstdout:\n{}", + stdout + ); + assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr); +} + +#[test] +fn standard_string_concat_emits_runtime_call_and_flows_as_string() { + let fixture = write_fixture( + "std-string-concat", + r#" +(module main) + +(fn hello () -> string + (std.string.concat "hel" "lo")) + +(fn shout ((value string)) -> string + (std.string.concat value "!")) + +(fn main () -> i32 + (std.io.print_string (shout (hello))) + (std.string.len (shout (hello)))) + +(test "std concat equality" + (= (std.string.concat "slo" "vo") "slovo")) + +(test "std concat length" + (= (std.string.len (std.string.concat "a" "bc")) 3)) +"#, + ); + let output = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "compiler rejected std string concat fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("declare ptr @__glagol_string_concat(ptr, ptr)") + && stdout.contains("call ptr @__glagol_string_concat(ptr @.str.") + && stdout.contains("call void @print_string(ptr %") + && stdout.contains("call i32 @string_len(ptr %") + && !stdout.contains("@std."), + "LLVM output did not contain expected concat runtime shape\nstdout:\n{}", + stdout + ); + assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr); + + let tests = run_glagol([OsStr::new("test"), fixture.as_os_str()]); + assert_success("run std string concat tests", &tests); + assert_eq!( + String::from_utf8_lossy(&tests.stdout), + concat!( + "test \"std concat equality\" ... ok\n", + "test \"std concat length\" ... ok\n", + "2 test(s) passed\n", + ), + "concat test runner stdout drifted" + ); +} + +#[test] +fn standard_string_concat_is_formatter_and_lowering_inspector_visible() { + let fixture = write_fixture( + "std-string-concat-tooling", + r#" +(module main) + +(fn joined () -> string + (std.string.concat + (std.string.concat "slo" "v") + "o")) + +(fn main () -> i32 + (std.string.len (joined))) +"#, + ); + + let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]); + assert_success("format std string concat", &formatted); + assert_eq!( + String::from_utf8_lossy(&formatted.stdout), + concat!( + "(module main)\n", + "\n", + "(fn joined () -> string\n", + " (std.string.concat (std.string.concat \"slo\" \"v\") \"o\"))\n", + "\n", + "(fn main () -> i32\n", + " (std.string.len (joined)))\n", + ), + "formatter output drifted for std string concat" + ); + + let surface = run_glagol([ + OsStr::new("--inspect-lowering=surface"), + fixture.as_os_str(), + ]); + assert_success("inspect std string concat surface lowering", &surface); + let surface_stdout = String::from_utf8_lossy(&surface.stdout); + assert!( + surface_stdout.matches("call std.string.concat").count() == 2, + "surface lowering did not show concat calls\nstdout:\n{}", + surface_stdout + ); + + let checked = run_glagol([ + OsStr::new("--inspect-lowering=checked"), + fixture.as_os_str(), + ]); + assert_success("inspect std string concat checked lowering", &checked); + let checked_stdout = String::from_utf8_lossy(&checked.stdout); + assert!( + checked_stdout + .matches("call std.string.concat : string") + .count() + == 2, + "checked lowering did not show typed concat calls\nstdout:\n{}", + checked_stdout + ); +} + +#[test] +fn standard_string_concat_keeps_plus_deferred_rejects_alias_and_checks_calls() { + let cases = [ + ( + "std-string-concat-left-type", + r#" +(module main) + +(fn main () -> string + (std.string.concat 1 "a")) +"#, + "TypeMismatch", + ), + ( + "std-string-concat-arity", + r#" +(module main) + +(fn main () -> string + (std.string.concat "a")) +"#, + "ArityMismatch", + ), + ( + "legacy-string-concat-rejected", + r#" +(module main) + +(fn main () -> string + (string_concat "a" "b")) +"#, + "UnknownFunction", + ), + ( + "private-runtime-helper-shadow-rejected", + r#" +(module main) + +(fn __glagol_string_concat ((left string) (right string)) -> string + "intercepted") + +(fn main () -> string + (std.string.concat "a" "b")) +"#, + "DuplicateFunction", + ), + ( + "plus-string-concat-still-deferred", + r#" +(module main) + +(fn main () -> string + (+ "a" "b")) +"#, + "UnsupportedStringConcatenation", + ), + ]; + + for (name, source, diagnostic) in cases { + let fixture = write_fixture(name, source); + let output = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + !output.status.success(), + "compiler unexpectedly accepted invalid concat case `{}`\nstdout:\n{}\nstderr:\n{}", + name, + stdout, + stderr + ); + assert!( + stderr.contains(diagnostic), + "diagnostic `{}` was not reported for `{}`\nstderr:\n{}", + diagnostic, + name, + stderr + ); + assert!( + stdout.is_empty(), + "rejected compile wrote stdout:\n{}", + stdout + ); + } +} + +#[test] +fn standard_string_parse_i32_result_emits_i64_runtime_decode_shape() { + let fixture = write_fixture( + "std-string-parse-i32-result-shape", + r#" +(module main) + +(fn parse_or_code ((text string)) -> i32 + (match (std.string.parse_i32_result text) + ((ok value) + value) + ((err code) + code))) + +(fn main () -> i32 + (parse_or_code "42")) +"#, + ); + let output = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "compiler rejected std string parse_i32_result fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("declare i64 @__glagol_string_parse_i32_result(ptr)") + && stdout.contains("call i64 @__glagol_string_parse_i32_result(ptr %text)") + && stdout.contains("lshr i64 %") + && stdout.contains("trunc i64 %") + && stdout.contains("insertvalue { i1, i32 }") + && stdout.contains("extractvalue { i1, i32 }") + && !stdout.contains("@std.string.parse_i32_result"), + "LLVM output did not contain expected parse_i32_result decode shape\nstdout:\n{}", + stdout + ); + assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr); +} + +#[test] +fn test_runner_executes_standard_string_parse_i32_result_boundaries() { + let non_ascii_digit = { + let path = env::temp_dir().join(format!( + "glagol-string-parse-non-ascii-digit-{}-{}.txt", + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + fs::write(&path, "\u{0661}") + .unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err)); + path + }; + let source = format!( + r#" +(module main) + +(test "parse ok positive" + (= (unwrap_ok (std.string.parse_i32_result "42")) 42)) + +(test "parse ok negative" + (= (unwrap_ok (std.string.parse_i32_result "-7")) -7)) + +(test "parse ok min i32" + (= (unwrap_ok (std.string.parse_i32_result "-2147483648")) -2147483648)) + +(test "parse ok max i32" + (= (unwrap_ok (std.string.parse_i32_result "2147483647")) 2147483647)) + +(test "parse empty err one" + (= (unwrap_err (std.string.parse_i32_result "")) 1)) + +(test "parse lone minus err one" + (= (unwrap_err (std.string.parse_i32_result "-")) 1)) + +(test "parse plus sign err one" + (= (unwrap_err (std.string.parse_i32_result "+1")) 1)) + +(test "parse leading whitespace err one" + (= (unwrap_err (std.string.parse_i32_result " 1")) 1)) + +(test "parse trailing byte err one" + (= (unwrap_err (std.string.parse_i32_result "42\n")) 1)) + +(test "parse embedded nondigit err one" + (= (unwrap_err (std.string.parse_i32_result "4x")) 1)) + +(test "parse underscore err one" + (= (unwrap_err (std.string.parse_i32_result "1_0")) 1)) + +(test "parse prefix err one" + (= (unwrap_err (std.string.parse_i32_result "0x10")) 1)) + +(test "parse overflow err one" + (= (unwrap_err (std.string.parse_i32_result "2147483648")) 1)) + +(test "parse underflow err one" + (= (unwrap_err (std.string.parse_i32_result "-2147483649")) 1)) + +(test "parse non ascii digit err one" + (= (unwrap_err (std.string.parse_i32_result (unwrap_ok (std.fs.read_text_result "{}")))) 1)) +"#, + slovo_path(&non_ascii_digit) + ); + let fixture = write_fixture("std-string-parse-i32-result-test-runner", &source); + let output = run_glagol([OsStr::new("test"), fixture.as_os_str()]); + assert_success("run std string parse_i32_result tests", &output); + assert_eq!( + String::from_utf8_lossy(&output.stdout), + concat!( + "test \"parse ok positive\" ... ok\n", + "test \"parse ok negative\" ... ok\n", + "test \"parse ok min i32\" ... ok\n", + "test \"parse ok max i32\" ... ok\n", + "test \"parse empty err one\" ... ok\n", + "test \"parse lone minus err one\" ... ok\n", + "test \"parse plus sign err one\" ... ok\n", + "test \"parse leading whitespace err one\" ... ok\n", + "test \"parse trailing byte err one\" ... ok\n", + "test \"parse embedded nondigit err one\" ... ok\n", + "test \"parse underscore err one\" ... ok\n", + "test \"parse prefix err one\" ... ok\n", + "test \"parse overflow err one\" ... ok\n", + "test \"parse underflow err one\" ... ok\n", + "test \"parse non ascii digit err one\" ... ok\n", + "15 test(s) passed\n", + ), + "parse_i32_result test runner stdout drifted" + ); +} + +#[test] +fn standard_string_parse_i32_result_runtime_smoke_when_clang_is_available() { + let Some(clang) = find_clang() else { + eprintln!( + "skipping std string parse_i32_result runtime smoke: set GLAGOL_CLANG or install clang" + ); + return; + }; + + let fixture = write_fixture( + "std-string-parse-i32-result-runtime-smoke", + r#" +(module main) + +(fn main () -> i32 + (std.io.print_i32 (unwrap_ok (std.string.parse_i32_result "42"))) + (std.io.print_i32 (unwrap_ok (std.string.parse_i32_result "-7"))) + (unwrap_err (std.string.parse_i32_result "42x"))) +"#, + ); + let compile = run_glagol([fixture.as_os_str()]); + assert_success( + "compile std string parse_i32_result runtime smoke", + &compile, + ); + + let run = compile_and_run_with_runtime(&clang, "std-string-parse-i32-result", &compile.stdout); + assert_eq!( + run.status.code(), + Some(1), + "std string parse_i32_result runtime smoke exit code drifted\nstdout:\n{}\nstderr:\n{}", + String::from_utf8_lossy(&run.stdout), + String::from_utf8_lossy(&run.stderr) + ); + assert_eq!( + String::from_utf8_lossy(&run.stdout), + "42\n-7\n", + "std string parse_i32_result runtime smoke stdout drifted" + ); + assert!( + run.stderr.is_empty(), + "std string parse_i32_result runtime smoke wrote stderr:\n{}", + String::from_utf8_lossy(&run.stderr) + ); +} + +#[test] +fn print_string_runtime_smoke_when_clang_is_available() { + let Some(clang) = find_clang() else { + eprintln!("skipping string runtime smoke: set GLAGOL_CLANG or install clang"); + return; + }; + + let fixture = write_fixture( + "print-string-runtime-smoke", + r#" +(module main) + +(fn main () -> i32 + (print_string "hello from glagol") + 0) +"#, + ); + let compile = run_glagol([fixture.as_os_str()]); + assert_success("compile string runtime smoke", &compile); + + let manifest = Path::new(env!("CARGO_MANIFEST_DIR")); + let temp_dir = env::temp_dir().join(format!( + "glagol-string-runtime-{}-{}", + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + fs::create_dir_all(&temp_dir) + .unwrap_or_else(|err| panic!("create `{}`: {}", temp_dir.display(), err)); + + let ir_path = temp_dir.join("string-runtime.ll"); + let exe_path = temp_dir.join("string-runtime"); + fs::write(&ir_path, &compile.stdout) + .unwrap_or_else(|err| panic!("write `{}`: {}", ir_path.display(), err)); + + let runtime = manifest.join("../runtime/runtime.c"); + let mut clang_command = Command::new(&clang); + clang_command + .arg(&runtime) + .arg(&ir_path) + .arg("-o") + .arg(&exe_path) + .current_dir(manifest); + configure_clang_runtime_env(&mut clang_command, &clang); + let clang_output = clang_command + .output() + .unwrap_or_else(|err| panic!("run `{}`: {}", clang.display(), err)); + assert_success("clang string runtime smoke", &clang_output); + + let run = Command::new(&exe_path) + .output() + .unwrap_or_else(|err| panic!("run `{}`: {}", exe_path.display(), err)); + assert_success("run string runtime smoke", &run); + assert_eq!( + String::from_utf8_lossy(&run.stdout), + "hello from glagol\n", + "runtime stdout drifted" + ); +} + +#[test] +fn promoted_string_print_fixture_runtime_smoke_when_clang_is_available() { + let Some(clang) = find_clang() else { + eprintln!( + "skipping promoted string fixture runtime smoke: set GLAGOL_CLANG or install clang" + ); + return; + }; + + let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/string-print.slo"); + let compile = run_glagol([fixture.as_os_str()]); + assert_success("compile promoted string-print fixture", &compile); + + let run = compile_and_run_with_runtime(&clang, "promoted-string-print", &compile.stdout); + assert_success("run promoted string-print fixture", &run); + assert_eq!( + String::from_utf8_lossy(&run.stdout), + "hello\nline\nquote\"slash\\tab\t\n", + "promoted fixture runtime stdout drifted" + ); +} + +#[test] +fn v1_2_string_and_bool_runtime_smoke_when_clang_is_available() { + let Some(clang) = find_clang() else { + eprintln!("skipping v1.2 string/bool runtime smoke: set GLAGOL_CLANG or install clang"); + return; + }; + + let fixture = write_fixture( + "v1-2-string-bool-runtime-smoke", + r#" +(module main) + +(fn label () -> string + "slovo") + +(fn main () -> i32 + (print_bool (= (label) "slovo")) + (print_bool (= (label) "other")) + (print_i32 (string_len (label))) + 0) +"#, + ); + let compile = run_glagol([fixture.as_os_str()]); + assert_success("compile v1.2 string/bool runtime smoke", &compile); + + let run = compile_and_run_with_runtime(&clang, "v1-2-string-bool", &compile.stdout); + assert_success("run v1.2 string/bool runtime smoke", &run); + assert_eq!( + String::from_utf8_lossy(&run.stdout), + "true\nfalse\n5\n", + "v1.2 string/bool runtime stdout drifted" + ); +} + +#[test] +fn standard_runtime_fixture_uses_legacy_llvm_symbols_and_tests_pass() { + let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/standard-runtime.slo"); + let compile = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + + assert!( + compile.status.success(), + "compile standard-runtime fixture failed\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("call void @print_string(ptr %") + && stdout.contains("call void @print_bool(i1 %") + && stdout.contains("call void @print_i32(i32 %") + && stdout.contains("call i32 @string_len(ptr ") + && !stdout.contains("@std."), + "standard-runtime LLVM did not lower through legacy symbols\nstdout:\n{}", + stdout + ); + assert!( + stderr.is_empty(), + "standard-runtime compile wrote stderr:\n{}", + stderr + ); + + let run = run_glagol([OsStr::new("--run-tests"), fixture.as_os_str()]); + assert_success("run standard-runtime tests", &run); + assert_eq!( + String::from_utf8_lossy(&run.stdout), + concat!( + "test \"std string equality\" ... ok\n", + "test \"std string byte length\" ... ok\n", + "2 test(s) passed\n", + ), + "standard-runtime test runner stdout drifted" + ); +} + +#[test] +fn standard_runtime_print_calls_are_rejected_by_test_runner() { + let cases = [ + ("std-print-i32-rejected", "(std.io.print_i32 1)"), + ( + "std-print-string-rejected", + "(std.io.print_string \"test\")", + ), + ("std-print-bool-rejected", "(std.io.print_bool true)"), + ]; + + for (name, print_call) in cases { + let source = format!( + r#" +(module main) + +(fn noisy () -> i32 + {} + 1) + +(test "std print rejected" + (= (noisy) 1)) +"#, + print_call + ); + let fixture = write_fixture(name, &source); + + let output = run_glagol([OsStr::new("--run-tests"), fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert_eq!( + output.status.code(), + Some(1), + "std print test runner rejection `{}` exit code drifted\nstdout:\n{}\nstderr:\n{}", + name, + stdout, + stderr + ); + assert!( + stdout.is_empty(), + "std print test runner rejection `{}` wrote stdout:\n{}", + name, + stdout + ); + assert!( + stderr.contains("UnsupportedTestExpression"), + "std print test runner rejection `{}` diagnostic drifted\nstderr:\n{}", + name, + stderr + ); + } +} + +#[test] +fn standard_runtime_fixture_runtime_smoke_when_clang_is_available() { + let Some(clang) = find_clang() else { + eprintln!("skipping standard-runtime smoke: set GLAGOL_CLANG or install clang"); + return; + }; + + let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/standard-runtime.slo"); + let compile = run_glagol([fixture.as_os_str()]); + assert_success("compile standard-runtime smoke", &compile); + + let run = compile_and_run_with_runtime(&clang, "standard-runtime", &compile.stdout); + assert_success("run standard-runtime smoke", &run); + assert_eq!( + String::from_utf8_lossy(&run.stdout), + "standard\ntrue\n8\n", + "standard-runtime stdout drifted" + ); +} + +#[test] +fn standard_string_concat_runtime_smoke_when_clang_is_available() { + let Some(clang) = find_clang() else { + eprintln!("skipping std string concat runtime smoke: set GLAGOL_CLANG or install clang"); + return; + }; + + let fixture = write_fixture( + "std-string-concat-runtime-smoke", + r#" +(module main) + +(fn main () -> i32 + (std.io.print_string (std.string.concat "hello" "!")) + (std.io.print_i32 (std.string.len (std.string.concat "ab" "c"))) + 0) +"#, + ); + let compile = run_glagol([fixture.as_os_str()]); + assert_success("compile std string concat runtime smoke", &compile); + + let run = compile_and_run_with_runtime(&clang, "std-string-concat", &compile.stdout); + assert_success("run std string concat runtime smoke", &run); + assert_eq!( + String::from_utf8_lossy(&run.stdout), + "hello!\n3\n", + "std string concat runtime stdout drifted" + ); +} + +#[test] +fn standard_string_concat_allocation_failure_traps_when_clang_is_available() { + let Some(clang) = find_clang() else { + eprintln!( + "skipping std string concat allocation trap smoke: set GLAGOL_CLANG or install clang" + ); + return; + }; + + let manifest = Path::new(env!("CARGO_MANIFEST_DIR")); + let temp_dir = env::temp_dir().join(format!( + "glagol-string-runtime-allocation-trap-{}-{}", + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + fs::create_dir_all(&temp_dir) + .unwrap_or_else(|err| panic!("create `{}`: {}", temp_dir.display(), err)); + + let probe = temp_dir.join("concat-allocation-trap.c"); + let exe = temp_dir.join("concat-allocation-trap"); + fs::write( + &probe, + r#"#include + +char *__glagol_string_concat(const char *left, const char *right); + +void *__wrap_malloc(size_t size) { + (void)size; + return NULL; +} + +int main(void) { + (void)__glagol_string_concat("a", "b"); + return 0; +} +"#, + ) + .unwrap_or_else(|err| panic!("write `{}`: {}", probe.display(), err)); + + let runtime = manifest.join("../runtime/runtime.c"); + let mut clang_command = Command::new(&clang); + clang_command + .arg(&runtime) + .arg(&probe) + .arg("-Wl,--wrap=malloc") + .arg("-o") + .arg(&exe) + .current_dir(manifest); + configure_clang_runtime_env(&mut clang_command, &clang); + let clang_output = clang_command + .output() + .unwrap_or_else(|err| panic!("run `{}`: {}", clang.display(), err)); + assert_success("clang allocation trap probe", &clang_output); + + let run = Command::new(&exe) + .output() + .unwrap_or_else(|err| panic!("run `{}`: {}", exe.display(), err)); + assert_eq!( + run.status.code(), + Some(1), + "allocation trap exit code drifted\nstdout:\n{}\nstderr:\n{}", + String::from_utf8_lossy(&run.stdout), + String::from_utf8_lossy(&run.stderr) + ); + assert_eq!( + String::from_utf8_lossy(&run.stderr), + "slovo runtime error: string allocation failed\n", + "allocation trap stderr drifted" + ); + assert!( + run.stdout.is_empty(), + "allocation trap wrote stdout:\n{}", + String::from_utf8_lossy(&run.stdout) + ); +} + +#[test] +fn v1_2_runtime_traps_exit_one_with_contract_messages_when_clang_is_available() { + let Some(clang) = find_clang() else { + eprintln!("skipping v1.2 runtime trap smoke: set GLAGOL_CLANG or install clang"); + return; + }; + + let cases = [ + ( + "array-bounds", + r#" +(module main) + +(fn at ((i i32)) -> i32 + (index (array i32 7) i)) + +(fn main () -> i32 + (at 1)) +"#, + "slovo runtime error: array index out of bounds\n", + ), + ( + "unwrap-some", + r#" +(module main) + +(fn main () -> i32 + (unwrap_some (none i32))) +"#, + "slovo runtime error: unwrap_some on none\n", + ), + ( + "unwrap-ok", + r#" +(module main) + +(fn main () -> i32 + (unwrap_ok (err i32 i32 1))) +"#, + "slovo runtime error: unwrap_ok on err\n", + ), + ( + "unwrap-err", + r#" +(module main) + +(fn main () -> i32 + (unwrap_err (ok i32 i32 1))) +"#, + "slovo runtime error: unwrap_err on ok\n", + ), + ]; + + for (name, source, expected_stderr) in cases { + let fixture = write_fixture(name, source); + let compile = run_glagol([fixture.as_os_str()]); + assert_success("compile v1.2 runtime trap smoke", &compile); + + let run = compile_and_run_with_runtime(&clang, name, &compile.stdout); + assert_eq!( + run.status.code(), + Some(1), + "trap `{}` exit code drifted\nstdout:\n{}\nstderr:\n{}", + name, + String::from_utf8_lossy(&run.stdout), + String::from_utf8_lossy(&run.stderr) + ); + assert_eq!( + String::from_utf8_lossy(&run.stderr), + expected_stderr, + "trap `{}` stderr drifted", + name + ); + assert!( + run.stdout.is_empty(), + "trap `{}` wrote stdout:\n{}", + name, + String::from_utf8_lossy(&run.stdout) + ); + } +} + +#[test] +fn v1_2_test_runner_reports_runtime_traps_with_contract_messages() { + let cases = [ + ( + "test-array-bounds", + r#" +(module main) + +(fn at ((i i32)) -> i32 + (index (array i32 7) i)) + +(test "array trap" + (= (at 1) 0)) +"#, + "slovo runtime error: array index out of bounds", + ), + ( + "test-unwrap-some", + r#" +(module main) + +(test "unwrap some trap" + (= (unwrap_some (none i32)) 0)) +"#, + "slovo runtime error: unwrap_some on none", + ), + ( + "test-unwrap-ok", + r#" +(module main) + +(test "unwrap ok trap" + (= (unwrap_ok (err i32 i32 1)) 0)) +"#, + "slovo runtime error: unwrap_ok on err", + ), + ( + "test-unwrap-err", + r#" +(module main) + +(test "unwrap err trap" + (= (unwrap_err (ok i32 i32 1)) 0)) +"#, + "slovo runtime error: unwrap_err on ok", + ), + ]; + + for (name, source, expected_message) in cases { + let fixture = write_fixture(name, source); + let output = run_glagol([OsStr::new("test"), fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert_eq!( + output.status.code(), + Some(1), + "test runner trap `{}` exit code drifted\nstdout:\n{}\nstderr:\n{}", + name, + stdout, + stderr + ); + assert!( + stdout.is_empty(), + "test runner trap `{}` wrote stdout:\n{}", + name, + stdout + ); + assert!( + stderr.contains("TestRuntimeTrap") + && stderr.contains("test trapped") + && stderr.contains(expected_message), + "test runner trap `{}` diagnostic drifted\nstderr:\n{}", + name, + stderr + ); + } +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn write_fixture(name: &str, source: &str) -> PathBuf { + let mut path = env::temp_dir(); + path.push(format!( + "glagol-string-runtime-{}-{}-{}.slo", + name, + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + fs::write(&path, source).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err)); + path +} + +fn slovo_path(path: &Path) -> String { + path.to_string_lossy() + .replace('\\', "\\\\") + .replace('"', "\\\"") +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); +} + +fn compile_and_run_with_runtime(clang: &Path, name: &str, ir: &[u8]) -> Output { + let manifest = Path::new(env!("CARGO_MANIFEST_DIR")); + let temp_dir = env::temp_dir().join(format!( + "glagol-string-runtime-{}-{}", + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + fs::create_dir_all(&temp_dir) + .unwrap_or_else(|err| panic!("create `{}`: {}", temp_dir.display(), err)); + + let ir_path = temp_dir.join(format!("{}.ll", name)); + let exe_path = temp_dir.join(name); + fs::write(&ir_path, ir).unwrap_or_else(|err| panic!("write `{}`: {}", ir_path.display(), err)); + + let runtime = manifest.join("../runtime/runtime.c"); + let mut clang_command = Command::new(clang); + clang_command + .arg(&runtime) + .arg(&ir_path) + .arg("-o") + .arg(&exe_path) + .current_dir(manifest); + configure_clang_runtime_env(&mut clang_command, clang); + let clang_output = clang_command + .output() + .unwrap_or_else(|err| panic!("run `{}`: {}", clang.display(), err)); + assert_success("clang string runtime smoke", &clang_output); + + Command::new(&exe_path) + .output() + .unwrap_or_else(|err| panic!("run `{}`: {}", exe_path.display(), err)) +} + +fn find_clang() -> Option { + if let Some(path) = env::var_os("GLAGOL_CLANG").filter(|value| !value.is_empty()) { + return Some(PathBuf::from(path)); + } + + let hermetic_clang = PathBuf::from("/tmp/glagol-clang-root/usr/bin/clang"); + if hermetic_clang.is_file() { + return Some(hermetic_clang); + } + + find_on_path("clang") +} + +fn find_on_path(program: &str) -> Option { + let path = env::var_os("PATH")?; + env::split_paths(&path) + .map(|dir| dir.join(program)) + .find(|candidate| candidate.is_file()) +} + +fn configure_clang_runtime_env(command: &mut Command, clang: &Path) { + if !clang.starts_with("/tmp/glagol-clang-root") { + return; + } + + let root = Path::new("/tmp/glagol-clang-root"); + let lib64 = root.join("usr/lib64"); + let lib = root.join("usr/lib"); + let mut paths = vec![lib64, lib]; + + if let Some(existing) = env::var_os("LD_LIBRARY_PATH") { + paths.extend(env::split_paths(&existing)); + } + + let joined = env::join_paths(paths).expect("join LD_LIBRARY_PATH"); + command.env("LD_LIBRARY_PATH", joined); +} diff --git a/compiler/tests/struct_expression.rs b/compiler/tests/struct_expression.rs new file mode 100644 index 0000000..dd03e0c --- /dev/null +++ b/compiler/tests/struct_expression.rs @@ -0,0 +1,258 @@ +use std::{fs, path::Path, process::Command}; + +#[test] +fn struct_fixture_emits_llvm_field_read_shape() { + let output = run_glagol(["../examples/struct.slo"]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "compiler rejected struct fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define i32 @point_sum()") + && stdout.contains("insertvalue { i32, i32 }") + && stdout.contains("extractvalue { i32, i32 }") + && stdout.contains("define i32 @main()"), + "LLVM output did not contain expected struct field-read shape\nstdout:\n{}", + stdout + ); + assert!( + !stdout.contains("struct field access"), + "compiler emitted test metadata into LLVM output\nstdout:\n{}", + stdout + ); + assert!( + !stdout.contains("type {"), + "first-pass struct fixture should not emit aggregate layout promises\nstdout:\n{}", + stdout + ); + assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr); +} + +#[test] +fn struct_value_flow_fixture_emits_llvm_aggregate_shape() { + let output = run_glagol(["../examples/struct-value-flow.slo"]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "compiler rejected struct value-flow fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define { i32, i32 } @make_point(i32 %x, i32 %y)") + && stdout.contains("define i32 @point_x({ i32, i32 } %p)") + && stdout.contains("define i32 @point_sum({ i32, i32 } %p)") + && stdout.contains("alloca { i32, i32 }") + && stdout.contains("call i32 @point_sum({ i32, i32 }"), + "LLVM output did not contain expected struct value-flow shape\nstdout:\n{}", + stdout + ); + assert!( + !stdout.contains("type {"), + "struct value-flow fixture should not emit named layout promises\nstdout:\n{}", + stdout + ); + assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr); +} + +#[test] +fn struct_fixture_runs_top_level_tests() { + let output = run_glagol(["--run-tests", "../examples/struct.slo"]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "test runner rejected struct fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert_eq!( + stdout, + concat!( + "test \"struct field access\" ... ok\n", + "test \"struct field compares\" ... ok\n", + "2 test(s) passed\n", + ), + "test runner output drifted" + ); + assert!(stderr.is_empty(), "test runner wrote stderr:\n{}", stderr); +} + +#[test] +fn struct_value_flow_fixture_runs_top_level_tests() { + let output = run_glagol(["--run-tests", "../examples/struct-value-flow.slo"]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "test runner rejected struct value-flow fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert_eq!( + stdout, + concat!( + "test \"struct local value flow\" ... ok\n", + "test \"struct parameter value flow\" ... ok\n", + "test \"stored struct field access\" ... ok\n", + "3 test(s) passed\n", + ), + "test runner output drifted" + ); + assert!(stderr.is_empty(), "test runner wrote stderr:\n{}", stderr); +} + +#[test] +fn struct_fixture_is_formatter_stable() { + let expected = fs::read_to_string("../tests/struct.slo").expect("read fixture"); + let output = run_glagol(["--format", "../tests/struct.slo"]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "formatter rejected struct fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert_eq!(stdout, expected, "formatter output drifted"); + assert!(stderr.is_empty(), "formatter wrote stderr:\n{}", stderr); +} + +#[test] +fn struct_value_flow_fixture_is_formatter_stable() { + let expected = fs::read_to_string("../tests/struct-value-flow.slo").expect("read fixture"); + let output = run_glagol(["--format", "../tests/struct-value-flow.slo"]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "formatter rejected struct value-flow fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert_eq!(stdout, expected, "formatter output drifted"); + assert!(stderr.is_empty(), "formatter wrote stderr:\n{}", stderr); +} + +#[test] +fn struct_fixture_prints_lowered_shape() { + let surface = run_glagol(["--inspect-lowering=surface", "../examples/struct.slo"]); + let surface_stdout = String::from_utf8_lossy(&surface.stdout); + let surface_stderr = String::from_utf8_lossy(&surface.stderr); + assert!( + surface.status.success(), + "surface lowering rejected struct fixture\nstdout:\n{}\nstderr:\n{}", + surface_stdout, + surface_stderr + ); + assert!( + surface_stdout.contains("struct Point") + && surface_stdout.contains("construct Point") + && surface_stdout.contains("field-access x") + && surface_stdout.contains("field-access y"), + "surface lowering output lost struct shape\nstdout:\n{}", + surface_stdout + ); + assert!( + surface_stderr.is_empty(), + "surface lowering wrote stderr:\n{}", + surface_stderr + ); + + let checked = run_glagol(["--inspect-lowering=checked", "../examples/struct.slo"]); + let checked_stdout = String::from_utf8_lossy(&checked.stdout); + let checked_stderr = String::from_utf8_lossy(&checked.stderr); + assert!( + checked.status.success(), + "checked lowering rejected struct fixture\nstdout:\n{}\nstderr:\n{}", + checked_stdout, + checked_stderr + ); + assert!( + checked_stdout.contains("struct Point") + && checked_stdout.contains("construct Point : Point") + && checked_stdout.contains("field-access x : i32") + && checked_stdout.contains("field-access y : i32"), + "checked lowering output lost typed struct shape\nstdout:\n{}", + checked_stdout + ); + assert!( + checked_stderr.is_empty(), + "checked lowering wrote stderr:\n{}", + checked_stderr + ); +} + +#[test] +fn struct_value_flow_fixture_prints_lowered_shape() { + let surface = run_glagol([ + "--inspect-lowering=surface", + "../examples/struct-value-flow.slo", + ]); + let surface_stdout = String::from_utf8_lossy(&surface.stdout); + let surface_stderr = String::from_utf8_lossy(&surface.stderr); + assert!( + surface.status.success(), + "surface lowering rejected struct value-flow fixture\nstdout:\n{}\nstderr:\n{}", + surface_stdout, + surface_stderr + ); + assert!( + surface_stdout.contains("fn make_point(x: i32, y: i32) -> Point") + && surface_stdout.contains("fn point_x(p: Point) -> i32") + && surface_stdout.contains("local let p: Point") + && surface_stdout.contains("field-access y"), + "surface lowering output lost struct value-flow shape\nstdout:\n{}", + surface_stdout + ); + assert!( + surface_stderr.is_empty(), + "surface lowering wrote stderr:\n{}", + surface_stderr + ); + + let checked = run_glagol([ + "--inspect-lowering=checked", + "../examples/struct-value-flow.slo", + ]); + let checked_stdout = String::from_utf8_lossy(&checked.stdout); + let checked_stderr = String::from_utf8_lossy(&checked.stderr); + assert!( + checked.status.success(), + "checked lowering rejected struct value-flow fixture\nstdout:\n{}\nstderr:\n{}", + checked_stdout, + checked_stderr + ); + assert!( + checked_stdout.contains("fn make_point(x: i32, y: i32) -> Point") + && checked_stdout.contains("fn point_x(p: Point) -> i32") + && checked_stdout.contains("call make_point : Point") + && checked_stdout.contains("var p : Point"), + "checked lowering output lost typed struct value-flow shape\nstdout:\n{}", + checked_stdout + ); + assert!( + checked_stderr.is_empty(), + "checked lowering wrote stderr:\n{}", + checked_stderr + ); +} + +fn run_glagol(args: [&str; N]) -> std::process::Output { + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} diff --git a/compiler/tests/tree_printer.rs b/compiler/tests/tree_printer.rs new file mode 100644 index 0000000..fb09a24 --- /dev/null +++ b/compiler/tests/tree_printer.rs @@ -0,0 +1,115 @@ +use std::{ + fs, + path::{Path, PathBuf}, + process::{Command, Output}, + sync::atomic::{AtomicUsize, Ordering}, +}; + +static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0); + +#[test] +fn add_fixture_prints_expected_parse_tree() { + let compiler = env!("CARGO_BIN_EXE_glagol"); + let fixture = Path::new("../examples/add.slo"); + let expected = fs::read_to_string("../tests/add.sexpr").expect("read add.sexpr"); + + let output = run_tree_printer(compiler, fixture); + + assert!( + output.status.success(), + "tree printer failed\nstdout:\n{}\nstderr:\n{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr), + ); + + let actual = String::from_utf8(output.stdout).expect("tree printer output is UTF-8"); + assert_eq!(actual, expected); + assert!( + output.stderr.is_empty(), + "tree printer wrote stderr:\n{}", + String::from_utf8_lossy(&output.stderr), + ); +} + +#[test] +fn parser_atoms_print_stably() { + let compiler = env!("CARGO_BIN_EXE_glagol"); + let fixture = write_fixture( + "parser-atoms", + r#" +(module syntax) +(sample () 3.5 "line\nquote\"slash\\" ->) +"#, + ); + let expected = + fs::read_to_string("../tests/parser-atoms.sexpr").expect("read parser-atoms.sexpr"); + + let output = run_tree_printer(compiler, &fixture); + + assert!( + output.status.success(), + "tree printer failed\nstdout:\n{}\nstderr:\n{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr), + ); + + let actual = String::from_utf8(output.stdout).expect("tree printer output is UTF-8"); + assert_eq!(actual, expected); + assert!( + output.stderr.is_empty(), + "tree printer wrote stderr:\n{}", + String::from_utf8_lossy(&output.stderr), + ); +} + +#[test] +fn top_level_test_prints_expected_parse_tree() { + let compiler = env!("CARGO_BIN_EXE_glagol"); + let fixture = Path::new("../tests/top-level-test.slo"); + let expected = + fs::read_to_string("../tests/top-level-test.sexpr").expect("read top-level-test.sexpr"); + + let output = run_tree_printer(compiler, fixture); + + assert!( + output.status.success(), + "tree printer failed\nstdout:\n{}\nstderr:\n{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr), + ); + + let actual = String::from_utf8(output.stdout).expect("tree printer output is UTF-8"); + assert_eq!(actual, expected); + assert!( + output.stderr.is_empty(), + "tree printer wrote stderr:\n{}", + String::from_utf8_lossy(&output.stderr), + ); +} + +fn run_tree_printer(compiler: &str, fixture: &Path) -> Output { + Command::new(compiler) + .arg("--print-tree") + .arg(fixture) + .output() + .unwrap_or_else(|err| { + panic!( + "run glagol --print-tree on `{}`: {}", + fixture.display(), + err + ) + }) +} + +fn write_fixture(name: &str, source: &str) -> PathBuf { + let mut path = std::env::temp_dir(); + let id = NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed); + path.push(format!( + "glagol-tree-printer-{}-{}-{}.slo", + std::process::id(), + id, + name + )); + fs::write(&path, source).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err)); + path +} diff --git a/compiler/tests/u32_numeric_primitive_alpha.rs b/compiler/tests/u32_numeric_primitive_alpha.rs new file mode 100644 index 0000000..38c4369 --- /dev/null +++ b/compiler/tests/u32_numeric_primitive_alpha.rs @@ -0,0 +1,222 @@ +use std::{ + env, + ffi::OsStr, + fs, + path::{Path, PathBuf}, + process::{Command, Output}, + sync::atomic::{AtomicUsize, Ordering}, +}; + +static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0); + +#[test] +fn u32_fixture_lowers_and_runs_tests() { + let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/u32-numeric-primitive.slo"); + + let llvm = run_glagol([fixture.as_os_str()]); + assert_success("compile u32 fixture", &llvm); + let stdout = String::from_utf8_lossy(&llvm.stdout); + assert!( + stdout.contains("declare void @print_u32(i32)") + && stdout.contains("define i32 @base()") + && stdout.contains("add i32") + && stdout.contains("mul i32") + && stdout.contains("icmp ugt i32") + && stdout.contains("icmp ult i32") + && stdout.contains("icmp eq i32") + && stdout.contains("call void @print_u32(i32") + && !stdout.contains("@std.io.print_u32"), + "u32 LLVM shape drifted\nstdout:\n{}", + stdout + ); + + let tests = run_glagol([OsStr::new("test"), fixture.as_os_str()]); + assert_success("run u32 fixture tests", &tests); + assert_eq!( + String::from_utf8_lossy(&tests.stdout), + concat!( + "test \"u32 arithmetic returns exact fixture value\" ... ok\n", + "test \"u32 comparison works in predicates\" ... ok\n", + "test \"u32 division and ordering\" ... ok\n", + "3 test(s) passed\n", + ), + "u32 test runner stdout drifted" + ); +} + +#[test] +fn u32_formatter_and_lowering_are_visible() { + let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/u32-numeric-primitive.slo"); + + let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]); + assert_success("format u32 fixture", &formatted); + let formatted_stdout = String::from_utf8_lossy(&formatted.stdout); + assert!( + formatted_stdout.contains("(fn base () -> u32") + && formatted_stdout.contains("1073741824u32") + && formatted_stdout.contains("(std.io.print_u32 (local_total))"), + "u32 formatter output omitted expected forms\nstdout:\n{}", + formatted_stdout + ); + + let surface = run_glagol([ + OsStr::new("--inspect-lowering=surface"), + fixture.as_os_str(), + ]); + assert_success("inspect u32 surface lowering", &surface); + assert_eq!( + String::from_utf8_lossy(&surface.stdout), + fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../tests/u32-numeric-primitive.surface.lower"), + ) + .expect("read u32 surface snapshot"), + "u32 surface lowering snapshot drifted" + ); + + let checked = run_glagol([ + OsStr::new("--inspect-lowering=checked"), + fixture.as_os_str(), + ]); + assert_success("inspect u32 checked lowering", &checked); + assert_eq!( + String::from_utf8_lossy(&checked.stdout), + fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../tests/u32-numeric-primitive.checked.lower"), + ) + .expect("read u32 checked snapshot"), + "u32 checked lowering snapshot drifted" + ); +} + +#[test] +fn mixed_u32_operands_are_rejected_clearly() { + for (name, expression, found) in [ + ("mixed-u32-i32", "(= 1u32 1)", "u32 and i32"), + ("mixed-u32-u64", "(= 1u32 1u64)", "u32 and u64"), + ("mixed-u32-f64", "(= 1u32 1.0)", "u32 and f64"), + ] { + let fixture = write_fixture( + name, + &format!( + "(module main)\n\n(fn main () -> i32\n (if {} 0 1))\n", + expression + ), + ); + let output = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + !output.status.success(), + "compiler unexpectedly accepted mixed u32 operands\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.is_empty(), + "rejected compile wrote stdout:\n{}", + stdout + ); + assert!( + stderr.contains("numeric operands must have the same primitive type") + && stderr.contains("mixed i32/i64/u32/u64/f64") + && stderr.contains(found), + "mixed numeric diagnostic drifted for {}\nstderr:\n{}", + name, + stderr + ); + } +} + +#[test] +fn u32_runtime_print_smoke_when_toolchain_is_available() { + let fixture = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/u32-numeric-primitive.slo"); + let binary = unique_path("u32-numeric-primitive-bin"); + + let build = run_glagol([ + OsStr::new("build"), + fixture.as_os_str(), + OsStr::new("-o"), + binary.as_os_str(), + ]); + if !build.status.success() { + let stdout = String::from_utf8_lossy(&build.stdout); + let stderr = String::from_utf8_lossy(&build.stderr); + assert!( + stdout.is_empty(), + "failed u32 build wrote stdout:\n{}", + stdout + ); + assert!( + stderr.contains("ToolchainUnavailable"), + "u32 build failed unexpectedly\nstderr:\n{}", + stderr + ); + return; + } + + let run = Command::new(&binary) + .output() + .expect("run u32 numeric primitive binary"); + assert_success("run u32 numeric primitive binary", &run); + assert_eq!( + String::from_utf8_lossy(&run.stdout), + "2147483663\n", + "u32 runtime print stdout drifted" + ); + assert!( + run.stderr.is_empty(), + "u32 runtime print wrote stderr:\n{}", + String::from_utf8_lossy(&run.stderr) + ); +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn write_fixture(name: &str, source: &str) -> PathBuf { + let mut path = env::temp_dir(); + path.push(format!( + "glagol-u32-alpha-{}-{}-{}.slo", + name, + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + fs::write(&path, source).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err)); + path +} + +fn unique_path(name: &str) -> PathBuf { + let mut path = env::temp_dir(); + path.push(format!( + "glagol-{}-{}-{}", + name, + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + path +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); +} diff --git a/compiler/tests/u64_numeric_primitive_alpha.rs b/compiler/tests/u64_numeric_primitive_alpha.rs new file mode 100644 index 0000000..a6a3684 --- /dev/null +++ b/compiler/tests/u64_numeric_primitive_alpha.rs @@ -0,0 +1,223 @@ +use std::{ + env, + ffi::OsStr, + fs, + path::{Path, PathBuf}, + process::{Command, Output}, + sync::atomic::{AtomicUsize, Ordering}, +}; + +static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0); + +#[test] +fn u64_fixture_lowers_and_runs_tests() { + let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/u64-numeric-primitive.slo"); + + let llvm = run_glagol([fixture.as_os_str()]); + assert_success("compile u64 fixture", &llvm); + let stdout = String::from_utf8_lossy(&llvm.stdout); + assert!( + stdout.contains("declare void @print_u64(i64)") + && stdout.contains("define i64 @base()") + && stdout.contains("add i64") + && stdout.contains("mul i64") + && stdout.contains("udiv i64") + && stdout.contains("icmp ugt i64") + && stdout.contains("icmp ult i64") + && stdout.contains("icmp eq i64") + && stdout.contains("call void @print_u64(i64") + && !stdout.contains("@std.io.print_u64"), + "u64 LLVM shape drifted\nstdout:\n{}", + stdout + ); + + let tests = run_glagol([OsStr::new("test"), fixture.as_os_str()]); + assert_success("run u64 fixture tests", &tests); + assert_eq!( + String::from_utf8_lossy(&tests.stdout), + concat!( + "test \"u64 arithmetic returns exact fixture value\" ... ok\n", + "test \"u64 comparison works in predicates\" ... ok\n", + "test \"u64 division and ordering\" ... ok\n", + "3 test(s) passed\n", + ), + "u64 test runner stdout drifted" + ); +} + +#[test] +fn u64_formatter_and_lowering_are_visible() { + let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/u64-numeric-primitive.slo"); + + let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]); + assert_success("format u64 fixture", &formatted); + let formatted_stdout = String::from_utf8_lossy(&formatted.stdout); + assert!( + formatted_stdout.contains("(fn base () -> u64") + && formatted_stdout.contains("4294967296u64") + && formatted_stdout.contains("(std.io.print_u64 (local_total))"), + "u64 formatter output omitted expected forms\nstdout:\n{}", + formatted_stdout + ); + + let surface = run_glagol([ + OsStr::new("--inspect-lowering=surface"), + fixture.as_os_str(), + ]); + assert_success("inspect u64 surface lowering", &surface); + assert_eq!( + String::from_utf8_lossy(&surface.stdout), + fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../tests/u64-numeric-primitive.surface.lower"), + ) + .expect("read u64 surface snapshot"), + "u64 surface lowering snapshot drifted" + ); + + let checked = run_glagol([ + OsStr::new("--inspect-lowering=checked"), + fixture.as_os_str(), + ]); + assert_success("inspect u64 checked lowering", &checked); + assert_eq!( + String::from_utf8_lossy(&checked.stdout), + fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../tests/u64-numeric-primitive.checked.lower"), + ) + .expect("read u64 checked snapshot"), + "u64 checked lowering snapshot drifted" + ); +} + +#[test] +fn mixed_u64_operands_are_rejected_clearly() { + for (name, expression, found) in [ + ("mixed-u64-i64", "(= 1u64 1i64)", "u64 and i64"), + ("mixed-u64-u32", "(= 1u64 1u32)", "u64 and u32"), + ("mixed-u64-f64", "(= 1u64 1.0)", "u64 and f64"), + ] { + let fixture = write_fixture( + name, + &format!( + "(module main)\n\n(fn main () -> i32\n (if {} 0 1))\n", + expression + ), + ); + let output = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + !output.status.success(), + "compiler unexpectedly accepted mixed u64 operands\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.is_empty(), + "rejected compile wrote stdout:\n{}", + stdout + ); + assert!( + stderr.contains("numeric operands must have the same primitive type") + && stderr.contains("mixed i32/i64/u32/u64/f64") + && stderr.contains(found), + "mixed numeric diagnostic drifted for {}\nstderr:\n{}", + name, + stderr + ); + } +} + +#[test] +fn u64_runtime_print_smoke_when_toolchain_is_available() { + let fixture = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/u64-numeric-primitive.slo"); + let binary = unique_path("u64-numeric-primitive-bin"); + + let build = run_glagol([ + OsStr::new("build"), + fixture.as_os_str(), + OsStr::new("-o"), + binary.as_os_str(), + ]); + if !build.status.success() { + let stdout = String::from_utf8_lossy(&build.stdout); + let stderr = String::from_utf8_lossy(&build.stderr); + assert!( + stdout.is_empty(), + "failed u64 build wrote stdout:\n{}", + stdout + ); + assert!( + stderr.contains("ToolchainUnavailable"), + "u64 build failed unexpectedly\nstderr:\n{}", + stderr + ); + return; + } + + let run = Command::new(&binary) + .output() + .expect("run u64 numeric primitive binary"); + assert_success("run u64 numeric primitive binary", &run); + assert_eq!( + String::from_utf8_lossy(&run.stdout), + "4294967315\n", + "u64 runtime print stdout drifted" + ); + assert!( + run.stderr.is_empty(), + "u64 runtime print wrote stderr:\n{}", + String::from_utf8_lossy(&run.stderr) + ); +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn write_fixture(name: &str, source: &str) -> PathBuf { + let mut path = env::temp_dir(); + path.push(format!( + "glagol-u64-alpha-{}-{}-{}.slo", + name, + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + fs::write(&path, source).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err)); + path +} + +fn unique_path(name: &str) -> PathBuf { + let mut path = env::temp_dir(); + path.push(format!( + "glagol-{}-{}-{}", + name, + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + path +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); +} diff --git a/compiler/tests/unsafe_blocks.rs b/compiler/tests/unsafe_blocks.rs new file mode 100644 index 0000000..774fb17 --- /dev/null +++ b/compiler/tests/unsafe_blocks.rs @@ -0,0 +1,266 @@ +use std::{fs, path::Path, process::Command}; + +const UNSAFE_HEADS: &[&str] = &[ + "alloc", + "dealloc", + "load", + "store", + "ptr_add", + "unchecked_index", + "reinterpret", + "ffi_call", +]; + +#[test] +fn unsafe_fixture_is_formatter_stable() { + let fixture = Path::new("../tests/unsafe.slo"); + let expected = fs::read_to_string(fixture).expect("read unsafe formatter fixture"); + + let output = run_glagol(["--format", fixture.to_str().expect("fixture path is UTF-8")]); + + assert_success_stdout(output, &expected, "unsafe formatter output"); +} + +#[test] +fn unsafe_fixture_prints_expected_surface_ast() { + let expected = + fs::read_to_string("../tests/unsafe.surface.lower").expect("read surface fixture"); + let output = run_glagol(["--inspect-lowering=surface", "../tests/unsafe.slo"]); + + assert_success_stdout(output, &expected, "unsafe surface lowering output"); +} + +#[test] +fn unsafe_fixture_prints_expected_checked_ast() { + let expected = + fs::read_to_string("../tests/unsafe.checked.lower").expect("read checked fixture"); + let output = run_glagol(["--inspect-lowering=checked", "../tests/unsafe.slo"]); + + assert_success_stdout(output, &expected, "unsafe checked lowering output"); +} + +#[test] +fn unsafe_fixture_emits_safe_body_llvm_shape() { + let output = run_glagol(["../examples/unsafe.slo"]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "compiler rejected unsafe fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define i32 @add_one_in_unsafe(i32 %value)") + && stdout.contains("%0 = add i32 %value, 1") + && stdout.contains("ret i32 %0") + && !stdout.contains("%one.addr = alloca i32") + && !stdout.contains("load i32, ptr %one.addr") + && stdout.contains("define i32 @main()"), + "compiler output for unsafe fixture lost safe block shape\nstdout:\n{}", + stdout + ); + assert!( + !stdout.contains("unsafe block returns final value") + && !stdout.contains("unsafe block can return bool"), + "compiler output should not emit top-level test names as LLVM metadata\nstdout:\n{}", + stdout + ); + assert!( + stderr.is_empty(), + "compiler wrote stderr for unsafe fixture:\n{}", + stderr + ); +} + +#[test] +fn unsafe_fixture_runs_top_level_tests() { + let output = run_glagol(["--run-tests", "../examples/unsafe.slo"]); + + assert_success_stdout( + output, + concat!( + "test \"unsafe block returns final value\" ... ok\n", + "test \"unsafe block can return bool\" ... ok\n", + "2 test(s) passed\n", + ), + "unsafe test runner output", + ); +} + +#[test] +fn unsafe_operation_heads_require_marker_before_function_lookup() { + for head in UNSAFE_HEADS { + let source = format!("(module main)\n\n(fn main () -> i32\n ({} 1))\n", head); + let output = run_glagol_source(&format!("unsafe-required-{}", head), &source); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + !output.status.success(), + "compiler accepted unsafe operation `{}` outside unsafe", + head + ); + assert!( + stderr.contains("(code UnsafeRequired)") && !stderr.contains("UnknownFunction"), + "unsafe operation `{}` did not produce UnsafeRequired before call lookup\nstderr:\n{}", + head, + stderr + ); + } + + let output = run_glagol_source( + "reserved-unsafe-head", + "(module main)\n\n(fn alloc () -> i32\n 1)\n\n(fn main () -> i32\n (alloc))\n", + ); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + !output.status.success(), + "compiler allowed user-defined unsafe head call outside unsafe" + ); + assert!( + stderr.contains("(code UnsafeRequired)") && !stderr.contains("UnknownFunction"), + "reserved unsafe head did not produce UnsafeRequired before call lookup\nstderr:\n{}", + stderr + ); +} + +#[test] +fn unsafe_operation_heads_are_reserved_from_user_bindings() { + for head in UNSAFE_HEADS { + let local_function = format!("(module main)\n\n(fn {} () -> i32\n 1)\n", head); + let output = run_glagol_source(&format!("reserved-function-{}", head), &local_function); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + !output.status.success() && stderr.contains("(code DuplicateFunction)"), + "compiler allowed unsafe head `{}` as a function\nstderr:\n{}", + head, + stderr + ); + + let parameter = format!( + "(module main)\n\n(fn main (({} i32)) -> i32\n {})\n", + head, head + ); + let output = run_glagol_source(&format!("reserved-param-{}", head), ¶meter); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + !output.status.success() && stderr.contains("(code ParameterShadowsCallable)"), + "compiler allowed unsafe head `{}` as a parameter\nstderr:\n{}", + head, + stderr + ); + + let local = format!( + "(module main)\n\n(fn main () -> i32\n (let {} i32 1)\n {})\n", + head, head + ); + let output = run_glagol_source(&format!("reserved-local-{}", head), &local); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + !output.status.success() && stderr.contains("(code LocalShadowsCallable)"), + "compiler allowed unsafe head `{}` as a local\nstderr:\n{}", + head, + stderr + ); + } +} + +#[test] +fn unsafe_operation_heads_remain_unsupported_inside_marker() { + for head in UNSAFE_HEADS { + let source = format!( + "(module main)\n\n(fn main () -> i32\n (unsafe\n ({} 1)))\n", + head + ); + let output = run_glagol_source(&format!("unsupported-unsafe-{}", head), &source); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + !output.status.success(), + "compiler accepted raw unsafe operation `{}` inside unsafe", + head + ); + assert!( + stderr.contains("(code UnsupportedUnsafeOperation)") + && !stderr.contains("UnknownFunction"), + "unsafe operation `{}` did not produce UnsupportedUnsafeOperation inside unsafe\nstderr:\n{}", + head, + stderr + ); + } +} + +#[test] +fn scoped_unsafe_locals_get_hygienic_llvm_storage_names() { + let source = r#" +(module main) + +(fn scoped () -> i32 + (unsafe + (var one i32 1) + (print_i32 one)) + (unsafe + (var one i32 2) + (print_i32 one)) + (var one i32 3) + one) + +(fn main () -> i32 + (scoped)) +"#; + let output = run_glagol_source("unsafe-scoped-locals", source); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "compiler rejected scoped unsafe locals\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.matches("%one.addr = alloca i32").count() == 1 + && stdout.matches("%one.addr.1 = alloca i32").count() == 1 + && stdout.matches("%one.addr.2 = alloca i32").count() == 1, + "LLVM output did not assign hygienic local storage names\nstdout:\n{}", + stdout + ); +} + +fn run_glagol(args: [&str; N]) -> std::process::Output { + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .output() + .expect("run glagol") +} + +fn run_glagol_source(name: &str, source: &str) -> std::process::Output { + let path = + std::env::temp_dir().join(format!("glagol-unsafe-{}-{}.slo", name, std::process::id())); + fs::write(&path, source).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err)); + let output = Command::new(env!("CARGO_BIN_EXE_glagol")) + .arg(&path) + .output() + .unwrap_or_else(|err| panic!("run glagol on `{}`: {}", path.display(), err)); + let _ = fs::remove_file(path); + output +} + +fn assert_success_stdout(output: std::process::Output, expected: &str, context: &str) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert_eq!(stdout, expected, "{} drifted", context); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} diff --git a/compiler/tests/unsigned_integer_to_string_alpha.rs b/compiler/tests/unsigned_integer_to_string_alpha.rs new file mode 100644 index 0000000..3795d4e --- /dev/null +++ b/compiler/tests/unsigned_integer_to_string_alpha.rs @@ -0,0 +1,240 @@ +use std::{ + env, + ffi::OsStr, + fs, + path::{Path, PathBuf}, + process::{Command, Output}, + sync::atomic::{AtomicUsize, Ordering}, +}; + +static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0); + +#[test] +fn unsigned_integer_to_string_fixture_lowers_and_runs_tests() { + let fixture = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/unsigned-integer-to-string.slo"); + + let llvm = run_glagol([fixture.as_os_str()]); + assert_success("compile unsigned-integer-to-string fixture", &llvm); + let stdout = String::from_utf8_lossy(&llvm.stdout); + assert!( + stdout.contains("declare ptr @__glagol_num_u32_to_string(i32)") + && stdout.contains("declare ptr @__glagol_num_u64_to_string(i64)") + && stdout.contains("call ptr @__glagol_num_u32_to_string(i32 0)") + && stdout.contains("call ptr @__glagol_num_u32_to_string(i32 4294967295)") + && stdout.contains("call ptr @__glagol_num_u64_to_string(i64 18446744073709551615)") + && stdout.contains("call ptr @__glagol_num_u64_to_string(i64 4294967296)") + && stdout.contains("call void @print_string(ptr %") + && !stdout.contains("@std.num."), + "unsigned-integer-to-string LLVM shape drifted\nstdout:\n{}", + stdout + ); + + let tests = run_glagol([OsStr::new("test"), fixture.as_os_str()]); + assert_success("run unsigned-integer-to-string fixture tests", &tests); + assert_eq!( + String::from_utf8_lossy(&tests.stdout), + concat!( + "test \"u32 zero to string\" ... ok\n", + "test \"u32 high to string\" ... ok\n", + "test \"u32 high string length\" ... ok\n", + "test \"u64 zero to string\" ... ok\n", + "test \"u64 high to string\" ... ok\n", + "test \"u64 beyond u32 to string\" ... ok\n", + "test \"u64 high string length\" ... ok\n", + "7 test(s) passed\n", + ), + "unsigned-integer-to-string test runner stdout drifted" + ); +} + +#[test] +fn unsigned_integer_to_string_hosted_runtime_formats_bounds_when_toolchain_is_available() { + let fixture = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/unsigned-integer-to-string.slo"); + let binary = unique_path("unsigned-integer-to-string-bin"); + + let build = run_glagol([ + OsStr::new("build"), + fixture.as_os_str(), + OsStr::new("-o"), + binary.as_os_str(), + ]); + if !build.status.success() { + let stdout = String::from_utf8_lossy(&build.stdout); + let stderr = String::from_utf8_lossy(&build.stderr); + assert!( + stdout.is_empty(), + "failed unsigned-integer-to-string build wrote stdout:\n{}", + stdout + ); + assert!( + stderr.contains("ToolchainUnavailable"), + "unsigned-integer-to-string build failed unexpectedly\nstderr:\n{}", + stderr + ); + return; + } + + let run = Command::new(&binary) + .output() + .expect("run unsigned-integer-to-string binary"); + assert_success("run unsigned-integer-to-string binary", &run); + assert_eq!( + String::from_utf8_lossy(&run.stdout), + "0\n4294967295\n0\n18446744073709551615\n4294967296\n", + "unsigned-integer-to-string binary stdout drifted" + ); + assert!( + run.stderr.is_empty(), + "unsigned-integer-to-string binary wrote stderr:\n{}", + String::from_utf8_lossy(&run.stderr) + ); +} + +#[test] +fn unsigned_integer_to_string_formatter_and_lowering_are_visible() { + let fixture = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/unsigned-integer-to-string.slo"); + + let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]); + assert_success("format unsigned-integer-to-string fixture", &formatted); + let formatted_stdout = String::from_utf8_lossy(&formatted.stdout); + assert!( + formatted_stdout.contains("(std.num.u32_to_string 4294967295u32)") + && formatted_stdout.contains("(std.num.u64_to_string 18446744073709551615u64)") + && formatted_stdout.contains("(std.string.len (u64_high_text))"), + "unsigned-integer-to-string formatter output omitted expected calls\nstdout:\n{}", + formatted_stdout + ); + + let surface = run_glagol([ + OsStr::new("--inspect-lowering=surface"), + fixture.as_os_str(), + ]); + assert_success( + "inspect unsigned-integer-to-string surface lowering", + &surface, + ); + assert_eq!( + String::from_utf8_lossy(&surface.stdout), + fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../tests/unsigned-integer-to-string.surface.lower"), + ) + .expect("read unsigned-integer-to-string surface snapshot"), + "unsigned-integer-to-string surface lowering snapshot drifted" + ); + + let checked = run_glagol([ + OsStr::new("--inspect-lowering=checked"), + fixture.as_os_str(), + ]); + assert_success( + "inspect unsigned-integer-to-string checked lowering", + &checked, + ); + assert_eq!( + String::from_utf8_lossy(&checked.stdout), + fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../tests/unsigned-integer-to-string.checked.lower"), + ) + .expect("read unsigned-integer-to-string checked snapshot"), + "unsigned-integer-to-string checked lowering snapshot drifted" + ); +} + +#[test] +fn unsigned_integer_to_string_rejections_are_explicit() { + for (name, source, expected) in [ + ( + "u32-arity", + "(module main)\n\n(fn main () -> string\n (std.num.u32_to_string))\n", + "wrong number of arguments", + ), + ( + "u64-type", + "(module main)\n\n(fn main () -> string\n (std.num.u64_to_string 1u32))\n", + "cannot call `std.num.u64_to_string` with argument of wrong type", + ), + ( + "generic-to-string", + "(module main)\n\n(fn main () -> i32\n (std.num.to_string 1u32)\n 0)\n", + "standard library call `std.num.to_string` is not supported", + ), + ] { + let fixture = write_fixture(name, source); + let output = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + !output.status.success(), + "compiler unexpectedly accepted unsigned-integer-to-string rejection fixture `{}`\nstdout:\n{}\nstderr:\n{}", + name, + stdout, + stderr + ); + assert!( + stdout.is_empty(), + "rejected compile wrote stdout for `{}`:\n{}", + name, + stdout + ); + assert!( + stderr.contains(expected), + "unsigned-integer-to-string diagnostic drifted for `{}`\nstderr:\n{}", + name, + stderr + ); + } +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} + +fn write_fixture(name: &str, source: &str) -> PathBuf { + let mut path = env::temp_dir(); + path.push(format!( + "glagol-unsigned-integer-to-string-alpha-{}-{}-{}.slo", + name, + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + fs::write(&path, source).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err)); + path +} + +fn unique_path(name: &str) -> PathBuf { + let mut path = env::temp_dir(); + path.push(format!( + "glagol-{}-{}-{}", + name, + std::process::id(), + NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed) + )); + path +} + +fn assert_success(context: &str, output: &Output) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + stdout, + stderr + ); + assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr); +} diff --git a/compiler/tests/user_adts_alpha.rs b/compiler/tests/user_adts_alpha.rs new file mode 100644 index 0000000..cb77e2f --- /dev/null +++ b/compiler/tests/user_adts_alpha.rs @@ -0,0 +1,105 @@ +use std::{ + ffi::OsStr, + path::{Path, PathBuf}, + process::{Command, Output}, +}; + +#[test] +fn user_adts_alpha_fixture_emits_enum_discriminants_and_tests_pass() { + let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/enum-basic.slo"); + let compile = run_glagol([fixture.as_os_str()]); + let stdout = String::from_utf8_lossy(&compile.stdout); + let stderr = String::from_utf8_lossy(&compile.stderr); + + assert!( + compile.status.success(), + "compiler rejected enum fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define i32 @signal_code(i32 %signal)") + && stdout.contains("switch i32 %signal") + && stdout.contains("icmp eq i32") + && !stdout.contains("@Signal."), + "LLVM output did not contain expected enum discriminant shape\nstdout:\n{}", + stdout + ); + + let run = run_glagol([OsStr::new("--run-tests"), fixture.as_os_str()]); + assert_success_stdout( + run, + concat!( + "test \"enum constructor equality\" ... ok\n", + "test \"enum local return call flow\" ... ok\n", + "test \"enum parameter equality\" ... ok\n", + "test \"enum match red\" ... ok\n", + "test \"enum match multi expression arm\" ... ok\n", + "test \"enum match green\" ... ok\n", + "6 test(s) passed\n", + ), + ); +} + +#[test] +fn user_adts_alpha_formatter_and_lowering_are_visible() { + let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/enum-basic.slo"); + + let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]); + assert_success_stdout( + formatted, + &std::fs::read_to_string(&fixture).expect("read enum fixture"), + ); + + let surface = run_glagol([ + OsStr::new("--inspect-lowering=surface"), + fixture.as_os_str(), + ]); + assert_success_stdout( + surface, + &std::fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/enum-basic.surface.lower"), + ) + .expect("read surface snapshot"), + ); + + let checked = run_glagol([ + OsStr::new("--inspect-lowering=checked"), + fixture.as_os_str(), + ]); + assert_success_stdout( + checked, + &std::fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/enum-basic.checked.lower"), + ) + .expect("read checked snapshot"), + ); +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(glagol_bin()) + .args(args) + .output() + .expect("run glagol") +} + +fn glagol_bin() -> PathBuf { + PathBuf::from(env!("CARGO_BIN_EXE_glagol")) +} + +fn assert_success_stdout(output: Output, expected: &str) { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "command failed\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert_eq!(stdout, expected); + assert!(stderr.is_empty(), "stderr was not empty:\n{}", stderr); +} diff --git a/compiler/tests/while_loop.rs b/compiler/tests/while_loop.rs new file mode 100644 index 0000000..1f12d39 --- /dev/null +++ b/compiler/tests/while_loop.rs @@ -0,0 +1,127 @@ +use std::{fs, path::Path, process::Command}; + +#[test] +fn while_fixture_emits_llvm_loop_shape() { + let output = run_glagol(["../tests/while.slo"]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "compiler rejected while fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stdout.contains("define i32 @count_to(i32 %limit)") + && stdout.contains("while.cond") + && stdout.contains("while.body") + && stdout.contains("while.end") + && stdout.contains("br i1") + && stdout.contains("br label %while.cond"), + "LLVM output did not contain expected while loop shape\nstdout:\n{}", + stdout + ); + assert!( + !stdout.contains("while counts"), + "compiler emitted test metadata into LLVM output\nstdout:\n{}", + stdout + ); + assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr); +} + +#[test] +fn while_fixture_runs_top_level_tests() { + let output = run_glagol(["--run-tests", "../tests/while.slo"]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "test runner rejected while fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert_eq!( + stdout, + concat!( + "test \"while counts\" ... ok\n", + "test \"while false skips\" ... ok\n", + "2 test(s) passed\n", + ), + "test runner output drifted" + ); + assert!(stderr.is_empty(), "test runner wrote stderr:\n{}", stderr); +} + +#[test] +fn while_fixture_is_formatter_stable() { + let expected = fs::read_to_string("../tests/while.slo").expect("read fixture"); + let output = run_glagol(["--format", "../tests/while.slo"]); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "formatter rejected while fixture\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert_eq!(stdout, expected, "formatter output drifted"); + assert!(stderr.is_empty(), "formatter wrote stderr:\n{}", stderr); +} + +#[test] +fn while_fixture_prints_lowered_shape() { + let surface = run_glagol(["--inspect-lowering=surface", "../tests/while.slo"]); + let surface_stdout = String::from_utf8_lossy(&surface.stdout); + let surface_stderr = String::from_utf8_lossy(&surface.stderr); + assert!( + surface.status.success(), + "surface lowering rejected while fixture\nstdout:\n{}\nstderr:\n{}", + surface_stdout, + surface_stderr + ); + assert!( + surface_stdout.contains("while\n") + && surface_stdout.contains("binary <") + && surface_stdout.contains("set i"), + "surface lowering output lost while shape\nstdout:\n{}", + surface_stdout + ); + assert!( + surface_stderr.is_empty(), + "surface lowering wrote stderr:\n{}", + surface_stderr + ); + + let checked = run_glagol(["--inspect-lowering=checked", "../tests/while.slo"]); + let checked_stdout = String::from_utf8_lossy(&checked.stdout); + let checked_stderr = String::from_utf8_lossy(&checked.stderr); + assert!( + checked.status.success(), + "checked lowering rejected while fixture\nstdout:\n{}\nstderr:\n{}", + checked_stdout, + checked_stderr + ); + assert!( + checked_stdout.contains("while : unit") + && checked_stdout.contains("binary < : bool") + && checked_stdout.contains("set i : unit"), + "checked lowering output lost typed while shape\nstdout:\n{}", + checked_stdout + ); + assert!( + checked_stderr.is_empty(), + "checked lowering wrote stderr:\n{}", + checked_stderr + ); +} + +fn run_glagol(args: [&str; N]) -> std::process::Output { + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR"))) + .output() + .expect("run glagol") +} diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..9e46e3f --- /dev/null +++ b/docs/README.md @@ -0,0 +1,13 @@ +# Slovo Documentation + +This directory contains the public documentation for Slovo and Glagol. + +- `language/`: language manifest, specs, roadmap, release notes, and historical + language examples +- `compiler/`: Glagol compiler manifest, roadmap, contribution notes, and + release notes +- `papers/`: technical whitepapers and generated PDF publications +- `assets/`: fonts and CSS used for PDF generation + +Use `../scripts/render-doc-pdfs.sh` from the repository root to regenerate the +publication PDFs. diff --git a/docs/assets/fonts/NotoSansGlagolitic-Regular.ttf b/docs/assets/fonts/NotoSansGlagolitic-Regular.ttf new file mode 100644 index 0000000..2862c2b Binary files /dev/null and b/docs/assets/fonts/NotoSansGlagolitic-Regular.ttf differ diff --git a/docs/assets/fonts/OFL-NotoSansGlagolitic.txt b/docs/assets/fonts/OFL-NotoSansGlagolitic.txt new file mode 100644 index 0000000..c82d72e --- /dev/null +++ b/docs/assets/fonts/OFL-NotoSansGlagolitic.txt @@ -0,0 +1,94 @@ +Copyright 2018 The Noto Project Authors (github.com/googlei18n/noto-fonts) + +This Font Software is licensed under the SIL Open Font License, +Version 1.1. + +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font +creation efforts of academic and linguistic communities, and to +provide a free and open framework in which fonts may be shared and +improved in partnership with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply to +any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software +components as distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, +deleting, or substituting -- in part or in whole -- any of the +components of the Original Version, by changing formats or by porting +the Font Software to a new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, +modify, redistribute, and sell modified and unmodified copies of the +Font Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, in +Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the +corresponding Copyright Holder. This restriction only applies to the +primary font name as presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created using +the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/docs/compiler/CONTRIBUTING.md b/docs/compiler/CONTRIBUTING.md new file mode 100644 index 0000000..f826813 --- /dev/null +++ b/docs/compiler/CONTRIBUTING.md @@ -0,0 +1,16 @@ +# Glagol Contribution Notes + +Glagol is the compiler implementation inside the Slovo monorepo. + +Run from the repository root: + +```bash +cargo fmt --manifest-path compiler/Cargo.toml --check +cargo test --manifest-path compiler/Cargo.toml +./scripts/release-gate.sh +``` + +Compiler changes should update parser, lowering, checking, formatting, +diagnostics, backend/runtime behavior, examples, and tests together for the +claimed surface. Unsupported source-reachable forms should produce structured +diagnostics rather than backend panics or invalid LLVM. diff --git a/docs/compiler/GLAGOL_COMPILER_MANIFEST.md b/docs/compiler/GLAGOL_COMPILER_MANIFEST.md new file mode 100644 index 0000000..8282eb3 --- /dev/null +++ b/docs/compiler/GLAGOL_COMPILER_MANIFEST.md @@ -0,0 +1,42 @@ +# Glagol Compiler Manifest: Principles for a Visible Typed Pipeline + +Sanjin Gumbarevic
+hermeticum_lab@protonmail.com + +> Make the tree visible. + +Glagol (ⰃⰎⰀⰃⰑⰎ) is the first compiler for Slovo. Its job is not merely to accept parenthesized text. Its job is to preserve Slovo's structure through parsing, checking, diagnostics, lowering, and executable output. + +## Principles + +- Slovo specifies first; Glagol implements second. +- Parse trees, ASTs, typed ASTs, and LLVM IR are separate compiler stages. +- LLVM emission starts from checked representation, not raw source forms. +- User-source errors produce diagnostics, not compiler panics. +- Formatter output, diagnostics, examples, tests, and docs are part of the + language contract. +- Native execution is a compiler target, but stable ABI promises require an + explicit future contract. +- Benchmarks are local evidence unless a release explicitly defines a broader + performance methodology. + +## Support Rule + +A source feature is supported only when Glagol can: + +1. parse it +2. lower it +3. type-check it +4. format it when formatting applies +5. diagnose invalid forms structurally +6. emit valid LLVM or reject it before backend emission +7. cover it with automated tests and release documentation + +Partial recognition is not support. + +## Beta Rule + +The first real beta compiler release is `1.0.0-beta`. It requires a matching +Slovo beta contract, conformance suite, stable diagnostics schema, stable +formatter behavior, stable package/manifest behavior, standard-library +compatibility rules, and release reviews with no blocking manifest drift. diff --git a/docs/compiler/RELEASE_NOTES.md b/docs/compiler/RELEASE_NOTES.md new file mode 100644 index 0000000..9f9a53b --- /dev/null +++ b/docs/compiler/RELEASE_NOTES.md @@ -0,0 +1,4893 @@ +# Glagol Release Notes + +## Release Maturity Policy + +Historical `exp-*` releases listed here are experimental maturity milestones. +`1.0.0-beta` is the first real general-purpose beta release. + +The pushed tag `v2.0.0-beta.1` is historical. It remains an experimental +integration/readiness release, not the first real beta. + +## 1.0.0-beta + +Release label: `1.0.0-beta` + +Release date: 2026-05-21 + +Release state: first real general-purpose beta toolchain release + +### Summary + +Glagol `1.0.0-beta` integrates the completed unsigned `u32` / `u64` compiler +and stdlib breadth scope with the already promoted project/package workflow, +explicit std-source imports, concrete vector families, fixed arrays, structs, +enums, result/option, JSON diagnostics, formatter modes, generated docs, and +the paired local release gate. + +The normative Glagol beta gate is `.llm/V1_0_0_BETA_RELEASE_GATE.md`. The beta +workflow proof lives in `compiler/tests/beta_1_0_0.rs`. + +### Explicit Deferrals + +No generics, no maps/sets, no remote registries, no networking/async runtime +surface, and no stable ABI/layout guarantees are included by beta. + +## exp-125 + +Release label: `exp-125` + +Release date: 2026-05-21 + +Release state: completed experimental precursor scope absorbed into `1.0.0-beta` + +### Summary + +exp-125 completed the connected unsigned `u32` / `u64` compiler and stdlib +breadth scope that directly enabled `1.0.0-beta`. + +## exp-123 + +## exp-124 + +Release label: `exp-124` + +Release date: 2026-05-21 + +Release state: gated experimental fixed-array enum-and-struct-elements compiler slice + +### Summary + +Glagol exp-124 gates one connected compiler broadening: fixed immutable arrays +now support direct known enum elements and current known non-recursive struct +elements alongside the earlier direct scalar and `string` lanes. + +The release updates checker admission, formatter array rendering, LLVM +fixed-array typing for named element layouts, test-runner array values, +machine-diagnostic snapshots, promoted `array-enum` and +`array-struct-elements` fixtures and lowering snapshots, focused Glagol tests, +`llvm_smoke`/`lowering_inspector` inventories, and promotion-gate alignment. + +### Explicit Deferrals + +No zero-length arrays, no mutable arrays, no element mutation, no array +equality, no array printing, no nested arrays, no slices or generics, no +stable ABI/layout guarantees, and no beta maturity are included. + +## exp-123 + +Release label: `exp-123` + +Release date: 2026-05-21 + +Release state: gated experimental owned-vector benchmark-suite extension and whitepaper refresh + +### Summary + +Glagol exp-123 gates one connected benchmark/publication broadening: the local +benchmark scaffold inventory now includes `vec-i32-index-loop` and +`vec-string-eq-loop` beside `math-loop`, `branch-loop`, `parse-loop`, +`array-index-loop`, `string-eq-loop`, `array-struct-field-loop`, and +`enum-struct-payload-loop`. + +The release keeps the shared runner model intact, adds the two new scaffold +directories with Slovo/C/Rust/Python/Clojure/Common Lisp implementations, +extends the focused benchmark scaffold test and promotion-gate inventory from +seven kernels to nine, reruns local benchmark evidence, and refreshes the +Glagol whitepaper/publication docs around the expanded suite and the current +experimental compiler surface through `exp-121`. + +### Explicit Deferrals + +No language-surface change, no compiler/runtime widening, no runtime ABI +claim, no benchmark thresholds, no cross-machine performance claims, no broad +optimizer guarantees, no PDF pipeline redesign, and no beta maturity are +included. + +## exp-122 + +Release label: `exp-122` + +Release date: 2026-05-21 + +Release state: gated experimental composite-data benchmark-suite extension and whitepaper refresh + +### Summary + +Glagol exp-122 gates one connected benchmark/publication broadening: the local +benchmark scaffold inventory now includes `array-struct-field-loop` and +`enum-struct-payload-loop` beside `math-loop`, `branch-loop`, `parse-loop`, +`array-index-loop`, and `string-eq-loop`. + +The release keeps the shared runner model intact, adds the two new scaffold +directories with Slovo/C/Rust/Python/Clojure/Common Lisp implementations, +extends the focused benchmark scaffold test and promotion-gate inventory from +five kernels to seven, reruns local benchmark evidence, and refreshes the +Glagol whitepaper/publication docs around the expanded suite and the current +experimental compiler surface through `exp-121`. + +During the final gate pass, Glagol also tightened one existing backend path: +`none f64` option aggregates now emit a typed `double 0.0` zero payload +instead of invalid `double 0`, preserving the already promoted `option f64` +surface under the clang-backed LLVM smoke gate. + +### Explicit Deferrals + +No language-surface change, no runtime ABI claim, no benchmark thresholds, no +cross-machine performance claims, no broad optimizer guarantees, no PDF +pipeline redesign, and no beta maturity are included. + +## exp-121 + +Release label: `exp-121` + +Release date: 2026-05-21 + +Release state: gated experimental non-recursive-struct enum-payload compiler slice + +### Summary + +Glagol exp-121 gates one connected enum-payload broadening: unary enum payload +variants now support current known non-recursive struct types. + +The release updates checker and formatter payload admission, LLVM enum +aggregate lowering and zero-payload handling, test-runner struct payload +support, machine-diagnostic snapshots, the promoted `enum-payload-structs` +fixture and lowering snapshots, focused `enum_payload_structs_alpha` +coverage, and promotion-gate alignment. The fixture intentionally uses an +array-bearing struct payload so exp-120 is exercised indirectly without +widening enums to direct array payloads. + +### Explicit Deferrals + +No direct array/vec/option/result payloads, no equality requirement for +struct-payload enums, no mutation, no recursive/cyclic payloads, no stable +ABI/layout guarantees, and no beta maturity are included. + +## exp-120 + +Release label: `exp-120` + +Release date: 2026-05-21 + +Release state: gated experimental fixed-array struct-field compiler slice + +### Summary + +Glagol exp-120 gates one connected compiler broadening: direct struct field +declarations now support the already promoted fixed immutable array families +`(array i32 N)`, `(array i64 N)`, `(array f64 N)`, `(array bool N)`, and +`(array string N)`. + +The release updates struct-field checker admission, machine-diagnostic +snapshots, the promoted `array-struct-fields` fixture and lowering snapshots, +focused `array_struct_fields_alpha` coverage, `llvm_smoke`/`lowering_inspector` +inventory, and promotion-gate alignment. The earlier array fixtures remain +valid as the underlying fixed-array lanes, and the earlier struct-field +fixtures remain valid as narrower sibling lanes. + +### Explicit Deferrals + +No zero-length arrays, no mutable arrays, no element mutation, no field +mutation, no nested arrays, no arrays of unsupported element kinds, no array +equality, no array printing, no stable ABI/layout guarantees, and no beta +maturity are included. + +## exp-119 + +Release label: `exp-119` + +Release date: 2026-05-21 + +Release state: gated experimental benchmark-suite extension and whitepaper refresh + +### Summary + +Glagol exp-119 gates one connected benchmark/publication broadening: the local +benchmark scaffold inventory now includes `array-index-loop` and +`string-eq-loop` beside the existing `math-loop`, `branch-loop`, and +`parse-loop` kernels. + +The release keeps the shared runner model intact, adds the two new scaffold +directories with Slovo/C/Rust/Python/Clojure/Common Lisp implementations, +extends the focused benchmark scaffold test and promotion-gate inventory, and +refreshes the Glagol whitepaper/publication docs around the expanded suite and +the current experimental compiler surface. + +### Explicit Deferrals + +No language-surface change, no runtime ABI claim, no benchmark thresholds, no +cross-machine performance claims, no broad optimizer guarantees, no PDF +pipeline redesign, and no beta maturity are included. + +## exp-118 + +Release label: `exp-118` + +Release date: 2026-05-21 + +Release state: gated experimental string fixed-array compiler slice + +### Summary + +Glagol exp-118 gates one connected compiler broadening: fixed immutable arrays +now support `string` elements as a sibling lane beside the already promoted +direct scalar `i32`, `i64`, `f64`, and `bool` families. + +The release updates array checker admission, formatter array rendering, LLVM +array typing/load/store/index lowering, test-runner array values, diagnostics +coverage, the promoted `array-string` and `array-string-value-flow` fixtures +and lowering snapshots, focused array-string tests, the promotion-gate +inventory, and the Glagol docs for this feature. + +The earlier `array.slo`, `array-value-flow.slo`, `array-direct-scalars.slo`, +and `array-direct-scalars-value-flow.slo` fixtures remain valid as the +predecessor lanes. + +### Explicit Deferrals + +No array mutation, no array equality, no array printing, no nested arrays, no +arrays in struct fields, no zero-length arrays, no slices, no generics, no new +compiler-known runtime names, no stable ABI/layout guarantees, and no beta +maturity are included. + +## exp-117 + +Release label: `exp-117` + +Release date: 2026-05-21 + +Release state: gated experimental direct-scalar fixed-array compiler slice + +### Summary + +Glagol exp-117 gates one connected compiler broadening: fixed immutable arrays +now support the direct scalar element families `i32`, `i64`, `f64`, and `bool` +instead of only `i32`. + +The release updates array checker admission, formatter array rendering, LLVM +array typing/load/store/index lowering, test-runner array values, diagnostics +coverage, the promoted `array-direct-scalars` and +`array-direct-scalars-value-flow` fixtures and lowering snapshots, focused +array tests, the promotion-gate inventory, and the Glagol docs for this +feature. + +The earlier `array.slo` and `array-value-flow.slo` fixtures remain valid as +the narrower `i32` predecessor lane. + +### Explicit Deferrals + +No string arrays, no array mutation, no array equality, no array printing, no +nested arrays, no arrays in struct fields, no zero-length arrays, no slices, +no generics, no new compiler-known runtime names, no stable ABI/layout +guarantees, and no beta maturity are included. + +## exp-116 + +Release label: `exp-116` + +Release date: 2026-05-21 + +Release state: gated experimental direct enum-payload compiler slice + +### Summary + +Glagol exp-116 gates one connected compiler broadening: user-defined enum +payload variants now support unary direct `i32`, `i64`, `f64`, `bool`, and +`string` payloads instead of unary `i32` only. + +The release keeps payloadless variants supported, keeps exactly one payload +per payload variant, and adds one conservative same-payload-kind-per-enum +rule whenever payload variants appear. That constraint keeps each enum on one +concrete LLVM payload layout without inventing generic sum-layout machinery. + +The release updates the enum checker path, formatter enum rendering, LLVM enum +layout/equality/match lowering, test-runner enum payload support, +project/workspace enum import coverage, diagnostics coverage, the promoted +`enum-payload-direct-scalars` fixture and lowering snapshots, focused +enum-payload tests, the promotion-gate inventory, and the Glagol docs for +this feature. + +### Explicit Deferrals + +No vec/array/option/result/struct/enum payloads, no mixed payload kinds +inside one enum, no generics, no payload mutation, no new parser forms, no +new typed-AST constructs, no new compiler-known runtime names, no stable +ABI/layout guarantees, and no beta maturity are included. + +## exp-115 + +Release label: `exp-115` + +Release date: 2026-05-21 + +Release state: gated experimental composite-struct-field compiler slice + +### Summary + +Glagol exp-115 gates one connected compiler broadening: direct struct fields +now support the current concrete vec families, current concrete option/result +families, and current non-recursive struct types alongside the already +promoted direct scalar, string, and enum field families. + +The release updates the struct-field checker path, recursive-layout rejection, +formatter struct-field rendering, diagnostics coverage, the promoted +`composite-struct-fields` fixture and lowering snapshots, focused +composite-struct tests, the promotion-gate inventory, and the Glagol docs for +this feature. + +Lowering and typed AST forms do not widen: direct struct declarations, +constructors, field access, and current composite value families already +existed, so the release keeps the delta in checker/formatter/test/docs rather +than introducing new syntax or runtime names. + +### Explicit Deferrals + +No arrays as struct fields, no field mutation, no vector element mutation, no +option/result payload mutation, no recursive or cyclic struct layouts, no new +parser forms, no new typed-AST constructs, no new compiler-known runtime +names, no stable ABI/layout guarantees, and no beta maturity are included. + +## exp-113 + +Release label: `exp-113` + +Release date: 2026-05-21 + +Release state: gated experimental mutable scalar-local compiler slice + +### Summary + +Glagol exp-113 gates one narrow compiler broadening: mutable `bool`, `i64`, +and `f64` locals now work through `var` and `set`. + +The release updates the local-type checker path, formatter local-type +messaging, LLVM local emission, diagnostics coverage, the promoted +local-variable fixture and lowering snapshots, focused mutable-scalar tests, +the promotion-gate inventory, and the Glagol docs for this feature. + +Lowering and typed AST forms do not widen: `lower_type`, `var`, and `set` +already existed, so the release keeps the delta in checker/backend/test/docs +rather than introducing new syntax or runtime names. + +### Explicit Deferrals + +No mutable `string`, vector, option/result, struct, or enum locals, no new +parser forms, no new typed-AST constructs, no new compiler-known runtime +names, no stable ABI/layout guarantees, and no beta maturity are included. + +## exp-114 + +Release label: `exp-114` + +Release date: 2026-05-21 + +Release state: gated experimental mutable composite-local compiler slice + +### Summary + +Glagol exp-114 gates one narrow compiler broadening: same-type mutable local +reassignment now works through `var` and `set` for `string`, the current +concrete vec families, the current concrete option/result families, known +struct values, and current enum values. + +The release updates the local-type checker path, mutable assignment gate, +formatter local rendering, LLVM local emission, diagnostics coverage, the +promoted `composite-locals` fixture and lowering snapshots, focused +mutable-composite tests, the promotion-gate inventory, and the Glagol docs +for this feature. + +Lowering and typed AST forms do not widen: `lower_type`, `var`, `set`, and +`match` already existed, so the release keeps the delta in +checker/backend/test/docs rather than introducing new syntax or runtime +names. + +### Explicit Deferrals + +No mutable arrays, no field/element/payload mutation, no mutable generics or +new value families, no new parser forms, no new typed-AST constructs, no new +compiler-known runtime names, no stable ABI/layout guarantees, and no beta +maturity are included. + +## exp-112 + +Release label: `exp-112` + +Release date: 2026-05-21 + +Release state: gated experimental immutable bool-local compiler slice + +### Summary + +Glagol exp-112 gates one narrow compiler broadening: immutable `bool` locals +now work in `let` bindings. + +The release updates the local-type checker path, LLVM local emission, +diagnostics coverage, one focused bool-local integration test, the +promotion-gate diagnostic inventory, and the Glagol docs for this feature. + +Lowering and typed AST forms do not widen: `lower_type` already accepted +`bool`, and the release keeps the feature limited to immutable `let` +bindings only. + +### Explicit Deferrals + +No mutable `bool` locals, no new parser forms, no new compiler-known +runtime names, no broader local-mutation semantics, no stable ABI/layout +guarantees, and no beta maturity are included. + +## exp-111 + +Release label: `exp-111` + +Release date: 2026-05-21 + +Release state: gated experimental io stdin-helper stdlib gate + +### Summary + +Glagol exp-111 gates one connected broadening of the existing `std.io` +helper lane. + +The release keeps the already promoted compiler/runtime semantics unchanged +and instead broadens the existing local and explicit-source `std.io` +fixtures, the focused source-helper/source-search tests, and the +promotion-gate inventory with: + +- `read_stdin_result` +- `read_stdin_option` +- `read_stdin_or` +- `read_stdin_i32_result` +- `read_stdin_i32_option` +- `read_stdin_i32_or_zero` +- `read_stdin_i32_or` +- `read_stdin_i64_result` +- `read_stdin_i64_option` +- `read_stdin_i64_or_zero` +- `read_stdin_i64_or` +- `read_stdin_f64_result` +- `read_stdin_f64_option` +- `read_stdin_f64_or_zero` +- `read_stdin_f64_or` +- `read_stdin_bool_result` +- `read_stdin_bool_option` +- `read_stdin_bool_or_false` +- `read_stdin_bool_or` + +The helper lane stays source-authored over the already promoted +`std.io.read_stdin_result`, the released `std.string.parse_*_result` +helpers, and the exp-109 `ok_or_none_*` bridge helpers. The Glagol local +fixture adds explicit local `result.slo` and `string.slo` bridge modules so +the widened io facade remains ordinary source composition rather than new +compiler-known runtime names. + +### Explicit Deferrals + +No parser/checker/backend/runtime widening, no new compiler-known +`std.*` runtime names, no trap-based `std.io.read_stdin`, no line/read-line +or prompt APIs, no terminal mode, no binary/streaming/async stdin, no +automatic std imports, no stable ABI/layout guarantees, and no beta maturity +are included. + +## exp-110 + +Release label: `exp-110` + +Release date: 2026-05-21 + +Release state: gated experimental string/env/fs/process/cli option-helper stdlib gate + +### Summary + +Glagol exp-110 gates one connected broadening of the existing `std.string`, +`std.env`, `std.fs`, `std.process`, and `std.cli` helper lanes. + +The release keeps the already promoted compiler/runtime semantics unchanged +and instead broadens the existing local and explicit-source fixtures, the +focused source-helper/source-search tests, and the promotion-gate inventory +with: + +- `parse_i32_option` +- `parse_i64_option` +- `parse_f64_option` +- `parse_bool_option` +- `get_option` +- `get_i32_option` +- `get_i64_option` +- `get_f64_option` +- `get_bool_option` +- `read_text_option` +- `read_i32_option` +- `read_i64_option` +- `read_f64_option` +- `read_bool_option` +- `arg_option` +- `arg_i32_option` +- `arg_i64_option` +- `arg_f64_option` +- `arg_bool_option` +- `arg_text_option` +- `arg_i32_option` +- `arg_i64_option` +- `arg_f64_option` +- `arg_bool_option` + +The helper lanes stay source-authored over the already promoted concrete +result families plus the exp-109 `ok_or_none_*` bridge helpers. The Glagol +local fixtures add explicit local `result.slo` bridge modules so the option +helpers remain ordinary source composition rather than new compiler-known +runtime names. + +### Explicit Deferrals + +No parser/checker/backend/runtime widening, no new compiler-known +`std.*` runtime names, no stdin helpers, no vec work, no automatic std +imports, no stable ABI/layout guarantees, and no beta maturity are included. + +## exp-109 + +Release label: `exp-109` + +Release date: 2026-05-21 + +Release state: gated experimental option/result bridge stdlib gate + +### Summary + +Glagol exp-109 gates one connected broadening of the existing `std.option` +and `std.result` helper lanes. + +The release keeps the already promoted concrete option/result semantics +unchanged and instead broadens the local `std-layout-local-option/` and +`std-layout-local-result/` fixtures, the explicit `std-import-option/` and +`std-import-result/` fixtures, the workspace `std-import-option/` fixture, +the focused source-helper/source-search tests, and the promotion-gate +inventory with: + +- `some_or_err_i32` +- `some_or_err_i64` +- `some_or_err_f64` +- `some_or_err_bool` +- `some_or_err_string` +- `ok_or_none_i32` +- `ok_or_none_i64` +- `ok_or_none_string` +- `ok_or_none_f64` +- `ok_or_none_bool` + +The helper lanes stay concrete and source-authored over the already promoted +option/result forms. `std.option` uses raw `ok` and `err` constructors +directly, and `std.result` uses raw `some` and `none` constructors directly, +without introducing local bridge shim modules, new compiler-known +`std.option.*` or `std.result.*` runtime names, or compiler/runtime +widening. + +### Explicit Deferrals + +No new compiler-known `std.*` runtime names, no generics, no broader +`map`/`and_then`/`transpose`/`flatten` package, no new payload families, no +automatic or compiler-loaded std imports, no stable ABI/layout guarantees, +and no beta maturity are included. + +## exp-108 + +Release label: `exp-108` + +Release date: 2026-05-21 + +Release state: gated experimental vec-string/vec-f64/vec-bool prefix/suffix stdlib gate + +### Summary + +Glagol exp-108 gates one connected broadening of the existing vec-string, +vec-f64, and vec-bool helper lanes. + +The release keeps the already promoted concrete `(vec string)`, `(vec f64)`, +and `(vec bool)` compiler/runtime semantics unchanged and instead broadens +the local `std-layout-local-vec_string/`, `std-layout-local-vec_f64/`, and +`std-layout-local-vec_bool/` fixtures, the explicit `std-import-vec_string/`, +`std-import-vec_f64/`, and `std-import-vec_bool/` fixtures, the focused +source-helper/source-search tests, and the promotion-gate inventory with: + +- `starts_with` +- `without_prefix` +- `ends_with` +- `without_suffix` + +The helper lanes stay recursive and immutable with no `var` or `set`, stay +concrete to the three existing vec families, and reuse only the already +promoted runtime names: + +- `std.vec.string.empty` +- `std.vec.string.append` +- `std.vec.string.len` +- `std.vec.string.index` +- `std.vec.f64.empty` +- `std.vec.f64.append` +- `std.vec.f64.len` +- `std.vec.f64.index` +- `std.vec.bool.empty` +- `std.vec.bool.append` +- `std.vec.bool.len` +- `std.vec.bool.index` + +### Explicit Deferrals + +No new compiler-known `std.*` runtime names, no generics, no sorting, +mapping, filtering, no nested/container vecs, no mutating or capacity APIs, +no stable ABI/layout guarantees, and no beta maturity are included. + +## exp-107 + +Release label: `exp-107` + +Release date: 2026-05-21 + +Release state: gated experimental vec-string/vec-f64/vec-bool edit-helper stdlib gate + +### Summary + +Glagol exp-107 gates one connected broadening of the existing vec-string, +vec-f64, and vec-bool helper lanes. + +The release keeps the already promoted concrete `(vec string)`, `(vec f64)`, +and `(vec bool)` compiler/runtime semantics unchanged and instead broadens +the local `std-layout-local-vec_string/`, `std-layout-local-vec_f64/`, and +`std-layout-local-vec_bool/` fixtures, the explicit `std-import-vec_string/`, +`std-import-vec_f64/`, and `std-import-vec_bool/` fixtures, the focused +source-helper/source-search tests, and the promotion-gate inventory with: + +- `insert_at` +- `insert_range` +- `replace_at` +- `replace_range` +- `remove_at` +- `remove_range` + +The helper lanes stay recursive and immutable with no `var` or `set`, stay +concrete to the three existing vec families, and reuse only the already +promoted runtime names: + +- `std.vec.string.empty` +- `std.vec.string.append` +- `std.vec.string.len` +- `std.vec.string.index` +- `std.vec.f64.empty` +- `std.vec.f64.append` +- `std.vec.f64.len` +- `std.vec.f64.index` +- `std.vec.bool.empty` +- `std.vec.bool.append` +- `std.vec.bool.len` +- `std.vec.bool.index` + +### Explicit Deferrals + +No new compiler-known `std.*` runtime names, no generics, no prefix/suffix +helpers, no sorting/mapping/filtering, no nested/container vecs, no +mutating or capacity APIs, no stable ABI/layout guarantees, and no beta +maturity are included. + +## exp-106 + +Release label: `exp-106` + +Release date: 2026-05-21 + +Release state: gated experimental vec-f64/vec-bool option-query stdlib gate + +### Summary + +Glagol exp-106 gates one connected broadening of the existing vec-f64 and +vec-bool helper lanes. + +The release keeps the already promoted concrete `(vec f64)` and `(vec bool)` +compiler/runtime semantics unchanged and instead broadens the local +`std-layout-local-vec_f64/` and `std-layout-local-vec_bool/` fixtures, the +explicit `std-import-vec_f64/` and `std-import-vec_bool/` fixtures, the +focused source-helper/source-search tests, and the promotion-gate inventory +with: + +- `index_option` +- `first_option` +- `last_option` +- `index_of_option` +- `last_index_of_option` + +The helper lanes stay recursive and immutable with no `var` or `set`, stay +concrete to `(vec f64)` and `(vec bool)` plus the already promoted concrete +option families they return, and reuse only the already promoted runtime +names: + +- `std.vec.f64.empty` +- `std.vec.f64.append` +- `std.vec.f64.len` +- `std.vec.f64.index` +- `std.vec.bool.empty` +- `std.vec.bool.append` +- `std.vec.bool.len` +- `std.vec.bool.index` + +### Explicit Deferrals + +No new compiler-known `std.*` runtime names, no generics, no edit helpers, +no nested/container vecs, no mutating or capacity APIs, no stable +ABI/layout guarantees, and no beta maturity are included. + +## exp-105 + +Release label: `exp-105` + +Release date: 2026-05-21 + +Release state: gated experimental vec-f64/vec-bool transform-helper stdlib gate + +### Summary + +Glagol exp-105 gates one connected broadening of the existing vec-f64 and +vec-bool helper lanes. + +The release keeps the already promoted concrete `(vec f64)` and `(vec bool)` +compiler/runtime semantics unchanged and instead broadens the local +`std-layout-local-vec_f64/` and `std-layout-local-vec_bool/` fixtures, the +explicit `std-import-vec_f64/` and `std-import-vec_bool/` fixtures, the +focused source-helper/source-search tests, and the promotion-gate inventory +with: + +- `concat` +- `take` +- `drop` +- `reverse` +- `subvec` + +The helper lanes stay recursive and immutable with no `var` or `set`, stay +concrete to `(vec f64)` and `(vec bool)` only, and reuse only the already +promoted runtime names: + +- `std.vec.f64.empty` +- `std.vec.f64.append` +- `std.vec.f64.len` +- `std.vec.f64.index` +- `std.vec.bool.empty` +- `std.vec.bool.append` +- `std.vec.bool.len` +- `std.vec.bool.index` + +### Explicit Deferrals + +No new compiler-known `std.*` runtime names, no generics, no option/result +helpers, no edit helpers, no nested/container vecs, no mutating or capacity +APIs, no stable ABI/layout guarantees, and no beta maturity are included. + +## exp-104 + +Release label: `exp-104` + +Release date: 2026-05-21 + +Release state: gated experimental vec-bool baseline compiler/runtime plus stdlib gate + +### Summary + +Glagol exp-104 gates the first concrete `(vec bool)` slice. + +The release extends the checker, formatter, LLVM backend, runtime, std +runtime inventory, and test runner so immutable `(vec bool)` params, +returns, locals, direct `std.vec.bool.empty/append/len/index` calls, and +same-family vec-bool equality work end to end. It also adds the small scalar +bool-equality enablement required by the source-authored helper lane. The +release adds the local `std-layout-local-vec_bool/` fixture, the explicit +`std-import-vec_bool/` fixture, focused vec-bool collections/source helper/ +source search coverage, diagnostics snapshot refreshes, and promotion-gate +inventory for the connected `std.vec_bool` helper inventory: + +- `empty` +- `append` +- `len` +- `at` +- `singleton` +- `append2` +- `append3` +- `pair` +- `triple` +- `is_empty` +- `index_or` +- `first_or` +- `last_or` +- `contains` +- `count_of` + +The source helpers stay recursive and immutable with no `var` or `set`, stay +concrete to `(vec bool)` only, and reuse only the already promoted +`std.vec.bool.empty`, `std.vec.bool.append`, `std.vec.bool.len`, and +`std.vec.bool.index` runtime names. + +### Explicit Deferrals + +No new compiler-known `std.*` runtime names beyond the vec-bool family, no +generics, no option-query helpers, no transform/edit helpers, no nested vecs +or vecs inside other containers, no mutating or capacity APIs, no stable +ABI/layout guarantees, and no beta maturity are included. + +## exp-103 + +Release label: `exp-103` + +Release date: 2026-05-21 + +Release state: gated experimental vec-f64 baseline compiler/runtime plus stdlib gate + +### Summary + +Glagol exp-103 gates the first concrete `(vec f64)` slice. + +The release extends the checker, formatter, LLVM backend, runtime, std +runtime inventory, and test runner so immutable `(vec f64)` params, returns, +locals, direct `std.vec.f64.empty/append/len/index` calls, and same-family +vec-f64 equality work end to end. It also adds the local +`std-layout-local-vec_f64/` fixture, the explicit `std-import-vec_f64/` +fixture, focused vec-f64 diagnostics and promotion-gate coverage, and the +connected `std.vec_f64` helper inventory: + +- `empty` +- `append` +- `len` +- `at` +- `singleton` +- `append2` +- `append3` +- `pair` +- `triple` +- `is_empty` +- `index_or` +- `first_or` +- `last_or` +- `contains` +- `sum` + +The source helpers stay recursive and immutable with no `var` or `set`, stay +concrete to `(vec f64)` only, and reuse only the already promoted +`std.vec.f64.empty`, `std.vec.f64.append`, `std.vec.f64.len`, and +`std.vec.f64.index` runtime names. + +### Explicit Deferrals + +No new compiler-known `std.*` runtime names beyond the vec-f64 family, no +generics, no option-query helpers, no transform/edit helpers, no nested vecs +or vecs inside other containers, no mutating or capacity APIs, no stable +ABI/layout guarantees, and no beta maturity are included. + +## exp-102 + +Release label: `exp-102` + +Release date: 2026-05-21 + +Release state: gated experimental option-bool-f64 baseline compiler/runtime plus stdlib gate + +### Summary + +Glagol exp-102 gates the concrete `(option f64)` and `(option bool)` slice. + +The release extends the checker, formatter, LLVM backend, and test runner so +immutable `(option f64)` and `(option bool)` params, returns, locals, direct +`some`/`none` construction, `is_some`, `is_none`, `unwrap_some`, and +source-level `match` payload binding work end to end. It also broadens the +local `std-layout-local-option/` fixture, the explicit `std-import-option/` +fixture, the workspace `std-import-option/` fixture, focused option-result +coverage, diagnostics snapshots, and promotion-gate inventory, and it stages +the connected `std.option` f64/bool helper inventory: + +- `some_f64` +- `none_f64` +- `is_some_f64` +- `is_none_f64` +- `unwrap_some_f64` +- `unwrap_or_f64` +- `some_bool` +- `none_bool` +- `is_some_bool` +- `is_none_bool` +- `unwrap_some_bool` +- `unwrap_or_bool` + +The source helpers stay explicit and concrete, and the compiler/runtime slice +stays concrete to `(option i32)`, `(option i64)`, `(option f64)`, +`(option bool)`, and `(option string)`. + +### Explicit Deferrals + +No compiler-known `std.option.*` runtime names, no generic option helpers, +no option payload families beyond `i32`, `i64`, `f64`, `bool`, and `string`, +no nested or container option support, no stable ABI/layout guarantees, and +no beta maturity are included. + +## exp-101 + +Release label: `exp-101` + +Release date: 2026-05-21 + +Release state: gated experimental vec-string option/transform stdlib gate + +### Summary + +Glagol exp-101 gates the broadened concrete `(vec string)` helper lane. + +The release updates the local `std-layout-local-vec_string/` fixture, the +explicit `std-import-vec_string/` fixture, focused vec-string source-helper +and source-search coverage, and promotion-gate inventory with one connected +helper package: + +- `index_option` +- `first_option` +- `last_option` +- `index_of_option` +- `last_index_of_option` +- `concat` +- `take` +- `drop` +- `reverse` +- `subvec` + +The source helpers stay recursive and immutable with no `var` or `set`, stay +concrete to `(vec string)` plus raw current `(option string)` / +`(option i32)` forms only, and reuse only the already promoted +`std.vec.string.empty`, `std.vec.string.append`, `std.vec.string.len`, and +`std.vec.string.index` runtime names. + +### Explicit Deferrals + +No new compiler-known `std.*` runtime names, no generics, no edit helpers, +no prefix/suffix helpers, no nested vecs or vecs inside other containers, no +mutating or capacity APIs, no stable ABI/layout guarantees, and no beta +maturity are included. + +## exp-100 + +Release label: `exp-100` + +Release date: 2026-05-21 + +Release state: gated experimental option-string baseline compiler/runtime plus stdlib gate + +### Summary + +Glagol exp-100 gates the first concrete `(option string)` slice. + +The release extends the checker, formatter, LLVM backend, and test runner so +immutable `(option string)` params, returns, locals, direct `some`/`none` +construction, `is_some`, `is_none`, `unwrap_some`, and source-level `match` +payload binding work end to end. It also broadens the local +`std-layout-local-option/` fixture, the explicit `std-import-option/` +fixture, the workspace `std-import-option/` fixture, focused option/result +coverage, and promotion-gate inventory, and it stages the connected +`std.option` string helper inventory: + +- `some_string` +- `none_string` +- `is_some_string` +- `is_none_string` +- `unwrap_some_string` +- `unwrap_or_string` + +The source helpers stay explicit and concrete, and the compiler/runtime slice +stays concrete to `(option i32)`, `(option i64)`, and `(option string)`. + +### Explicit Deferrals + +No compiler-known `std.option.*` runtime names, no generic option helpers, +no option payload families beyond `i32`, `i64`, and `string`, no nested or +container option support, no stable ABI/layout guarantees, and no beta +maturity are included. + +## exp-99 + +Release label: `exp-99` + +Release date: 2026-05-21 + +Release state: gated experimental vec-string baseline compiler/runtime plus stdlib gate + +### Summary + +Glagol exp-99 gates the first concrete `(vec string)` slice. + +The release extends the checker, formatter, LLVM backend, runtime, std +runtime inventory, and test runner so immutable `(vec string)` params, +returns, locals, direct `std.vec.string.empty`, `std.vec.string.append`, +`std.vec.string.len`, `std.vec.string.index`, and concrete vec-string +equality work end to end. It also adds `examples/vec-string.slo`, adds the +local `std-layout-local-vec_string/` fixture, adds the explicit +`std-import-vec_string/` fixture, adds focused diagnostics and promotion-gate +coverage, and stages the frozen `std.vec_string` helper inventory: + +- `empty` +- `append` +- `len` +- `at` +- `singleton` +- `append2` +- `append3` +- `pair` +- `triple` +- `is_empty` +- `index_or` +- `first_or` +- `last_or` +- `contains` +- `count_of` + +The source helpers stay recursive and immutable with no `var` or `set`, use +only the promoted `std.vec.string` runtime names, and keep real-program +coverage focused on `contains` and `count_of`. + +In Glagol-only repo states, the explicit-source `std-import-vec_string/` +fixture now remains authored and format-checked even if sibling +`lib/std/vec_string.slo` has not landed yet; the full check/test lane for +that fixture still depends on the paired Slovo source file. + +### Explicit Deferrals + +No generics, no other vec element families, no option-query helpers, no +transform/range/edit helpers, no nested vecs or vecs inside other +containers, no mutating or capacity APIs, no stable ABI/layout guarantees, +and no beta maturity are included. + +## exp-98 + +Release label: `exp-98` + +Release date: 2026-05-21 + +Release state: gated experimental vec-i64-edit-helper stdlib gate + +### Summary + +Glagol exp-98 gates the concrete edit-helper extension for the staged +`std.vec_i64` source-authored facade. + +The release updates the local `std-layout-local-vec_i64/` fixture, updates +the explicit `std-import-vec_i64/` fixture against repo-root +`lib/std/vec_i64.slo`, adds focused integration coverage, and extends +promotion-gate inventory and sibling Slovo alignment checks for these +helpers: + +- `insert_at` +- `insert_range` +- `replace_at` +- `replace_range` +- `remove_at` +- `remove_range` + +The helpers reuse only the existing `std.vec.i64` runtime family plus the +already promoted `(option i32)` and `(option i64)` surface. +`insert_at(values,position,value)` inserts before `position`, appends when +`position == len(values)`, and otherwise leaves out-of-range inputs unchanged. +`insert_range(values,position,inserted)` follows the same position rules while +preserving left-to-right order of both vectors. `replace_at` and +`remove_at` leave negative and out-of-range positions unchanged. The range +helpers leave `values` unchanged for negative starts, degenerate or reversed +ranges, and `start >= len(values)`; overlong `end_exclusive` values edit +through the tail; and valid edits preserve element order while leaving source +vectors unchanged. The local Glagol-owned fixture stays source-authored, +recursive, and immutable without `var` or `set`, and the release adds no new +parser, checker, runtime, formatter, or LLVM semantics. + +### Explicit Deferrals + +No generic vectors, no new compiler-known `std.*` runtime names, no automatic +standard-library imports, no compiler-loaded std source, no mutable vector +APIs, no capacity/reserve management, no iterators, sorting, mapping, +filtering, stable ABI/layout guarantees, or beta maturity are included. + +## exp-97 + +Release label: `exp-97` + +Release date: 2026-05-21 + +Release state: gated experimental vec-i64-transform-helper stdlib gate + +### Summary + +Glagol exp-97 gates the concrete transform-helper extension for the staged +`std.vec_i64` source-authored facade. + +The release updates the local `std-layout-local-vec_i64/` fixture, updates +the explicit `std-import-vec_i64/` fixture against repo-root +`lib/std/vec_i64.slo`, adds focused integration coverage, and extends +promotion-gate inventory and sibling Slovo alignment checks for these +helpers: + +- `concat` +- `take` +- `drop` +- `reverse` +- `subvec` + +The helpers reuse only the existing `std.vec.i64` runtime family plus the +already promoted `(option i32)` and `(option i64)` surface. `concat(left,right)` +preserves left-to-right element order, `take(values,count)` treats negative +counts as zero and saturates to the full vector, `drop(values,count)` treats +negative counts as zero and saturates to the empty vector, `reverse(values)` +returns a new vector in reverse order, and `subvec(values,start,end_exclusive)` +returns the bounded middle slice while using the same concrete vec_i64 lane. +The local Glagol-owned fixture stays source-authored, recursive, and +immutable without `var` or `set`, and the release adds no new parser, +checker, runtime, formatter, or LLVM semantics. + +### Explicit Deferrals + +No vec_i64 insert/replace/remove helpers, no generics, no new compiler-known +`std.*` runtime names, no mutating vector or capacity APIs, no iterators, +sorting, mapping, filtering, stable ABI/layout guarantees, or beta maturity +are included. + +## exp-96 + +Release label: `exp-96` + +Release date: 2026-05-20 + +Release state: gated experimental vec-i64-option-query-helper stdlib gate + +### Summary + +Glagol exp-96 gates the concrete option-query helper extension for the staged +`std.vec_i64` source-authored facade. + +The release updates the local `std-layout-local-vec_i64/` fixture, updates +the explicit `std-import-vec_i64/` fixture against repo-root +`lib/std/vec_i64.slo`, adds focused integration coverage, and extends +promotion-gate inventory and sibling Slovo alignment checks for these +helpers: + +- `index_option` +- `first_option` +- `last_option` +- `index_of_option` +- `last_index_of_option` + +The helpers reuse only the existing `std.vec.i64` runtime family plus the +already promoted `(option i32)` and `(option i64)` surface. +`index_option(values,position)`, `first_option(values)`, and +`last_option(values)` return concrete `(option i64)` observations of vec +elements; `index_of_option(values,target)` and +`last_index_of_option(values,target)` return concrete `(option i32)` position +results. The local Glagol-owned fixture stays source-authored, recursive, and +immutable without `var` or `set`, and the release adds no new parser, +checker, runtime, formatter, or LLVM semantics. + +### Explicit Deferrals + +No vec_i64 transform/range/edit helpers, no generics, no new compiler-known +`std.*` runtime names, no mutating vector or capacity APIs, no iterators, +sorting, mapping, filtering, stable ABI/layout guarantees, or beta maturity +are included. + +## exp-95 + +Release label: `exp-95` + +Release date: 2026-05-20 + +Release state: gated experimental option-i64-baseline compiler/runtime and stdlib gate + +### Summary + +Glagol exp-95 gates the first concrete `(option i64)` baseline across the +compiler, local source fixtures, and explicit-source `std.option` import path. + +The release broadens the checker, formatter, LLVM backend, and test runner so +immutable `(option i64)` params, returns, locals, `some`, `none`, `is_some`, +`is_none`, `unwrap_some`, and source-level `match` payload bindings work end +to end. It adds the local `std-layout-local-option/` i64 helper coverage, +widens the explicit `std-import-option/` project against repo-root +`lib/std/option.slo`, extends diagnostics snapshots and promotion-gate +inventory, and broadens the staged helper surface with: + +- `some_i64` +- `none_i64` +- `is_some_i64` +- `is_none_i64` +- `unwrap_some_i64` +- `unwrap_or_i64` + +### Explicit Deferrals + +No compiler-known `std.option.*` runtime names, no generic option helpers, no +option payload families beyond `i32` and `i64`, no nested/container option +support, no option equality or printing, no stable ABI/layout guarantee, no +manifest schema changes, and no beta maturity are included. + +## exp-94 + +Release label: `exp-94` + +Release date: 2026-05-20 + +Release state: gated experimental vec-i64-baseline compiler/runtime and stdlib gate + +### Summary + +Glagol exp-94 gates the concrete `(vec i64)` runtime and compiler surface plus +the first local and explicit-source `std.vec_i64` facade baseline. + +The release promotes `std.vec.i64.empty`, `std.vec.i64.append`, +`std.vec.i64.len`, and `std.vec.i64.index`; adds `(vec i64)` equality; +extends the checker, formatter, lowering, LLVM backend, runtime, test runner, +and runtime catalog for concrete `(vec i64)` values; adds the local +`std-layout-local-vec_i64/` fixture; adds the explicit `std-import-vec_i64/` +fixture against repo-root `lib/std/vec_i64.slo`; adds focused integration +coverage; and extends +promotion-gate inventory for this connected baseline: + +- `empty` +- `append` +- `len` +- `at` +- `singleton` +- `append2` +- `append3` +- `pair` +- `triple` +- `is_empty` +- `index_or` +- `first_or` +- `last_or` +- `contains` +- `sum` + +The source surface stays concrete to `(vec i64)` and immutable. It reuses only +the promoted `std.vec.i64` runtime names, leaves source vectors unchanged, and +does not widen option/result payload families or introduce generic vector +semantics. + +### Explicit Deferrals + +No generic vectors, no vector payload families beyond the concrete `(vec i32)` +and `(vec i64)` slices, no automatic standard-library imports, no edits under +`slovo/**`, no compiler-loaded std source beyond the existing explicit search +rules, no new compiler-known `std.*` runtime names beyond `std.vec.i64.*`, no +slice or view types, no mutable vector locals, no mutating vector APIs, no +option/result payload widening, no vector families for `f64`, `string`, or +`bool`, no capacity or reserve management, no sorting, mapping, filtering, or +iterator APIs, no stable ABI/layout guarantees, and no beta maturity are +included. + +## exp-93 + +Release label: `exp-93` + +Release date: 2026-05-20 + +Release state: gated experimental vec-i32-count-of-helper stdlib gate + +### Summary + +Glagol exp-93 gates the concrete count-of helper extension for the staged +`std.vec_i32` source-authored facade. + +The release updates the explicit `std-import-vec_i32/` project, updates the +local source-authored `std-layout-local-vec_i32/` fixture, adds focused +integration coverage, and extends promotion-gate inventory and sibling Slovo +alignment checks for this helper: + +- `count_of` + +The helper is an ordinary source-authored composition over the existing +promoted `std.vec.i32` runtime family and the already staged `std.vec_i32` +wrappers for `len` and `at`, plus equality and `while`. +`count_of(values,target)` returns the number of elements of `values` equal to +`target`; returns `0` for empty and no-match cases; counts repeated matches +exactly; leaves the source vector unchanged; and adds no new parser, +checker, runtime, formatter, or LLVM semantics for this release. + +### Explicit Deferrals + +No generic vectors, no vector payload families beyond `(vec i32)`, no +automatic standard-library imports, no compiler-loaded std source, no new +compiler-known `std.*` runtime names, no new private recursive helper loops, +no broader aggregation/filter families beyond the current concrete vec +surface, no slice or view types, no mutable `(vec i32)` local support, no +capacity or reserve management, no sorting, mapping, filtering, or iterator +APIs, no stable ABI/layout guarantees, and no beta maturity are included. + +## exp-92 + +Release label: `exp-92` + +Release date: 2026-05-20 + +Release state: gated experimental vec-i32-without-prefix-helper stdlib gate + +### Summary + +Glagol exp-92 gates the concrete without-prefix helper extension for the +staged `std.vec_i32` source-authored facade. + +The release updates the explicit `std-import-vec_i32/` project, updates the +local source-authored `std-layout-local-vec_i32/` fixture, adds focused +integration coverage, and extends promotion-gate inventory and sibling Slovo +alignment checks for this helper: + +- `without_prefix` + +The helper is an ordinary source-authored composition over the existing +promoted `std.vec.i32` runtime family and the already staged `std.vec_i32` +wrappers for `empty`, `append`, `len`, and `at`, plus the already staged +source helpers `starts_with`, `drop`, and `len`. +`without_prefix(values,prefix)` removes a matching leading prefix from +`values`; returns `values` unchanged for empty, longer, or mismatched +prefixes; returns `(empty)` for an exact match; preserves the order of the +remaining suffix; leaves both source vectors unchanged; and adds no new +parser, checker, runtime, formatter, or LLVM semantics for this release. + +### Explicit Deferrals + +No generic vectors, no vector payload families beyond `(vec i32)`, no +automatic standard-library imports, no compiler-loaded std source, no new +compiler-known `std.*` runtime names, no new private recursive helper loops, +no broader prefix/suffix helper families beyond the current `starts_with`, +`without_prefix`, `ends_with`, and `without_suffix` helpers, no slice or +view types, no mutable `(vec i32)` local support, no capacity or reserve +management, no sorting, mapping, filtering, or iterator APIs, no stable +ABI/layout guarantees, and no beta maturity are included. + +## exp-91 + +Release label: `exp-91` + +Release date: 2026-05-20 + +Release state: gated experimental vec-i32-without-suffix-helper stdlib gate + +### Summary + +Glagol exp-91 gates the concrete without-suffix helper extension for the +staged `std.vec_i32` source-authored facade. + +The release updates the explicit `std-import-vec_i32/` project, updates the +local source-authored `std-layout-local-vec_i32/` fixture, adds focused +integration coverage, and extends promotion-gate inventory and sibling Slovo +alignment checks for this helper: + +- `without_suffix` + +The helper is an ordinary source-authored composition over the existing +promoted `std.vec.i32` runtime family and the already staged `std.vec_i32` +wrappers for `empty`, `append`, `len`, and `at`, plus the already staged +source helpers `ends_with`, `take`, and `len`. +`without_suffix(values,suffix)` removes a matching trailing suffix from +`values`; returns `values` unchanged for empty, longer, or mismatched +suffixes; returns `(empty)` for an exact match; preserves the order of the +remaining prefix; leaves both source vectors unchanged; and adds no new +parser, checker, runtime, formatter, or LLVM semantics for this release. + +### Explicit Deferrals + +No generic vectors, no vector payload families beyond `(vec i32)`, no +automatic standard-library imports, no compiler-loaded std source, no new +compiler-known `std.*` runtime names, no new private recursive helper loops, +no broader prefix/suffix helper families beyond the current `starts_with`, +`ends_with`, and `without_suffix` helpers, no slice or view types, no +mutable `(vec i32)` local support, no capacity or reserve management, no +sorting, mapping, filtering, or iterator APIs, no stable ABI/layout +guarantees, and no beta maturity are included. + +## exp-90 + +Release label: `exp-90` + +Release date: 2026-05-20 + +Release state: gated experimental vec-i32-ends-with-helper stdlib gate + +### Summary + +Glagol exp-90 gates the concrete ends-with helper extension for the staged +`std.vec_i32` source-authored facade. + +The release updates the explicit `std-import-vec_i32/` project, updates the +local source-authored `std-layout-local-vec_i32/` fixture, adds focused +integration coverage, and extends promotion-gate inventory and sibling Slovo +alignment checks for this helper: + +- `ends_with` + +The helper is an ordinary source-authored composition over the existing +promoted `std.vec.i32` runtime family and the already staged `std.vec_i32` +wrappers for `empty`, `append`, `len`, and `at`, plus the already staged +source helpers `drop` and vec equality. +`ends_with(values,suffix)` returns `true` exactly when `values` ends with all +elements of `suffix` in order; returns `true` for an empty suffix; returns +`false` when `len(suffix) > len(values)`; returns `true` for equal vectors +and shorter matching suffixes; returns `false` for mismatched suffixes; +leaves both source vectors unchanged; and adds no new parser, checker, +runtime, formatter, or LLVM semantics for this release. + +### Explicit Deferrals + +No generic vectors, no vector payload families beyond `(vec i32)`, no +automatic standard-library imports, no compiler-loaded std source, no new +compiler-known `std.*` runtime names, no new private recursive helper loops, +no broader prefix/suffix query families beyond the current `starts_with` and +`ends_with` helpers, no slice or view types, no mutable `(vec i32)` local +support, no capacity or reserve management, no sorting, mapping, filtering, +or iterator APIs, no stable ABI/layout guarantees, and no beta maturity are +included. + +## exp-89 + +Release label: `exp-89` + +Release date: 2026-05-20 + +Release state: gated experimental vec-i32-starts-with-helper stdlib gate + +### Summary + +Glagol exp-89 gates the concrete starts-with helper extension for the staged +`std.vec_i32` source-authored facade. + +The release updates the explicit `std-import-vec_i32/` project, updates the +local source-authored `std-layout-local-vec_i32/` fixture, adds focused +integration coverage, and extends promotion-gate inventory and sibling Slovo +alignment checks for this helper: + +- `starts_with` + +The helper is an ordinary source-authored composition over the existing +promoted `std.vec.i32` runtime family and the already staged `std.vec_i32` +wrappers for `empty`, `append`, `len`, and `at`, plus the already staged +source helpers `take` and vec equality. +`starts_with(values,prefix)` returns `true` exactly when `values` begins +with all elements of `prefix` in order; returns `true` for an empty prefix; +returns `false` when `len(prefix) > len(values)`; returns `true` for equal +vectors and shorter matching prefixes; returns `false` for mismatched +prefixes; leaves both source vectors unchanged; and adds no new parser, +checker, runtime, formatter, or LLVM semantics for this release. + +### Explicit Deferrals + +No generic vectors, no vector payload families beyond `(vec i32)`, no +automatic standard-library imports, no compiler-loaded std source, no new +compiler-known `std.*` runtime names, no new private recursive helper loops, +no broader prefix/suffix query families beyond this single helper, no slice +or view types, no mutable `(vec i32)` local support, no capacity or reserve +management, no sorting, mapping, filtering, or iterator APIs, no stable +ABI/layout guarantees, and no beta maturity are included. + +## exp-88 + +Release label: `exp-88` + +Release date: 2026-05-20 + +Release state: gated experimental vec-i32-replace-range-helper stdlib gate + +### Summary + +Glagol exp-88 gates the concrete replace-range helper extension for the +staged `std.vec_i32` source-authored facade. + +The release updates the explicit `std-import-vec_i32/` project, updates the +local source-authored `std-layout-local-vec_i32/` fixture, adds focused +integration coverage, and extends promotion-gate inventory and sibling Slovo +alignment checks for this helper: + +- `replace_range` + +The helper is an ordinary source-authored composition over the existing +promoted `std.vec.i32` runtime family and the already staged `std.vec_i32` +wrappers for `empty`, `append`, `len`, and `at`, plus the already staged +source helpers `take`, `drop`, and `concat`. +`replace_range(values,start,end_exclusive,replacement)` returns `values` +unchanged when `start < 0`, when `end_exclusive <= start`, or when +`start >= len(values)`; replaces the half-open range +`[start, end_exclusive)` when both bounds are in range; replaces the tail +from `start` when `end_exclusive >= len(values)`; preserves the order of both +vectors; leaves both source vector values unchanged; and adds no new parser, +checker, runtime, formatter, or LLVM semantics for this release. + +### Explicit Deferrals + +No generic vectors, no vector payload families beyond `(vec i32)`, no +automatic standard-library imports, no compiler-loaded std source, no new +compiler-known `std.*` runtime names, no new private recursive helper loops, +no bulk-edit APIs beyond this single contiguous replacement helper, no slice +or view types, no mutable `(vec i32)` local support, no capacity or reserve +management, no sorting, mapping, filtering, or iterator APIs, no stable +ABI/layout guarantees, and no beta maturity are included. + +## exp-87 + +Release label: `exp-87` + +Release date: 2026-05-20 + +Release state: gated experimental vec-i32-insert-range-helper stdlib gate + +### Summary + +Glagol exp-87 gates the concrete insert-range helper extension for the staged +`std.vec_i32` source-authored facade. + +The release updates the explicit `std-import-vec_i32/` project, updates the +local source-authored `std-layout-local-vec_i32/` fixture, adds focused +integration coverage, and extends promotion-gate inventory and sibling Slovo +alignment checks for this helper: + +- `insert_range` + +The helper is an ordinary source-authored composition over the existing +promoted `std.vec.i32` runtime family and the already staged `std.vec_i32` +wrappers for `empty`, `append`, `len`, and `at`, plus the already staged +source helpers `take`, `drop`, and `concat`. +`insert_range(values,position,inserted)` returns `values` unchanged when +`position < 0` or `position > len(values)`; inserts `inserted` before the +current element when `0 <= position < len(values)`; appends `inserted` when +`position == len(values)`; preserves the order of both vectors; leaves both +source vector values unchanged; and adds no new parser, checker, runtime, +formatter, or LLVM semantics for this release. + +### Explicit Deferrals + +No generic vectors, no vector payload families beyond `(vec i32)`, no +automatic standard-library imports, no compiler-loaded std source, no new +compiler-known `std.*` runtime names, no new private recursive helper loops, +no bulk-edit APIs beyond this single contiguous range insertion helper, no +multi-position insertion helpers, no mutable `(vec i32)` local support, no +capacity or reserve management, no sorting, mapping, filtering, or iterator +APIs, no stable ABI/layout guarantees, and no beta maturity are included. + +## exp-86 + +Release label: `exp-86` + +Release date: 2026-05-20 + +Release state: gated experimental vec-i32-remove-range-helper stdlib gate + +### Summary + +Glagol exp-86 gates the concrete remove-range helper extension for the staged +`std.vec_i32` source-authored facade. + +The release updates the explicit `std-import-vec_i32/` project, updates the +local source-authored `std-layout-local-vec_i32/` fixture, adds focused +integration coverage, and extends promotion-gate inventory and sibling Slovo +alignment checks for this helper: + +- `remove_range` + +The helper is an ordinary source-authored composition over the existing +promoted `std.vec.i32` runtime family and the already staged `std.vec_i32` +wrappers for `empty`, `append`, `len`, and `at`, plus the already staged +source helpers `take`, `drop`, and `concat`. +`remove_range(values,start,end_exclusive)` returns `values` unchanged when +`start < 0`, when `end_exclusive <= start`, or when `start >= len(values)`; +returns the prefix before `start` when `end_exclusive >= len(values)`; +otherwise returns a new vector with the half-open range +`[start, end_exclusive)` removed, preserves the order of the remaining +elements, and leaves the source vector value unchanged. Glagol adds no new +parser, checker, runtime, formatter, or LLVM semantics for this release. + +### Explicit Deferrals + +No generic vectors, no vector payload families beyond `(vec i32)`, no +automatic standard-library imports, no compiler-loaded std source, no new +compiler-known `std.*` runtime names, no new private recursive helper loops, +no bulk-edit APIs beyond this single contiguous removal helper, no mutable +`(vec i32)` local support, no capacity or reserve management, no sorting, +mapping, filtering, or iterator APIs, no stable ABI/layout guarantees, and no +beta maturity are included. + +## exp-85 + +Release label: `exp-85` + +Release date: 2026-05-20 + +Release state: gated experimental vec-i32-subvec-helper stdlib gate + +### Summary + +Glagol exp-85 gates the concrete subvector-helper extension for the staged +`std.vec_i32` source-authored facade. + +The release updates the explicit `std-import-vec_i32/` project, updates the +local source-authored `std-layout-local-vec_i32/` fixture, adds focused +integration coverage, and extends promotion-gate inventory and sibling Slovo +alignment checks for this helper: + +- `subvec` + +The helper is an ordinary source-authored composition over the existing +promoted `std.vec.i32` runtime family and the already staged `std.vec_i32` +wrappers for `empty`, `append`, `len`, and `at`, plus the already staged +source helpers `drop` and `take`. `subvec(values,start,end_exclusive)` +returns the empty vector when `start < 0`, when `end_exclusive <= start`, or +when `start >= len(values)`; returns the remaining tail from `start` when +`end_exclusive > len(values)`; otherwise returns a copied contiguous +subvector `[start, end_exclusive)` with preserved left-to-right order; and +leaves the source vector value unchanged. Glagol adds no new parser, checker, +runtime, formatter, or LLVM semantics for this release. + +### Explicit Deferrals + +No generic vectors, no vector payload families beyond `(vec i32)`, no +automatic standard-library imports, no compiler-loaded std source, no new +compiler-known `std.*` runtime names, no new private recursive helper loops, +no slice or view types, no mutable `(vec i32)` local support, no capacity or +reserve management, no sorting, mapping, filtering, or iterator APIs, no +stable ABI/layout guarantees, and no beta maturity are included. + +## exp-84 + +Release label: `exp-84` + +Release date: 2026-05-20 + +Release state: gated experimental vec-i32-insert-helper stdlib gate + +### Summary + +Glagol exp-84 gates the concrete insert-helper extension for the staged +`std.vec_i32` source-authored facade. + +The release updates the explicit `std-import-vec_i32/` project, updates the +local source-authored `std-layout-local-vec_i32/` fixture, adds focused +integration coverage, and extends promotion-gate inventory and sibling Slovo +alignment checks for this helper: + +- `insert_at` + +The helper is an ordinary source-authored composition over the existing +promoted `std.vec.i32` runtime family and the already staged `std.vec_i32` +wrappers for `empty`, `append`, `len`, and `at`, plus the already staged +source helpers `take`, `drop`, and `concat`. +`insert_at(values,position,value)` returns `values` unchanged when +`position < 0` or `position > len(values)`; inserts `value` before the current +element when `0 <= position < len(values)`; appends `value` when +`position == len(values)`; preserves the surrounding order; increases the +result length by one for valid insertions; and leaves the source vector value +unchanged. Glagol adds no new parser, checker, runtime, formatter, or LLVM +semantics for this release. + +### Explicit Deferrals + +No generic vectors, no vector payload families beyond `(vec i32)`, no +automatic standard-library imports, no compiler-loaded std source, no new +compiler-known `std.*` runtime names, no new private recursive helper loops, +no bulk-edit APIs, no multi-index insertion, no mutable `(vec i32)` local +support, no capacity or reserve management, no sorting, mapping, filtering, +or iterator APIs, no stable ABI/layout guarantees, and no beta maturity are +included. + +## exp-83 + +Release label: `exp-83` + +Release date: 2026-05-20 + +Release state: gated experimental vec-i32-remove-helper stdlib gate + +### Summary + +Glagol exp-83 gates the concrete remove-helper extension for the staged +`std.vec_i32` source-authored facade. + +The release updates the explicit `std-import-vec_i32/` project, updates the +local source-authored `std-layout-local-vec_i32/` fixture, adds focused +integration coverage, and extends promotion-gate inventory and sibling Slovo +alignment checks for this helper: + +- `remove_at` + +The helper is an ordinary source-authored composition over the existing +promoted `std.vec.i32` runtime family and the already staged `std.vec_i32` +wrappers for `empty`, `append`, `len`, and `at`, plus the already staged +source helpers `take`, `drop`, and `concat`. `remove_at(values,position)` +returns `values` unchanged when `position < 0` or `position >= len(values)`; +otherwise it returns a new vector with the element at `position` removed, +preserves the order of the remaining elements, shortens the result length by +one, and leaves the source vector value unchanged. Glagol adds no new parser, +checker, runtime, formatter, or LLVM semantics for this release. + +### Explicit Deferrals + +No generic vectors, no vector payload families beyond `(vec i32)`, no +automatic standard-library imports, no compiler-loaded std source, no new +compiler-known `std.*` runtime names, no new private recursive helper loops, +no insert helpers, no bulk-edit APIs, no multi-index removal, no mutable +`(vec i32)` local support, no capacity or reserve management, no sorting, +mapping, filtering, or iterator APIs, no stable ABI/layout guarantees, and no +beta maturity are included. + +## exp-82 + +Release label: `exp-82` + +Release date: 2026-05-20 + +Release state: gated experimental vec-i32-replace-helper stdlib gate + +### Summary + +Glagol exp-82 gates the concrete replace-helper extension for the staged +`std.vec_i32` source-authored facade. + +The release updates the explicit `std-import-vec_i32/` project, updates the +local source-authored `std-layout-local-vec_i32/` fixture, adds focused +integration coverage, and extends promotion-gate inventory and sibling Slovo +alignment checks for this helper: + +- `replace_at` + +The helper is an ordinary source-authored composition over the existing +promoted `std.vec.i32` runtime family and the already staged `std.vec_i32` +wrappers for `empty`, `append`, `len`, and `at`, plus the already staged +source helpers `take`, `drop`, and `concat`. +`replace_at(values,position,replacement)` returns `values` unchanged when +`position < 0` or `position >= len(values)`; otherwise it returns a new vector +with the same length and order except for the replaced slot. Glagol adds no +new parser, checker, runtime, formatter, or LLVM semantics for this release. + +### Explicit Deferrals + +No generic vectors, no vector payload families beyond `(vec i32)`, no +automatic standard-library imports, no compiler-loaded std source, no new +compiler-known `std.*` runtime names, no private mutation loops, no insert or +remove helpers, no bulk-edit APIs, no mutable `(vec i32)` local support, no +capacity or reserve management, no sorting, mapping, filtering, or iterator +APIs, no stable ABI/layout guarantees, and no beta maturity are included. + +## exp-81 + +Release label: `exp-81` + +Release date: 2026-05-20 + +Release state: released experimental vec-i32-range-helper stdlib gate + +### Summary + +Glagol exp-81 gates the concrete range-helper extension for the staged +`std.vec_i32` source-authored facade. + +The release updates the explicit `std-import-vec_i32/` project, updates the +local source-authored `std-layout-local-vec_i32/` fixture, adds focused +integration coverage, and extends promotion-gate inventory and sibling Slovo +alignment checks for this helper: + +- `range` + +The helper is ordinary source-authored recursion over the existing promoted +`std.vec.i32` runtime family and the already staged `std.vec_i32` wrappers for +`empty`, `append`, `len`, and `at`. `range(start,end_exclusive)` generates the +ascending half-open sequence `[start, start + 1, ..., end_exclusive - 1]`, +returns the empty vector when `end_exclusive <= start`, and accepts negative +bounds. The recursive generation helper remains private to the fixture source. +Glagol adds no new parser, checker, runtime, formatter, or LLVM semantics for +this release. + +### Explicit Deferrals + +No generic vectors, no vector payload families beyond `(vec i32)`, no +automatic standard-library imports, no compiler-loaded std source, no new +compiler-known `std.*` runtime names, no edit helpers, no descending, stepped, +or inclusive range variants, no mutable `(vec i32)` local support, no capacity +or reserve management, no sorting, mapping, filtering, or iterator APIs, no +stable ABI/layout guarantees, and no beta maturity are included. + +## exp-80 + +Release label: `exp-80` + +Release date: 2026-05-20 + +Release state: released experimental vec-i32-generated-constructor-helper stdlib gate + +### Summary + +Glagol exp-80 gates the concrete generated-constructor-helper extension for +the staged `std.vec_i32` source-authored facade. + +The release updates the explicit `std-import-vec_i32/` project, updates the +local source-authored `std-layout-local-vec_i32/` fixture, adds focused +integration coverage, and extends promotion-gate inventory and sibling Slovo +alignment checks for these helpers: + +- `repeat` +- `range_from_zero` + +The helpers are ordinary source-authored recursion over the existing promoted +`std.vec.i32` runtime family and the already staged `std.vec_i32` wrappers for +`empty`, `append`, `len`, and `at`. `repeat(value,count)` returns the empty +vector for `count <= 0`, and `range_from_zero(count)` generates the ascending +half-open sequence `[0, 1, ..., count - 1]`, returning the empty vector for +`count <= 0`. The recursive generation helpers remain private to the fixture +source. Glagol adds no new parser, checker, runtime, formatter, or LLVM +semantics for this release. + +### Explicit Deferrals + +No generic vectors, no vector payload families beyond `(vec i32)`, no +automatic standard-library imports, no compiler-loaded std source, no new +compiler-known `std.*` runtime names, no edit helpers, no public `range` +helper, no descending, stepped, or inclusive range variants, no mutable +`(vec i32)` local support, no capacity or reserve management, no sorting, +mapping, filtering, or iterator APIs, no stable ABI/layout guarantees, and no +beta maturity are included. + +## exp-79 + +Release label: `exp-79` + +Release date: 2026-05-20 + +Release state: released experimental vec-i32-transform-helper stdlib gate + +### Summary + +Glagol exp-79 gates the concrete transform-helper extension for the staged +`std.vec_i32` source-authored facade. + +The release updates the explicit `std-import-vec_i32/` project, updates the +local source-authored `std-layout-local-vec_i32/` fixture, adds focused +integration coverage, and extends promotion-gate inventory and sibling Slovo +alignment checks for these helpers: + +- `concat` +- `take` +- `drop` +- `reverse` + +The helpers are ordinary source-authored recursion over the existing promoted +`std.vec.i32` runtime family and the already staged `std.vec_i32` wrappers for +`empty`, `append`, `len`, and `at`. Glagol adds no new parser, checker, +runtime, formatter, or LLVM semantics for this release. + +### Explicit Deferrals + +No generic vectors, no vector payload families beyond `(vec i32)`, no +automatic standard-library imports, no compiler-loaded std source, no new +compiler-known `std.*` runtime names, no mutable `(vec i32)` local support, no +capacity or reserve management, no sorting, mapping, filtering, or iterator +APIs, no stable ABI/layout guarantees, and no beta maturity are included. + +## exp-78 + +Release label: `exp-78` + +Release date: 2026-05-20 + +Release state: released experimental cli-local-source-facade stdlib gate + +### Summary + +Glagol exp-78 gates the Glagol-side local source-authored `cli` facade that +mirrors the existing staged `std.cli` helper surface without widening it. + +The release adds `examples/projects/std-layout-local-cli/` with explicit local +`cli.slo`, `process.slo`, and `string.slo` modules plus the matching `main.slo` +consumer, adds focused integration coverage, and extends promotion-gate +inventory/alignment checks for these helpers: + +- `arg_text_result` +- `arg_i32_result` +- `arg_i32_or_zero` +- `arg_i32_or` +- `arg_i64_result` +- `arg_i64_or_zero` +- `arg_i64_or` +- `arg_f64_result` +- `arg_f64_or_zero` +- `arg_f64_or` +- `arg_bool_result` +- `arg_bool_or_false` +- `arg_bool_or` + +The local fixture stays source-authored and explicit: `main.slo` imports local +`cli`, `cli.slo` imports local `process` and `string`, `process.slo` wraps only +the existing promoted `std.process.arg_result`, and `string.slo` wraps only the +existing promoted concrete parse-result helpers. Glagol adds no new parser, +checker, runtime, formatter, or LLVM semantics for this release. + +### Explicit Deferrals + +No widened `std.cli` API, no automatic standard-library imports, no compiler- +loaded std source, no automatic standard-library search semantics, no new +compiler-known `std.*` runtime names, no process spawning, exit/status control, +no current-directory APIs, no signal handling, no shell parsing, no +subcommands, no flag or option frameworks, no stable ABI/layout guarantees, +and no beta maturity are included. + +## exp-77 + +Release label: `exp-77` + +Release date: 2026-05-20 + +Release state: released experimental vec-i32-option-query-helper stdlib gate + +### Summary + +Glagol exp-77 extends the concrete `(vec i32)` source-authored collection +facade in the staged `std.vec_i32` module with option-returning query helpers. + +The release updates the explicit `std-import-vec_i32/` project, updates the +local source-authored `std-layout-local-vec_i32/` fixture, adds focused +integration coverage, and extends promotion-gate inventory and sibling Slovo +alignment checks for these helpers: + +- `index_option` +- `first_option` +- `last_option` +- `index_of_option` +- `last_index_of_option` + +The helpers are ordinary source wrappers and loops over the existing +promoted `std.vec.i32` runtime family plus the existing `(option i32)` +surface. Glagol adds no new parser, checker, runtime, or LLVM semantics for +this release. + +### Explicit Deferrals + +No generic vectors, no vector payload families beyond `(vec i32)`, no option +payload families beyond `(option i32)`, no automatic standard-library imports, +no compiler-loaded std source, no new compiler-known `std.*` runtime names, no +capacity or reserve management, no sorting, mapping, filtering, or iterator +APIs, no mutating collection APIs, no stable ABI/layout guarantees, and no +beta maturity are included. + +## exp-76 + +Release label: `exp-76` + +Release date: 2026-05-20 + +Release state: released experimental vec-i32-source-helper stdlib gate + +### Summary + +Glagol exp-76 gates a concrete `(vec i32)` source-authored collection facade +in the staged `std.vec_i32` module. + +The release adds the explicit `std-import-vec_i32/` project, adds the local +source-authored `std-layout-local-vec_i32/` fixture, adds focused +integration coverage, and extends promotion-gate inventory and sibling Slovo +alignment checks for these helpers: + +- `empty` +- `append` +- `len` +- `at` +- `singleton` +- `append2` +- `append3` +- `pair` +- `triple` +- `is_empty` +- `index_or` +- `first_or` +- `last_or` +- `contains` +- `sum` + +The helpers are ordinary source wrappers and loops over the existing +promoted `std.vec.i32` runtime family. Glagol adds no new parser, checker, +runtime, or LLVM semantics for this release. + +### Explicit Deferrals + +No generic vectors, no vector payload families beyond `(vec i32)`, no +automatic standard-library imports, no compiler-loaded std source, no new +compiler-known `std.*` runtime names, no capacity or reserve management, no +sorting, mapping, filtering, or iterator APIs, no stable ABI/layout +guarantees, and no beta maturity are included. + +## exp-75 + +Release label: `exp-75` + +Release date: 2026-05-20 + +Release state: released experimental option-constructor stdlib gate + +### Summary + +Glagol exp-75 gates source-authored concrete option constructors in the +staged `std.option` facade. + +The release expands the explicit `std-import-option/` project, updates the +local source-authored `std-layout-local-option/` fixture, and extends focused +integration coverage plus promotion-gate inventory/alignment checks for these +helpers: + +- `some_i32` +- `none_i32` + +The helpers are ordinary source wrappers over the existing `some` and `none` +forms for the currently promoted `(option i32)` family. Glagol adds no new +parser, checker, runtime, or LLVM semantics for this release. + +### Explicit Deferrals + +No generic option helpers, no new option payload families, no automatic +standard-library imports, no compiler-loaded std source, no new +compiler-known `std.*` runtime names, no stable ABI/layout guarantees, and no +beta maturity are included. + +## exp-74 + +Release label: `exp-74` + +Release date: 2026-05-20 + +Release state: released experimental result-constructor stdlib gate + +### Summary + +Glagol exp-74 gates source-authored concrete result constructors in the +staged `std.result` facade. + +The release expands the explicit `std-import-result/` project, updates the +local source-authored `std-layout-local-result/` fixture, and extends focused +integration coverage plus promotion-gate inventory/alignment checks for these +helpers: + +- `ok_i32` +- `err_i32` +- `ok_i64` +- `err_i64` +- `ok_string` +- `err_string` +- `ok_f64` +- `err_f64` +- `ok_bool` +- `err_bool` + +The helpers are ordinary source wrappers over the existing `ok` and `err` +forms for the currently promoted concrete result families. Glagol adds no new +parser, checker, runtime, or LLVM semantics for this release. + +### Explicit Deferrals + +No generic result helpers, no new result payload families, no richer error +ADTs, no automatic standard-library imports, no compiler-loaded std source, +no new compiler-known `std.*` runtime names, no stable ABI/layout +guarantees, and no beta maturity are included. + +## exp-73 + +Release label: `exp-73` + +Release date: 2026-05-20 + +Release state: released experimental io-value-helper stdlib gate + +### Summary + +Glagol exp-73 gates source-authored value-returning print helpers in the +staged `std.io` facade. + +The release expands the explicit `std-import-io/` project, adds the local +source-authored `std-layout-local-io/` fixture plus focused integration +coverage, and extends promotion-gate inventory/alignment checks for these +helpers: + +- `print_i32_value` +- `print_i64_value` +- `print_f64_value` +- `print_string_value` +- `print_bool_value` + +The helpers are ordinary source wrappers over the existing promoted print +runtime calls. Glagol adds no new parser, checker, runtime, or LLVM semantics +for this release. + +### Explicit Deferrals + +No automatic standard-library imports, no compiler-loaded std source, no +stderr facade helpers, no stdin facade helpers, no line iteration, no prompt +APIs, no terminal control, no binary or streaming IO, no async IO, no new +compiler-known `std.*` runtime names, no stable ABI/layout guarantees, and no +beta maturity are included. + +## exp-72 + +Release label: `exp-72` + +Release date: 2026-05-20 + +Release state: released experimental cli-custom-fallback stdlib gate + +### Summary + +Glagol exp-72 gates source-authored typed CLI argument custom-fallback +helpers in the staged `std.cli` facade. + +The release expands the explicit `std-import-cli/` project, adds focused +integration coverage, and extends promotion-gate inventory/alignment checks +for these helpers: + +- `arg_i32_or` +- `arg_i64_or` +- `arg_f64_or` +- `arg_bool_or` + +The helpers are ordinary source wrappers over the existing argument lookup +and concrete parse result families. Glagol adds no new parser, checker, +runtime, or LLVM semantics for this release. + +### Explicit Deferrals + +No automatic standard-library imports, no compiler-loaded std source, no +process spawning, no exit/status control, no current-directory APIs, no +signal handling, no shell parsing, no subcommands, no richer CLI framework +APIs, no generic configuration helpers, no new compiler-known `std.*` runtime +names, no stable ABI/layout guarantees, and no beta maturity are included. + +## exp-71 + +Release label: `exp-71` + +Release date: 2026-05-20 + +Release state: released experimental process-custom-fallback stdlib gate + +### Summary + +Glagol exp-71 gates source-authored typed process argument custom-fallback +helpers in the staged `std.process` facade. + +The release expands the local source-authored process fixture at +`examples/projects/std-layout-local-process/` with a local `string.slo` +parse wrapper module, expands the explicit `std-import-process/` project, +adds focused integration coverage, and extends promotion-gate +inventory/alignment checks for these helpers: + +- `arg_i32_or` +- `arg_i64_or` +- `arg_f64_or` +- `arg_bool_or` + +The helpers are ordinary source wrappers over the existing argument lookup +and concrete parse result families. Glagol adds no new parser, checker, +runtime, or LLVM semantics for this release. + +### Explicit Deferrals + +No automatic standard-library imports, no compiler-loaded std source, no +process spawning, no exit/status control, no current-directory APIs, no +signal handling, no shell parsing, no subcommands, no richer CLI framework +APIs, no generic configuration helpers, no new compiler-known `std.*` runtime +names, no stable ABI/layout guarantees, and no beta maturity are included. + +## exp-70 + +Release label: `exp-70` + +Release date: 2026-05-20 + +Release state: released experimental fs-custom-fallback stdlib gate + +### Summary + +Glagol exp-70 gates source-authored typed filesystem read custom-fallback +helpers in the staged `std.fs` facade. + +The release expands the local source-authored fs fixture at +`examples/projects/std-layout-local-fs/` with a local `string.slo` parse +wrapper module, expands the explicit `std-import-fs/` project, adds focused +integration coverage, and extends promotion-gate inventory/alignment checks +for these helpers: + +- `read_i32_or` +- `read_i64_or` +- `read_f64_or` +- `read_bool_or` + +The helpers are ordinary source wrappers over the existing text-read and +concrete parse result families. Glagol adds no new parser, checker, runtime, +or LLVM semantics for this release. + +### Explicit Deferrals + +No automatic standard-library imports, no compiler-loaded std source, no +binary file APIs, no directory traversal, no streaming file IO, no async file +IO, no typed file writes, no rich host errors, no new compiler-known `std.*` +runtime names, no stable ABI/layout guarantees, and no beta maturity are +included. + +## exp-69 + +Release label: `exp-69` + +Release date: 2026-05-20 + +Release state: released experimental env-custom-fallback stdlib gate + +### Summary + +Glagol exp-69 gates source-authored typed environment parse custom-fallback +helpers in the staged `std.env` facade. + +The release expands the local source-authored env fixture at +`examples/projects/std-layout-local-env/`, expands the explicit +`std-import-env/` project, adds deterministic present/missing/invalid +environment setup to focused integration coverage, and extends +promotion-gate inventory/alignment checks for these helpers: + +- `get_i32_or` +- `get_i64_or` +- `get_f64_or` +- `get_bool_or` + +The helpers are ordinary source wrappers over the existing environment lookup +and concrete parse result families. Glagol adds no new parser, checker, +runtime, or LLVM semantics for this release. + +### Explicit Deferrals + +No automatic standard-library imports, no compiler-loaded std source, no +generic configuration helpers, no environment mutation, no environment +enumeration, no environment writes, no rich host errors, no new +compiler-known `std.*` runtime names, no stable ABI/layout guarantees, and +no beta maturity are included. + +## exp-68 + +Release label: `exp-68` + +Release date: 2026-05-20 + +Release state: released experimental string-custom-fallback stdlib gate + +### Summary + +Glagol exp-68 gates source-authored typed string parse custom-fallback +helpers in the staged `std.string` facade. + +The release expands the local source-authored string fixture at +`examples/projects/std-layout-local-string/`, expands the explicit +`std-import-string/` project, adds focused integration coverage, and extends +promotion-gate inventory/alignment checks for these helpers: + +- `parse_i32_or` +- `parse_i64_or` +- `parse_f64_or` +- `parse_bool_or` + +The helpers are ordinary source wrappers over the existing concrete parse +result families. Glagol adds no new parser, checker, runtime, or LLVM +semantics for this release. + +### Explicit Deferrals + +No automatic standard-library imports, no compiler-loaded std source, no +generic parse helpers, no whitespace trimming, no case-insensitive bool +parsing, no rich parse errors, no new compiler-known `std.*` runtime names, +no stable ABI/layout guarantees, and no beta maturity are included. + +## exp-67 + +Release label: `exp-67` + +Release date: 2026-05-20 + +Release state: released experimental process-typed stdlib gate + +### Summary + +Glagol exp-67 gates source-authored typed process argument helpers in the +staged `std.process` facade. + +The release expands the local source-authored process fixture at +`examples/projects/std-layout-local-process/` with a local `string.slo` +parse wrapper module, expands the explicit `std-import-process/` project, +adds focused integration coverage, and extends promotion-gate +inventory/alignment checks for these helpers: + +- `arg_i32_result` +- `arg_i32_or_zero` +- `arg_i64_result` +- `arg_i64_or_zero` +- `arg_f64_result` +- `arg_f64_or_zero` +- `arg_bool_result` +- `arg_bool_or_false` + +The helpers are ordinary source wrappers over the existing argument lookup +and concrete parse result families. Glagol adds no new parser, checker, +runtime, or LLVM semantics for this release. + +### Explicit Deferrals + +No automatic standard-library imports, no compiler-loaded std source, no +process spawning, no exit/status control, no current-directory APIs, no +signal handling, no shell parsing, no subcommands, no richer CLI framework +APIs, no rich host errors, no new compiler-known `std.*` runtime names, no +stable ABI/layout guarantees, and no beta maturity are included. + +## exp-66 + +Release label: `exp-66` + +Release date: 2026-05-20 + +Release state: released experimental fs-typed-read stdlib gate + +### Summary + +Glagol exp-66 gates source-authored typed filesystem read helpers in the +staged `std.fs` facade. + +The release expands the local source-authored fs fixture at +`examples/projects/std-layout-local-fs/` with a local `string.slo` parse +wrapper module, expands the explicit `std-import-fs/` project, adds focused +integration coverage, and extends promotion-gate inventory/alignment checks +for these helpers: + +- `read_i32_result` +- `read_i32_or_zero` +- `read_i64_result` +- `read_i64_or_zero` +- `read_f64_result` +- `read_f64_or_zero` +- `read_bool_result` +- `read_bool_or_false` + +The helpers are ordinary source wrappers over the existing text-read and +concrete parse result families. Glagol adds no new parser, checker, runtime, +or LLVM semantics for this release. + +### Explicit Deferrals + +No automatic standard-library imports, no compiler-loaded std source, no +binary file APIs, no directory traversal, no streaming file IO, no async +file IO, no typed file writes, no rich host errors, no new compiler-known +`std.*` runtime names, no stable ABI/layout guarantees, and no beta maturity +are included. + +## exp-65 + +Release label: `exp-65` + +Release date: 2026-05-20 + +Release state: released experimental env-typed stdlib gate + +### Summary + +Glagol exp-65 gates source-authored typed environment helpers in the staged +`std.env` facade. + +The release expands the local source-authored env fixture at +`examples/projects/std-layout-local-env/` with a local `string.slo` parse +wrapper module, expands the explicit `std-import-env/` project, adds +deterministic present/missing/invalid environment setup to focused +integration coverage, and extends promotion-gate inventory/alignment checks +for these helpers: + +- `get_i32_result` +- `get_i32_or_zero` +- `get_i64_result` +- `get_i64_or_zero` +- `get_f64_result` +- `get_f64_or_zero` +- `get_bool_result` +- `get_bool_or_false` + +The helpers are ordinary source wrappers over the existing environment lookup +and concrete parse result families. Glagol adds no new parser, checker, +runtime, or LLVM semantics for this release. + +### Explicit Deferrals + +No automatic standard-library imports, no compiler-loaded std source, no +environment mutation, no environment enumeration, no environment writes, no +generic configuration helpers, no rich host errors, no new compiler-known +`std.*` runtime names, no stable ABI/layout guarantees, and no beta maturity +are included. + +## exp-64 + +Release label: `exp-64` + +Release date: 2026-05-20 + +Release state: released experimental num-fallback stdlib gate + +### Summary + +Glagol exp-64 gates source-authored checked conversion fallback helpers in the +staged `std.num` facade. + +The release adds a local source-authored num fixture at +`examples/projects/std-layout-local-num/`, expands the explicit +`std-import-num/` project, adds focused integration coverage, and extends +promotion-gate inventory/alignment checks for these helpers: + +- `i64_to_i32_or` +- `f64_to_i32_or` +- `f64_to_i64_or` + +The helpers are ordinary source wrappers over the existing checked numeric +conversion result families. Glagol adds no new parser, checker, runtime, or +LLVM semantics for this release. + +### Explicit Deferrals + +No automatic standard-library imports, no compiler-loaded std source, no +unchecked narrowing conversions, no saturating conversions, no generic numeric +traits, no new compiler-known `std.*` runtime names, no stable ABI/layout +guarantees, and no beta maturity are included. + +## exp-63 + +Release label: `exp-63` + +Release date: 2026-05-20 + +Release state: released experimental fs-fallback stdlib gate + +### Summary + +Glagol exp-63 gates source-authored fallback/status helpers in the staged +`std.fs` facade. + +The release expands the local source-authored fs fixture at +`examples/projects/std-layout-local-fs/`, expands the explicit +`std-import-fs/` project, adds focused integration coverage, and extends +promotion-gate inventory/alignment checks for these helpers: + +- `read_text_or` +- `write_text_ok` + +The helpers are ordinary source wrappers over the existing text filesystem +result families. Glagol adds no new parser, checker, runtime, or LLVM +semantics for this release. + +### Explicit Deferrals + +No automatic standard-library imports, no compiler-loaded std source, no +binary file APIs, no directory traversal, no streaming file IO, no async file +IO, no rich host errors, no new compiler-known `std.*` runtime names, no +stable ABI/layout guarantees, and no beta maturity are included. + +## exp-62 + +Release label: `exp-62` + +Release date: 2026-05-20 + +Release state: released experimental env-fallback stdlib gate + +### Summary + +Glagol exp-62 gates source-authored fallback helpers in the staged `std.env` +facade. + +The release expands the local source-authored env fixture at +`examples/projects/std-layout-local-env/`, expands the explicit +`std-import-env/` project, adds deterministic present/missing environment +setup to focused integration coverage, and extends promotion-gate +inventory/alignment checks for these helpers: + +- `has` +- `get_or` + +The helpers are ordinary source wrappers over the existing environment lookup +result family. Glagol adds no new parser, checker, runtime, or LLVM semantics +for this release. + +### Explicit Deferrals + +No automatic standard-library imports, no compiler-loaded std source, no +environment mutation, no environment enumeration, no platform/configuration +APIs, no rich host errors, no new compiler-known `std.*` runtime names, no +stable ABI/layout guarantees, and no beta maturity are included. + +## exp-61 + +Release label: `exp-61` + +Release date: 2026-05-20 + +Release state: released experimental process-fallback stdlib gate + +### Summary + +Glagol exp-61 gates source-authored fallback helpers in the staged +`std.process` facade. + +The release adds a local source-authored process fixture at +`examples/projects/std-layout-local-process/`, expands the explicit +`std-import-process/` project, adds focused integration coverage, and extends +promotion-gate inventory/alignment checks for these helpers: + +- `arg_or` +- `arg_or_empty` + +The helpers are ordinary source wrappers over the existing process argument +result family. Glagol adds no new parser, checker, runtime, or LLVM semantics +for this release. + +### Explicit Deferrals + +No automatic standard-library imports, no compiler-loaded std source, no shell +parsing, no option/flag parsing, no subcommands, no spawning, no exit/status +control, no rich host errors, no new compiler-known `std.*` runtime names, no +stable ABI/layout guarantees, and no beta maturity are included. + +## exp-60 + +Release label: `exp-60` + +Release date: 2026-05-20 + +Release state: released experimental string-fallback stdlib gate + +### Summary + +Glagol exp-60 gates source-authored string parse fallback helpers in the +staged `std.string` facade. + +The release adds a local source-authored string fixture at +`examples/projects/std-layout-local-string/`, expands the explicit +`std-import-string/` project, adds focused integration coverage, and extends +promotion-gate inventory/alignment checks for these helpers: + +- `parse_i32_or_zero` +- `parse_i64_or_zero` +- `parse_f64_or_zero` +- `parse_bool_or_false` + +The helpers are ordinary source wrappers over the existing concrete parse +result families. Glagol adds no new parser, checker, runtime, or LLVM +semantics for this release. + +### Explicit Deferrals + +No automatic standard-library imports, no compiler-loaded std source, no +generic parse helpers, no whitespace trimming, no case-insensitive bool +parsing, no rich parse errors, no new compiler-known `std.*` runtime names, +no stable ABI/layout guarantees, and no beta maturity are included. + +## exp-59 + +Release label: `exp-59` + +Release date: 2026-05-20 + +Release state: released experimental hosted-build optimization and benchmark +publication gate + +### Summary + +Glagol exp-59 tightens hosted executable builds and refreshes the publication +process around the benchmark evidence. + +The release keeps the language surface unchanged, but it now builds hosted +executables through `clang -O2`, lowers immutable numeric locals directly as +SSA values where the current checker/runtime model allows it, updates the +benchmark methodology/writeups, and adds repo-local PDF rendering scripts for +the current whitepaper and compiler manifest. + +### Explicit Deferrals + +No general optimizer pass pipeline, no benchmark thresholds, no +cross-machine performance claims, no stable ABI/layout guarantees, no broad +SSA lowering for every value family, and no beta maturity are included. + +## exp-58 + +Release label: `exp-58` + +Release date: 2026-05-19 + +Release state: released experimental boolean logic compiler gate + +### Summary + +Glagol exp-58 gates `and`, `or`, and `not` as boolean logic forms. + +The release adds `examples/boolean-logic.slo`, focused integration coverage, +promotion-gate coverage, and sibling Slovo fixture alignment. `and` and `or` +lower through existing `if` semantics and short-circuit. + +### Explicit Deferrals + +No truthiness, variadic boolean operators, pattern guards, macro expansion, +stable ABI/layout, optimizer guarantees, benchmark thresholds, or beta +maturity are included. + +## exp-57 + +Release label: `exp-57` + +Release date: 2026-05-19 + +Release state: released experimental integer bitwise compiler/stdlib gate + +### Summary + +Glagol exp-57 gates `bit_and`, `bit_or`, and `bit_xor` for same-width `i32` +and `i64` operands. + +The release adds `examples/integer-bitwise.slo`, focused integration +coverage, promotion-gate coverage, and expanded `std.math` project coverage +for `bit_and_i32`, `bit_or_i32`, `bit_xor_i32`, `bit_and_i64`, +`bit_or_i64`, and `bit_xor_i64`. + +### Explicit Deferrals + +No shifts, bit-not, unsigned arithmetic, bit-width-specific integer families, +floating-point bitwise operations, generic math, mixed numeric arithmetic, +stable ABI/layout, optimizer guarantees, benchmark thresholds, or beta +maturity are included. + +## exp-56 + +Release label: `exp-56` + +Release date: 2026-05-19 + +Release state: released experimental integer remainder compiler/stdlib gate + +### Summary + +Glagol exp-56 gates `%` as signed integer remainder for same-width `i32` and +`i64` operands. + +The release adds `examples/integer-remainder.slo`, focused integration +coverage, promotion-gate coverage, and expanded `std.math` project coverage +for `rem_i32`, `is_even_i32`, `is_odd_i32`, `rem_i64`, `is_even_i64`, and +`is_odd_i64`. + +### Explicit Deferrals + +No floating-point remainder, Euclidean modulo, unsigned arithmetic, bit +operations, generic math, mixed numeric arithmetic, stable ABI/layout, +optimizer guarantees, benchmark thresholds, or beta maturity are included. + +## exp-55 + +Release label: `exp-55` + +Release date: 2026-05-19 + +Release state: released experimental f64/bool result source-flow gate + +### Summary + +Glagol exp-55 gates source constructors and source `match` payload bindings +for `(result f64 i32)` and `(result bool i32)`. + +The release adds `examples/result-f64-bool-match.slo`, focused integration +coverage, and updated `std.cli` coverage. The CLI facade now propagates +missing argument indexes as `err 1` for `f64` and `bool` helpers instead of +using trap-based argument lookup. + +### Explicit Deferrals + +No generic result types, result payload families beyond the already promoted +concrete families, generic `map`/`and_then`, exception handling, automatic +standard-library imports, stable ABI/layout/ownership, optimizer guarantees, +benchmark thresholds, or beta maturity are included. + +## exp-54 + +Release label: `exp-54` + +Release date: 2026-05-19 + +Release state: released experimental typed CLI facade gate + +### Summary + +Glagol exp-54 gates the expanded `std.cli` source facade with typed argument +parse helpers for `i64`, `f64`, and `bool`. + +The release updates `examples/projects/std-import-cli/`, focused integration +coverage, and promotion-gate coverage. The deterministic fixture checks +missing-index result propagation for the current source-supported `string`, +`i32`, and `i64` result families, while keeping `f64` and `bool` +parse-result helpers compile-gated through the explicit import surface. + +### Explicit Deferrals + +No automatic standard-library imports, `std.slo` aggregator, package registry +behavior, lockfiles, package std dependencies, new compiler-known runtime +names, `err f64 i32` or `err bool i32` source constructors, source `match` +over `(result f64 i32)` or `(result bool i32)`, shell parsing, option/flag +parsing, subcommands, environment-backed configuration, stable CLI framework +APIs, stable ABI/layout/ownership, optimizer guarantees, benchmark +thresholds, or beta maturity are included. + +## exp-53 + +Release label: `exp-53` + +Release date: 2026-05-19 + +Release state: released experimental CLI-facade standard-source search gate + +### Summary + +Glagol exp-53 gates explicit project-mode source search for the staged +standard CLI facade module: `std.cli`. + +The release adds `examples/projects/std-import-cli/`, focused integration +coverage, and promotion-gate coverage. The fixture imports sibling Slovo +standard source without carrying a local copied module, and the facade +exercises transitive standard-source imports through `std.process` and +`std.string`. + +### Explicit Deferrals + +No automatic standard-library imports, `std.slo` aggregator, package registry +behavior, lockfiles, package std dependencies, new compiler-known runtime +names, shell parsing, option/flag parsing, subcommands, environment-backed +configuration, stable CLI framework APIs, stable ABI/layout/ownership, +optimizer guarantees, benchmark thresholds, or beta maturity are included. + +## exp-52 + +Release label: `exp-52` + +Release date: 2026-05-19 + +Release state: released experimental process-facade standard-source search gate + +### Summary + +Glagol exp-52 gates explicit project-mode source search for the staged +standard process facade module: `std.process`. + +The release adds `examples/projects/std-import-process/`, focused integration +coverage, and promotion-gate coverage. The fixture imports sibling Slovo +standard source without carrying a local copied module. + +### Explicit Deferrals + +No automatic standard-library imports, `std.slo` aggregator, package registry +behavior, lockfiles, package std dependencies, new compiler-known process +runtime names, process spawning, exit/status control, current-directory APIs, +signal handling, stable ABI/layout/ownership, optimizer guarantees, benchmark +thresholds, or beta maturity are included. + +## exp-51 + +Release label: `exp-51` + +Release date: 2026-05-19 + +Release state: released experimental standard-library path-list discovery gate + +### Summary + +Glagol exp-51 interprets `SLOVO_STD_PATH` as an ordered OS path list. The +standard-source resolver checks listed roots in order and resolves the first +root containing the requested module file. + +This supports layered standard-library development: an override root can +contain only the module under test, while a later root can provide the rest of +the staged standard library. + +### Explicit Deferrals + +No automatic standard-library imports, `std.slo` aggregator, package registry +behavior, lockfiles, package std dependencies, semantic version solving, +stable package manager behavior, broad standard-library APIs, stable +ABI/layout/ownership, optimizer guarantees, benchmark thresholds, or beta +maturity are included. + +## exp-50 + +Release label: `exp-50` + +Release date: 2026-05-19 + +Release state: released experimental installed standard-library discovery gate + +### Summary + +Glagol exp-50 gates installed-toolchain discovery for explicit +standard-source imports. A copied `glagol` binary can find staged Slovo +standard-library modules under executable-relative `../share/slovo/std` +without `SLOVO_STD_PATH` or a sibling checkout. + +The resolver now checks candidates per requested module file, so an earlier +partial std root does not mask a later root containing the module. + +### Explicit Deferrals + +No automatic standard-library imports, `std.slo` aggregator, package registry +behavior, lockfiles, package std dependencies, stable install layout +guarantees beyond this alpha candidate, new compiler-known runtime names, +broad standard-library APIs, stable ABI/layout/ownership, optimizer +guarantees, benchmark thresholds, or beta maturity are included. + +## exp-49 + +Release label: `exp-49` + +Release date: 2026-05-19 + +Release state: released experimental io-facade standard-source search gate + +### Summary + +Glagol exp-49 gates explicit project-mode source search for the staged +standard IO facade module: `std.io`. + +The release adds `examples/projects/std-import-io/`, focused integration +coverage, and promotion-gate coverage. The fixture imports sibling Slovo +standard source without carrying a local copied module. + +### Explicit Deferrals + +No automatic standard-library imports, `std.slo` aggregator, package registry +behavior, installed stdlib paths, new compiler-known IO names, formatted +output APIs, stream abstractions, async IO, stable ABI/layout/ownership, +optimizer guarantees, benchmark thresholds, or beta maturity are included. + +## exp-48 + +Release label: `exp-48` + +Release date: 2026-05-19 + +Release state: released experimental core-facade standard-source search gate + +### Summary + +Glagol exp-48 gates explicit project-mode source search for the staged +standard core facade modules: `std.string` and `std.num`. + +The release adds `examples/projects/std-import-string/` and +`examples/projects/std-import-num/`, focused integration coverage, and +promotion-gate coverage. Each fixture imports sibling Slovo standard source +without carrying a local copied module. + +### Explicit Deferrals + +No automatic standard-library imports, `std.slo` aggregator, package registry +behavior, installed stdlib paths, generic parse/format APIs, broad numeric +casts, stable ABI/layout/ownership, optimizer guarantees, benchmark +thresholds, or beta maturity are included. + +## exp-47 + +Release label: `exp-47` + +Release date: 2026-05-19 + +Release state: released experimental host-facade standard-source search gate + +### Summary + +Glagol exp-47 gates explicit project-mode source search for the staged +standard host facade modules: `std.time`, `std.random`, `std.env`, and +`std.fs`. + +The release adds `examples/projects/std-import-time/`, +`examples/projects/std-import-random/`, `examples/projects/std-import-env/`, +and `examples/projects/std-import-fs/`, focused integration coverage, and +promotion-gate coverage. Each fixture imports sibling Slovo standard source +without carrying a local copied module. + +### Explicit Deferrals + +No automatic standard-library imports, `std.slo` aggregator, workspace +dependency syntax for std, package registry behavior, installed stdlib paths, +broad host APIs, stable ABI/layout/ownership, optimizer guarantees, benchmark +thresholds, or beta maturity are included. + +## exp-46 + +Release label: `exp-46` + +Release date: 2026-05-19 + +Release state: released experimental workspace standard-source search gate + +### Summary + +Glagol exp-46 implements explicit standard-library source search inside +workspace packages. Workspace package modules can import `std.` source +modules using the same `SLOVO_STD_PATH` or `lib/std` discovery model +as project mode. + +The release adds `examples/workspaces/std-import-option/`, focused integration +coverage, and promotion-gate coverage. + +### Explicit Deferrals + +No automatic standard-library imports, workspace dependency syntax for std, +package registry behavior, installed toolchain stdlib paths, `std.slo` +aggregator, generic option helpers, broad stdlib APIs, stable +ABI/layout/ownership, optimizer guarantees, benchmark thresholds, or beta +maturity are included. + +## exp-45 + +Release label: `exp-45` + +Release date: 2026-05-19 + +Release state: released experimental result/option standard-source search gate + +### Summary + +Glagol exp-45 gates the exp-44 source-search resolver for two additional +staged standard modules: `std.result` and `std.option`. + +The release adds `examples/projects/std-import-result/` and +`examples/projects/std-import-option/`, focused integration coverage, and +promotion-gate coverage. Both fixtures import repo-root Slovo standard source +without carrying local `result.slo` or `option.slo` copies. + +### Explicit Deferrals + +No automatic standard-library imports, workspace/package `std` imports, +package registry behavior, `std.slo` aggregator, glob imports, aliases, +qualified member access, generic result helpers, generic option helpers, broad +stdlib APIs, stable ABI/layout/ownership, optimizer guarantees, benchmark +thresholds, or beta maturity are included. + +## exp-44 + +Release label: `exp-44` + +Release date: 2026-05-19 + +Release state: released experimental standard-library source-search gate + +### Summary + +Glagol exp-44 implements project-mode source search for explicit +`std.` imports, starting with `std.math`. A project can now write +`(import std.math (...))` and the compiler loads sibling Slovo +`std/math.slo` source instead of requiring a local copied `math.slo`. + +The release adds `examples/projects/std-import-math/`, focused integration +coverage, and promotion-gate coverage. The standard-library root is discovered +through `SLOVO_STD_PATH` or a `lib/std` checkout. + +### Explicit Deferrals + +No automatic standard-library imports, workspace/package `std` imports, +package registry behavior, `std.slo` aggregator, glob imports, aliases, +qualified member access, compiler-known `std.*` runtime names, broad stdlib +APIs, stable ABI/layout/ownership, optimizer guarantees, benchmark +thresholds, or beta maturity are included. + +## exp-43 + +Release label: `exp-43` + +Release date: 2026-05-19 + +Release state: released experimental technical whitepaper and design-skill +gates + +### Summary + +Glagol exp-43 publishes `docs/GLAGOL_WHITEPAPER.md`, generated PDF +publication artifacts, a concise compiler manifest, and a repository-local +beta compiler-design skill under `.llm/skills/`. + +### Explicit Deferrals + +No parser behavior, checker behavior, backend behavior, runtime APIs, +compiler-known `std.*` names, automatic standard-library imports, stable +ABI/layout/ownership, optimizer guarantees, benchmark thresholds, +cross-machine performance claims, package registry behavior, or beta maturity +are included. + +## exp-42 + +Release label: `exp-42` + +Release date: 2026-05-19 + +Release state: released experimental hot-loop benchmark mode gates + +### Summary + +Glagol exp-42 separates two benchmark meanings in the shared runner: +`cold-process` for one normal startup-inclusive run, and `hot-loop` for a +larger runtime-supplied loop count with total time plus normalized median +time for the base loop count. + +The benchmark metadata now records hot-loop counts and checksums. The Slovo +benchmark fixtures accept the runner loop count through process arguments +with stdin fallback, so Slovo can participate in hot-loop mode. + +### Explicit Deferrals + +No language semantics, compiler optimizations, high-resolution Slovo timing +APIs, stable benchmark thresholds, cross-machine performance claims, stable +ABI/layout/ownership, package registry behavior, hot-runtime benchmark +daemon, or beta maturity are included. + +## exp-41 + +Release label: `exp-41` + +Release date: 2026-05-19 + +Release state: released experimental Lisp benchmark comparison gates + +### Summary + +Glagol exp-41 extends the local benchmark suite with Common Lisp/SBCL source +slots beside the existing Clojure slots. The benchmark suite includes +`math-loop`, `branch-loop`, and `parse-loop` scaffolds, each with Slovo, C, +Rust, Python, Clojure, and Common Lisp/SBCL source slots where the local +toolchain is available. + +The shared runner detects SBCL through `sbcl`, `SBCL`, or `--sbcl`. Benchmark +timing remains cold-process local-machine evidence only: Clojure timings +include JVM and Clojure startup, while Common Lisp timings include SBCL script +startup. + +### Explicit Deferrals + +No language semantics, compiler optimizations, stable benchmark thresholds, +cross-machine performance claims, stable ABI/layout/ownership, package +registry behavior, hot-runtime benchmark daemon, or beta maturity are +included. + +## exp-40 + +Release label: `exp-40` + +Release date: 2026-05-19 + +Release state: released experimental benchmark-suite and license gates + +### Summary + +Glagol exp-40 adds a broader local benchmark suite and project license files. +The benchmark suite includes `math-loop`, `branch-loop`, and `parse-loop` +scaffolds, each with Slovo, C, Rust, Python, and Clojure source slots where +the local toolchain is available. + +The shared runner validates deterministic checksums, skips missing toolchains, +and reports local-machine timing comparisons only. Clojure is detected through +`clojure`, `CLOJURE`, or `CLOJURE_JAR`. + +Glagol is documented and licensed as `MIT OR Apache-2.0`, matching the Cargo +package metadata. Alpha/beta release criteria are recorded in `.llm/`. + +### Explicit Deferrals + +No language semantics, compiler optimizations, stable benchmark thresholds, +cross-machine performance claims, stable ABI/layout/ownership, package +registry behavior, or beta maturity are included. + +## exp-39 + +Release label: `exp-39` + +Release date: 2026-05-19 + +Release state: released experimental math-helper and benchmark-scaffold gates + +### Summary + +Glagol exp-39 implements Standard Math Extensions And Benchmark Scaffold Alpha +on the Glagol side as fixture, documentation, promotion-gate coverage, and a +local benchmark scaffold. It extends +`examples/projects/std-layout-local-math/` so an ordinary project explicitly +imports local `math.slo` helpers for `abs`, `neg`, `min`, `max`, `clamp`, +`square`, `cube`, `is_zero`, `is_positive`, `is_negative`, and inclusive +`in_range` across `i32`, `i64`, and finite `f64`. + +It also adds `benchmarks/math-loop/`, with Slovo, C, Rust, and Python +implementations plus a runner for local-machine timing comparisons. The runner +supplies the loop count through stdin, validates a shared checksum, skips +missing toolchains where possible, and reports timing data only as local +evidence. + +### Explicit Deferrals + +No new compiler-known `std.*` names, automatic std import/search path, +compiler-loaded std source, new numeric primitives, modulo, +trigonometry/sqrt/pow/logarithm APIs, generic math, overloads, traits, mixed +numeric arithmetic, optimizer guarantees, benchmark thresholds, cross-machine +performance claims, runtime ABI changes, stable ABI/layout/ownership, or beta +maturity are included. + +## exp-38 + +Release label: `exp-38` + +Release date: 2026-05-19 + +Release state: released experimental source-facade gates + +### Summary + +Glagol exp-38 implements Standard Host Source Facades Alpha on the Glagol side +as fixture, documentation, and promotion-gate coverage. It adds +`examples/projects/std-layout-local-random/`, +`examples/projects/std-layout-local-env/`, and +`examples/projects/std-layout-local-fs/` so ordinary projects explicitly +import local facades backed by already promoted `std.random.i32`, +`std.env.get`, `std.env.get_result`, `std.fs.read_text`, +`std.fs.read_text_result`, `std.fs.write_text`, and +`std.fs.write_text_result` calls. + +The gated facade set is `random_i32`, `random_i32_non_negative`, `get`, +`get_result`, `read_text`, `read_text_result`, `write_text_status`, and +`write_text_result`. + +The default promotion gate checks the local fixtures remain explicit. The +ignored monorepo Slovo std-source gate now also checks sibling +`std/random.slo`, `std/env.slo`, and `std/fs.slo` as part of the exact std +source inventory and facade-name alignment. + +### Explicit Deferrals + +No new compiler-known `std.*` names, automatic std import/search path, +compiler-loaded std source, seed/range/bytes/float/UUID/crypto random APIs, +environment mutation/enumeration, binary/directory/streaming/async filesystem +APIs, rich host error ADTs, runtime ABI changes, stable ABI/layout/ownership, +or beta maturity are included. + +## exp-37 + +Release label: `exp-37` + +Release date: 2026-05-19 + +Release state: released experimental source-facade gates + +### Summary + +Glagol exp-37 implements Standard Time Source Facade Alpha on the Glagol side +as fixture, documentation, and promotion-gate coverage. It adds +`examples/projects/std-layout-local-time/` so an ordinary project explicitly +imports local `time.slo` facades backed by the existing promoted +`std.time.monotonic_ms` and `std.time.sleep_ms` calls. + +The gated facade set is `monotonic_ms` and `sleep_ms_zero`. `sleep_ms_zero` +calls `std.time.sleep_ms 0` and returns `0`, because source unit-return facade +functions are not promoted. + +The default promotion gate checks the local fixture remains explicit. The +ignored monorepo Slovo std-source gate now also checks repo `lib/std/time.slo` +as part of the exact std source inventory and facade-name alignment. + +### Explicit Deferrals + +No new compiler-known `std.*` names, automatic std import/search path, +compiler-loaded std source, wall-clock/calendar/timezone APIs, +high-resolution timers, async timers, cancellation, scheduling guarantees, +runtime ABI changes, stable ABI/layout/ownership, or beta maturity are +included. + +## exp-36 + +Release label: `exp-36` + +Release date: 2026-05-19 + +Release state: released experimental source-helper gates + +### Summary + +Glagol exp-36 implements Standard Option Source Helpers Alpha on the Glagol +side as fixture, documentation, and promotion-gate coverage. It adds +`examples/projects/std-layout-local-option/` so an ordinary project explicitly +imports local `option.slo` helpers for source-authored `(option i32)` values +created with `some` and `none`. + +The gated helper set is `is_some_i32`, `is_none_i32`, `unwrap_some_i32`, and +`unwrap_or_i32`. + +The default promotion gate checks the local fixture remains explicit and +source-authored, with no automatic std imports and no compiler-known +`std.option.*` names. The ignored monorepo Slovo std-source gate now also +checks repo `lib/std/option.slo` inventory and helper-name alignment. + +### Explicit Deferrals + +No new compiler-known `std.*` names, generic option helpers, option payload +families beyond `i32`, source-loaded std modules, automatic std import/search +path, runtime ABI changes, manifest schema changes, stable ABI/layout/ownership, +or beta maturity are included. exp-95 later broadens the same staged facade +and compiler slice to concrete `(option i64)`. + +## exp-35 + +Release label: `exp-35` + +Release date: 2026-05-19 + +Release state: released experimental source-helper gates + +### Summary + +Glagol exp-35 implements Standard Result Bool Source Helpers Alpha on the +Glagol side as fixture, documentation, and promotion-gate coverage. It extends +`examples/projects/std-layout-local-result/` so an ordinary project explicitly +imports local `result.slo` helpers for `(result bool i32)` values produced by +`std.string.parse_bool_result`. + +The gated helper set is `is_ok_bool`, `is_err_bool`, `unwrap_ok_bool`, +`unwrap_err_bool`, and `unwrap_or_bool`. + +The default promotion gate checks the local fixture remains explicit and +source-authored, with no automatic std imports and no compiler-known +`std.result.unwrap_or`. The ignored monorepo Slovo std-source gate now also +checks repo `lib/std/result.slo` bool helper-name alignment. + +### Explicit Deferrals + +No source-authored `(result bool i32)` constructors, generic bool result +matching, generic result helpers, compiler-known `std.result.unwrap_or`, +`std.result.map`, `std.result.and_then`, option helpers, new compiler-known +`std.*` runtime calls, runtime ABI changes, automatic std import/search path, +compiler-loaded std source, manifest schema changes, stable +ABI/layout/ownership, or beta maturity are included. + +## exp-34 + +Release label: `exp-34` + +Release date: 2026-05-19 + +Release state: released experimental compiler support + +### Summary + +Glagol exp-34 implements String Parse Bool Result Alpha. The compiler now +promotes exactly +`std.string.parse_bool_result : (string) -> (result bool i32)`. + +The release accepts only complete lowercase ASCII `true` and `false`, returns +`ok true` or `ok false`, and returns `err 1` for empty strings, +uppercase/mixed-case text, leading or trailing whitespace, numeric text, or +any other text. Ordinary parse failure does not trap. + +The compiler lowers the call through the private runtime helper +`__glagol_string_parse_bool_result`, and the test runner mirrors the same +exact lowercase semantics. The fixture is +`examples/string-parse-bool-result.slo`. + +### Explicit Deferrals + +No generic parse APIs, trap-based `std.string.parse_bool`, case-insensitive +parsing, whitespace trimming, locale/Unicode parsing, numeric bool parsing, +string/bytes parse APIs, rich parse errors, generic result support, +source-authored `(result bool i32)` constructors or matches, stable helper +ABI/layout/ownership, runtime headers/libraries, manifest schema changes, or +beta maturity are included. + +## exp-33 + +Release label: `exp-33` + +Release date: 2026-05-19 + +Release state: released experimental source-helper gates + +### Summary + +Glagol exp-33 implements Standard Result Source Helpers Alpha on the Glagol +side as fixture, documentation, and promotion-gate coverage. It adds +`examples/projects/std-layout-local-result/` so an ordinary project explicitly +imports a local `result.slo` module with source-authored helpers over the +current concrete result families. + +The gated helper set is `is_err_i64`, `unwrap_err_i64`, `is_err_string`, +`unwrap_err_string`, `is_err_f64`, `unwrap_err_f64`, `unwrap_or_i32`, +`unwrap_or_i64`, `unwrap_or_string`, and `unwrap_or_f64`. + +The default promotion gate checks the local fixture remains explicit and +source-authored, with no automatic std imports and no compiler-known +`std.result.unwrap_or`. The ignored monorepo Slovo std-source gate now also +checks repo `lib/std/result.slo` helper-name alignment. + +### Explicit Deferrals + +No generic result helpers, compiler-known `std.result.unwrap_or`, +`std.result.map`, `std.result.and_then`, option helpers, new result payload +families, new compiler-known `std.*` runtime calls, runtime ABI changes, +automatic std import/search path, compiler-loaded std source, manifest schema +changes, stable ABI/layout/ownership, or beta maturity are included. + +## exp-32 + +Release label: `exp-32` + +Release date: 2026-05-19 + +Release state: released experimental source-helper gates + +### Summary + +Glagol exp-32 implements Standard Math Source Helpers Alpha on the Glagol side +as fixture, documentation, and promotion-gate coverage. It extends +`examples/projects/std-layout-local-math/` so an ordinary project explicitly +imports a local `math.slo` module with source-authored helpers for the current +`i32`, `i64`, and `f64` numeric families. + +The gated helper set is `abs_i32`, `min_i32`, `max_i32`, `clamp_i32`, +`square_i32`, `abs_i64`, `min_i64`, `max_i64`, `clamp_i64`, `square_i64`, +`abs_f64`, `min_f64`, `max_f64`, `clamp_f64`, and `square_f64`. + +The default promotion gate checks the local fixture remains explicit and +source-authored, with no `std.*` runtime calls. The ignored monorepo Slovo +std-source gate now also checks repo `lib/std/math.slo` helper-name alignment. + +### Explicit Deferrals + +No new compiler-known `std.*` runtime calls, runtime ABI changes, automatic +std import/search path, compiler-loaded std source, broad math library, +trigonometry/sqrt/pow, `f32`, unsigned or narrower integer families, generic +math, overloads, traits, mixed numeric arithmetic, manifest schema changes, +stable ABI/layout/ownership, or beta maturity are included. + +## exp-31 + +Release label: `exp-31` + +Release date: 2026-05-19 + +Release state: released experimental compiler support + +### Summary + +Glagol exp-31 implements Checked F64 To I64 Result Alpha. The compiler now +promotes exactly +`std.num.f64_to_i64_result : (f64) -> (result i64 i32)`. + +The call returns `ok value` only when the input is finite, exactly integral, +and inside the signed `i64` range. It returns `err 1` for non-finite, +fractional, or out-of-range input without trapping for ordinary conversion +failure. The implementation executes in the deterministic test runner, +formats through the existing formatter, preserves surface/checked lowering +snapshots, and lowers directly to LLVM with ordered range checks before +`fptosi`. + +The promoted fixture is `f64-to-i64-result.slo`. + +### Explicit Deferrals + +No unchecked `f64` to `i64`, casts or cast syntax, generic `cast_checked`, +`f32`, unsigned or narrower integer families, additional numeric conversion +families, mixed numeric arithmetic, numeric containers, stable ABI/layout, +manifest schema changes, or beta maturity are claimed by this slice. + +## exp-30 + +Release label: `exp-30` + +Release date: 2026-05-19 + +Release state: released experimental contract-gate support + +### Summary + +Glagol exp-30 implements Standard Library Source Layout Alpha on the compiler +side as documentation and gates for the Slovo `lib/std/` source layout contract. +The expected Slovo contract files are `std/README.md`, `std/io.slo`, +`std/string.slo`, `std/num.slo`, `std/result.slo`, and `std/math.slo`. + +Glagol adds `examples/projects/std-layout-local-math/`, a normal project-mode +fixture that explicitly imports a local `math.slo` module. This demonstrates +that the source pattern remains valid for ordinary explicit imports without +claiming automatic standard-library loading. + +The ignored promotion gate +`hermeticum_slovo_std_source_layout_alpha_is_preserved` checks the sibling +Slovo `lib/std/` layout plus source-shape and formatter stability. + +### Explicit Deferrals + +No new source syntax, new compiler-known standard-runtime functions, automatic +`std/` import/search path, package registry behavior, manifest schema changes, +self-hosted replacement for compiler-known `std.*` calls, runtime ABI/layout +claim, or beta maturity is included. Promoted `std.*` calls continue to execute +through `compiler/src/std_runtime.rs`. + +## exp-29 + +Release label: `exp-29` + +Release date: 2026-05-19 + +Release state: released experimental compiler support + +### Summary + +Glagol exp-29 implements Numeric Struct Fields Alpha. Top-level structs may +use direct immutable `i64` and finite `f64` fields alongside existing direct +`i32`, `bool`, immutable `string`, and current enum fields. + +The release covers struct constructors, immutable local/parameter/return/call +flow, field access returning `i64` or `f64`, and use of accessed fields with +existing same-type arithmetic/comparison plus existing print and format +helpers where those helpers are already supported. + +The promoted fixture is `numeric-struct-fields.slo`. + +### Explicit Deferrals + +Struct field mutation, nested structs, arrays/vectors/options/results as +fields, enum payload widening, numeric containers, `f32`, unsigned or narrower +integer families, mixed numeric arithmetic, implicit conversions, new +parse/format APIs, generic structs, methods/traits, stable ABI/layout/ +ownership, manifest schema changes, FFI layout claims, and beta maturity +remain outside this slice. + +## exp-28 + +Release label: `exp-28` + +Release date: 2026-05-19 + +Release state: released experimental compiler support + +### Summary + +Glagol exp-28 implements String Parse F64 Result Alpha. The compiler now +promotes exactly +`std.string.parse_f64_result : (string) -> (result f64 i32)`. + +The call accepts complete finite ASCII decimal text in the narrow +`-?digits.digits` shape, returns `ok value` for finite parsed `f64` values, +and returns `err 1` for ordinary parse failure, non-finite text, unsupported +leading/trailing characters, or out-of-domain input. The concrete +`(result f64 i32)` family is supported for direct fixture flow and existing +result helpers without making result handling generic. Source-authored +`(ok f64 i32 ...)`, `(err f64 i32 ...)`, and `match` over +`(result f64 i32)` remain deferred. + +The implementation executes in the deterministic test runner, formats through +the existing formatter, preserves surface/checked lowering snapshots, and +lowers hosted builds through private runtime helper +`__glagol_string_parse_f64_result`. + +The promoted fixture is `string-parse-f64-result.slo`. + +### Explicit Deferrals + +No generic parse, trap-based `std.string.parse_f64`, leading `+`, exponent +notation, missing digits around `.`, locale parsing, Unicode digits, +underscores, rich parse errors, stable helper ABI/layout, runtime +headers/libraries, generic result support, f64 containers, mixed numeric +arithmetic, manifest schema changes, or beta maturity are claimed by this +slice. + +## exp-27 + +Release label: `exp-27` + +Release date: 2026-05-19 + +Release state: released experimental compiler support + +### Summary + +Glagol exp-27 implements Checked F64 To I32 Result Alpha. The compiler now +promotes exactly +`std.num.f64_to_i32_result : (f64) -> (result i32 i32)`. + +The call returns `ok value` only when the input is finite, exactly integral, +and inside the signed `i32` range. It returns `err 1` for non-finite, +fractional, or out-of-range input without trapping for ordinary conversion +failure. The implementation executes in the deterministic test runner, +formats through the existing formatter, preserves surface/checked lowering +snapshots, and lowers directly to LLVM with ordered range checks before +`fptosi`. + +The promoted fixture is `f64-to-i32-result.slo`. + +### Explicit Deferrals + +No unchecked `f64` to `i32`, casts or cast syntax, generic `cast_checked`, +`f32`, unsigned or narrower integer families, additional numeric conversion +families, f64 parse, mixed numeric arithmetic, numeric containers, stable +ABI/layout, runtime headers/libraries, manifest schema changes, or beta +maturity are claimed by this slice. + +## exp-26 + +Release label: `exp-26` + +Release date: 2026-05-18 + +Release state: released experimental compiler support + +### Summary + +Glagol exp-26 implements F64 To String Alpha. The compiler now promotes +exactly `std.num.f64_to_string : (f64) -> string`. + +The call returns finite decimal ASCII text for promoted finite `f64` inputs. +It type checks like other promoted standard-runtime names, executes in the +deterministic test runner, formats through the existing formatter, preserves +surface/checked lowering snapshots, lowers hosted builds through a private +runtime helper, and adds machine diagnostic snapshots for arity, type, +context, bool context, standard-name shadowing, and helper-name shadowing. +The promoted fixture is `f64-to-string.slo`. + +### Explicit Deferrals + +No `f32`, f64 parse, generic format/display/interpolation, locale/base/radix/ +grouping/padding/precision controls, stable NaN/infinity text, implicit +conversion, stable helper ABI/layout, runtime headers/libraries, manifest +schema changes, or beta maturity are claimed by this slice. + +## exp-25 + +Release label: `exp-25` + +Release date: 2026-05-18 + +Release state: released experimental compiler support + +### Summary + +Glagol exp-25 implements String Parse I64 Result Alpha. The compiler now +promotes exactly +`std.string.parse_i64_result : (string) -> (result i64 i32)`. + +The call accepts an entire non-empty signed decimal ASCII string, permits only +an optional leading `-`, rejects leading `+`, whitespace, underscores, +base/radix prefixes, locale forms, Unicode digits, suffixes, and trailing +bytes, and returns `ok value` only inside the signed `i64` range. Malformed or +out-of-range inputs return `err 1` without ordinary traps. The promoted +fixture is `string-parse-i64-result.slo`. + +The implementation type checks the call, executes it in the deterministic test +runner, formats it through the existing formatter, preserves surface/checked +lowering snapshots, lowers hosted builds through a private runtime helper, and +adds machine diagnostic snapshots for arity, type, context, bool context, +standard-name shadowing, and helper-name shadowing. + +### Explicit Deferrals + +No leading `+`, whitespace trimming, locale/base/radix/underscore handling, +Unicode digit parsing, f64 parse, bool/string/bytes parse, generic parse, rich +parse errors, stable helper ABI/layout, runtime headers/libraries, manifest +schema changes, or beta maturity are claimed by this slice. + +## exp-24 + +Release label: `exp-24` + +Release date: 2026-05-18 + +Release state: released experimental compiler support + +### Summary + +Glagol exp-24 implements Integer To String Alpha. The compiler now promotes +exactly `std.num.i32_to_string : (i32) -> string` and +`std.num.i64_to_string : (i64) -> string`. + +The calls return signed decimal ASCII strings with a leading `-` for negative +values and no leading `+`. They type check like other promoted +standard-runtime names, execute in the deterministic test runner, format +through the existing formatter, and lower to private runtime helpers that +allocate strings consistently with current string runtime behavior. The +promoted fixture is `integer-to-string.slo`. + +### Explicit Deferrals + +No f64 formatting, parse APIs, locale/base/radix/grouping/padding controls, +generic display/format traits, implicit conversions, stable helper ABI/layout, +manifest schema changes, or beta maturity are claimed by this slice. + +## exp-23 + +Release label: `exp-23` + +Release date: 2026-05-18 + +Release state: released experimental compiler support + +### Summary + +Glagol exp-23 implements Checked I64-To-I32 Conversion Alpha. The compiler now +promotes exactly `std.num.i64_to_i32_result : (i64) -> (result i32 i32)`. + +The call returns `ok value` for signed `i64` inputs in the signed `i32` range +`[-2147483648, 2147483647]` and `err 1` for out-of-range inputs. It type +checks like other promoted standard-runtime names, executes in the +deterministic test runner, formats through the existing formatter, and lowers +directly to LLVM range checks plus truncation into the existing +`(result i32 i32)` representation. The promoted fixture is +`checked-i64-to-i32-conversion.slo`. + +### Explicit Deferrals + +No traps, implicit promotion, unchecked narrowing, cast syntax, additional +narrowing conversions, f64 conversions, stable helper ABI/layout, manifest +schema changes, or beta maturity are claimed by this slice. + +## exp-22 + +Release label: `exp-22` + +Release date: 2026-05-18 + +Release state: released experimental compiler support + +### Summary + +Glagol exp-22 implements Numeric Widening Conversions Alpha. The compiler now +promotes exactly `std.num.i32_to_i64 : (i32) -> i64`, +`std.num.i32_to_f64 : (i32) -> f64`, and +`std.num.i64_to_f64 : (i64) -> f64`. + +The calls type check like other promoted standard-runtime names, execute in +the test runner, format through the existing formatter, and lower directly to +LLVM `sext` or `sitofp` conversion instructions. The promoted fixture is +`numeric-widening-conversions.slo`. + +### Explicit Deferrals + +Implicit promotion, mixed numeric operators, narrowing or checked +conversions, cast syntax, f32, unsigned and narrower integer families, +char/bytes/decimal, numeric parse/format APIs, stable ABI/layout, manifest +schema changes, and beta maturity remain outside this slice. + +## exp-21 + +Release label: `exp-21` + +Release date: 2026-05-18 + +Release state: released experimental compiler support + +### Summary + +Glagol exp-21 implements I64 Numeric Primitive Alpha. The compiler accepts +direct `i64` type names, explicit signed decimal `i64` suffix atoms, +immutable `i64` locals, parameters, returns, calls, tests, same-type +arithmetic and comparison, LLVM `i64` lowering, and +`std.io.print_i64 : (i64) -> unit`. + +The promoted fixture is `i64-numeric-primitive.slo`. + +### Explicit Deferrals + +f32, unsigned integers, narrower signed integer widths, char/bytes/decimal, +casts, implicit promotion, mixed numeric operators, i64 containers, i64 enum +payloads or struct fields, parse_i64, random i64, stable ABI/layout, manifest +schema changes, and beta maturity remain outside this slice. + +## exp-20 + +Release label: `exp-20` + +Release date: 2026-05-18 + +Release state: released experimental compiler support + +### Summary + +Glagol exp-20 implements F64 Numeric Primitive Alpha. The language now accepts +direct `f64` type names and float atoms, checks f64 locals, parameters, +returns, calls, top-level tests, arithmetic `+ - * /`, equality, and ordered +comparisons when both operands are `f64`, and emits LLVM `double` value flow. + +The standard runtime promotes exactly `std.io.print_f64 : (f64) -> unit`, +lowering to `print_f64(double)` in `runtime/runtime.c` with `%.17g\n` +formatting. The promoted fixture is `f64-numeric-primitive.slo`. + +### Explicit Deferrals + +f32, i64/u64/u32/u16/u8/i16/i8, char, bytes, decimal, casts, mixed i32/f64 +arithmetic or comparison, f64 arrays/vectors/options/results/enum payloads/ +struct fields, parse_f64, random floats, stable ABI/layout, manifest schema +changes, and beta maturity remain outside this slice. + +## exp-19 + +Release label: `exp-19` + +Release date: 2026-05-18 + +Release state: released experimental compiler support + +### Summary + +Glagol exp-19 implements Primitive Struct Fields Alpha. Top-level structs may +use direct `bool` and immutable `string` fields alongside existing direct +`i32` fields and exp-18 direct enum fields. + +The slice covers struct construction, field access, immutable struct locals, +parameters, returns, calls, top-level tests, lowering inspection, formatter +stability, and LLVM aggregate emission for direct primitive fields. The +promoted fixture is `primitive-struct-fields.slo`. + +### Explicit Deferrals + +Arrays, vectors, options, and results containing primitive fields remain +unsupported as struct fields. Nested structs, struct field/value mutation, +broader string ownership and cleanup promises, generics, methods/traits, +stable ABI/layout, manifest schema changes, import/package widening, and beta +maturity remain outside this slice. + +## exp-18 + +Release label: `exp-18` + +Release date: 2026-05-18 + +Release state: released experimental compiler support + +### Summary + +Glagol exp-18 implements Enum Struct Fields Alpha. Top-level structs may use +current user-defined enum types directly as fields, alongside existing `i32` +fields. This covers payloadless enums and unary `i32` payload enum values in +struct construction, field access, immutable locals, parameters, returns, +calls, same-enum equality, exhaustive enum matches, top-level tests, and +`main`. + +The promoted fixture is `enum-struct-fields.slo`, with formatter and lowering +snapshots plus focused diagnostics for enum containers inside struct fields. + +### Explicit Deferrals + +Arrays, vectors, options, and results containing enums remain unsupported as +struct fields and continue to report `UnsupportedEnumContainer`. Nested struct +fields, struct/enum mutation, printing enum values, enum ordering/hash/ +reflection/discriminants, broader enum payloads, generics, stable ABI/layout, +manifest schema changes, package registry behavior, import aliases/globs/ +re-exports, and beta maturity remain outside this slice. + +## exp-17 + +Release label: `exp-17` + +Release date: 2026-05-18 + +Release state: released experimental compiler support + +### Summary + +Glagol exp-17 implements Project/Workspace Enum Imports Alpha. Top-level enum +declarations now participate in flat project and local workspace visibility: +modules can export enum names and consumers can import those enum types +alongside functions and structs. + +Imported enums preserve exp-4 payloadless and exp-16 unary `i32` payload +metadata across module and package boundaries. Imported enum values work in +function signatures, immutable locals, calls/returns, qualified constructors, +same-enum equality, exhaustive enum matches, `main`, and tests. + +The promoted project fixture is `examples/projects/enum-imports/`. + +### Explicit Deferrals + +Non-`i32` payload types, multiple payloads, record variants, enum containers, +enum mutation, enum printing, enum ordering/hash/reflection/discriminants, +generics, stable ABI/layout, manifest schema changes, registry/package-manager +behavior, and beta maturity remain outside this slice. + +## exp-16 + +Release label: `exp-16` + +Release date: 2026-05-18 + +Release state: released experimental compiler support + +### Summary + +Glagol exp-16 implements Unary I32 Enum Payloads Alpha. User enum declarations +may mix payloadless variants with unary `i32` payload variants, for example +`(Value i32)`. Qualified constructors support `(Reading.Missing)` and +`(Reading.Value value)`, and exhaustive enum matches require no binding for +payloadless variants and exactly one immutable arm-local `i32` binding for +payload variants. + +Enum values flow through immutable locals, parameters, returns, calls, tests, +`main`, and same-enum equality. Equality compares tag plus payload for payload +enums and tag only for payloadless enums. LLVM and the test runner use a +compiler-owned `{ i32, i32 }` tag/payload representation for payload enums; +this is not a stable ABI or layout claim. + +The promoted fixture is `enum-payload-i32.slo`, with formatter and lowering +snapshots plus focused diagnostics for unsupported payload types, constructor +arity/type failures, and match binding requirements. + +### Explicit Deferrals + +Non-`i32` payload types, multiple payloads, record variants, generic +enums/functions, wildcard/rest/nested enum patterns, pattern guards, enum +values in containers, enum mutation, enum printing, enum ordering/hash/ +reflection/discriminants, runtime/runtime.c changes, stable ABI/layout, +manifest schema changes, and beta maturity remain outside this slice. + +## exp-15 + +Release label: `exp-15` + +Release date: 2026-05-18 + +Release state: released experimental compiler support + +### Summary + +Glagol exp-15 implements Result Helper Standard Names Alpha. It accepts +`std.result.is_ok`, `std.result.is_err`, `std.result.unwrap_ok`, and +`std.result.unwrap_err` for exactly `(result i32 i32)` and +`(result string i32)`, mapping them to the existing result observer and unwrap +behavior. + +Legacy `is_ok`, `is_err`, `unwrap_ok`, and `unwrap_err` remain supported. +No `std.result.map`, `std.result.unwrap_or`, `std.result.and_then`, option +standard names, generic results, new result payload families, runtime API, +manifest schema change, or beta maturity is claimed. + +## exp-14 + +Release label: `exp-14` + +Release date: 2026-05-18 + +Release state: released experimental conformance alignment + +### Summary + +Glagol exp-14 is the Standard Runtime Conformance Alignment release. It aligns +docs, fixtures, formatter/lowering snapshots, promotion inventories, and a +focused conformance gate over the already released exp-13 surface plus released +exp-8 time/sleep support. It is not beta. + +This release adds no source syntax, type forms, standard-library operations, +runtime APIs, ABI/layout promises, or manifest schema changes. + +### Gate + +The focused conformance gate, full release gate, and Slovo/Glagol review pass +together. The new time/sleep fixture uses only `std.time.monotonic_ms` and +`std.time.sleep_ms 0`; it does not assert positive-duration timing behavior. + +## exp-13 + +Release label: `exp-13` + +Release date: 2026-05-18 + +Release state: released experimental compiler support + +### Summary + +Glagol exp-13 implements Slovo exp-13 String Parse I32 Result Alpha as +released experimental compiler support. It is not beta. + +The release promotes exactly one standard-runtime call: + +- `std.string.parse_i32_result : (string) -> (result i32 i32)` + +The operation parses an entire ASCII decimal signed `i32` string and returns +`ok value` on success or ordinary parse failure as `err 1`. + +### Release State + +The fixture, diagnostics, lowering snapshots, and promotion inventories are +released experimental compiler-support state after the implementation, +diagnostics, lowering-inspector snapshots, runtime/test-runner behavior, +promotion gates, release review, and full release gate passed. + +No trap-based `std.string.parse_i32`, float/bool/string/bytes parsing, +whitespace/locale/base-prefix/underscore/plus-sign parsing, generic parse API, +rich parse errors, Unicode digit parsing, string indexing/slicing, +tokenizer/scanner API, stdin line API, manifest schema changes, stable helper +ABI/layout, runtime headers/libraries, or beta maturity is claimed. + +## exp-12 + +Release label: `exp-12` + +Release date: 2026-05-18 + +### Summary + +Glagol exp-12 implements Slovo exp-12 Standard Input Result Alpha as released +experimental compiler support. It is not beta. + +The release promotes exactly one standard-runtime call: + +- `std.io.read_stdin_result : () -> (result string i32)` + +The release returns `ok` with remaining stdin text, including ordinary EOF as +`ok ""`; ordinary host/input failure returns `err 1`. + +### Release State + +exp-12 is released experimental compiler support after the implementation, +Slovo fixture alignment, formatter, diagnostics, lowering-inspector snapshots, +test-runner, LLVM/runtime, promotion gates, release review, and full release +gate passed. + +No trap-based `std.io.read_stdin`, line iteration, prompt APIs, terminal mode, +binary stdin, streaming, async stdin, encoding/Unicode promise, stable helper +ABI/layout, runtime headers/libraries, manifest schema change, or beta maturity +is claimed. + +## exp-11 + +Release label: `exp-11` + +Release date: 2026-05-18 + +### Summary + +Glagol exp-11 implements Slovo exp-11 Basic Randomness Alpha. It is released +experimental compiler support, not beta. + +The release promotes exactly one standard-runtime call: + +- `std.random.i32 : () -> i32` + +The call returns a non-negative implementation-owned `i32` suitable only for +basic command-line programs. If native runtime randomness is unavailable, the +runtime trap text must be exactly +`slovo runtime error: random i32 unavailable`. + +### Release State + +exp-11 is released experimental compiler support after Slovo and Glagol +fixtures, formatter, diagnostics, lowering-inspector snapshots, test-runner, +LLVM/runtime, promotion gates, release review, and release gates passed. + +No seed APIs, deterministic public sequences, cryptographic suitability, +range/bounds APIs, bytes, strings, UUIDs, artifact-manifest schema changes, +stable helper ABI, runtime headers/libraries, or beta maturity are claimed. + +## exp-10 + +Release label: `exp-10` + +Release date: 2026-05-18 + +### Summary + +Glagol exp-10 implements Slovo exp-10 Result-Based Host Errors Alpha. It is +released experimental compiler support, not beta. + +The release promotes exactly one new concrete result family, +`(result string i32)`, and four additive host calls: + +- `std.process.arg_result : (i32) -> (result string i32)` +- `std.env.get_result : (string) -> (result string i32)` +- `std.fs.read_text_result : (string) -> (result string i32)` +- `std.fs.write_text_result : (string string) -> (result i32 i32)` + +Ordinary host failure returns `err 1`. The exp-3 calls remain unchanged: +argument/read failures still trap, missing environment variables still return +`""`, and writes still return `0` or `1`. + +### Release State + +exp-10 is released experimental compiler support after formatter, checker, +lowering, diagnostics, test-runner, LLVM/runtime, fixture-alignment, release +review, and release gates passed. + +## exp-9 + +Release label: `exp-9` + +Release date: 2026-05-18 + +### Summary + +Glagol exp-9 is the released experimental hardening slice over the released +exp-8 surface. The exp-9 work includes compatibility inventory, migration +guide, source-reachable panic audit, benchmark smoke notes, release gate +evidence, and deterministic hardening tests in +`compiler/tests/reliability_hardening.rs`. + +Controller verification has passed the focused exp-9 hardening test and the +full local release gate. Benchmark smoke records structured +`ToolchainUnavailable` for hosted native-build checks because `clang` was not +on PATH in this environment. + +exp-9 is hardening-only. It does not add source syntax, type-system behavior, +standard-runtime names, runtime capabilities, package features, manifest schema +versions, stable ABI/layout promises, public performance thresholds, or beta +maturity. + +### Release State + +exp-9 is released experimental compiler support after matching Slovo and +Glagol gates. + +## exp-8 + +Release label: `exp-8` + +Release date: 2026-05-18 + +### Summary + +Glagol exp-8 implements the conservative host time and sleep alpha. It adds +exactly two compiler-known standard-runtime calls: + +- `std.time.monotonic_ms : () -> i32` +- `std.time.sleep_ms : (i32) -> unit` + +`std.time.monotonic_ms` returns non-negative process-relative host monotonic +elapsed milliseconds, saturating to `i32::MAX`. `std.time.sleep_ms` accepts +non-negative millisecond durations; `0` is valid and deterministic in the test +runner. + +### Diagnostics And Limits + +Negative sleeps trap as `slovo runtime error: sleep_ms negative duration`. +Hosted runtime failure to read monotonic time traps as +`slovo runtime error: monotonic time unavailable`; non-negative sleep failure +may trap as `slovo runtime error: sleep failed`. + +exp-8 adds no source syntax, wall-clock/calendar API, time zones, +high-resolution clock contract, timers, threads, channels, async behavior, +scheduling guarantees, cancellation, or stable helper ABI. The current +artifact manifest has no general standard-runtime usage structure, so exp-8 +does not add one-off time metadata. + +## exp-7 + +Release label: `exp-7` + +Release date: 2026-05-18 + +### Summary + +Glagol exp-7 implements the experimental test selection and test-run metadata +alpha. `glagol test --filter ` and the legacy +single-file `glagol --run-tests --filter ` route select +tests by deterministic, case-sensitive display-name substring matching. + +Selected tests execute in existing order. Non-selected discovered tests are +skipped before evaluation and counted as skipped. Zero matches is a successful +run. Text output and artifact manifests expose `total_discovered`, `selected`, +`passed`, `failed`, `skipped`, and optional `filter` metadata. + +### Diagnostics And Limits + +Missing filter values, repeated `--filter`, and use of `--filter` outside test +mode are command-line diagnostics. The exp-6 C-import test-runner boundary is +unchanged: selected tests that reach imported C calls report +`UnsupportedTestExpression`, while non-selected C-import tests are skipped +before evaluation. + +LSP/editor protocols, debug metadata, source maps, SARIF, watch/daemon +behavior, documentation comments, lint categories, benchmark behavior, test +tags/groups/retries/parallelism, and new Slovo source syntax are deferred. +exp-7 is experimental, not beta. + +## exp-6 + +Release label: `exp-6` + +Release date: 2026-05-18 + +### Summary + +Glagol exp-6 implements the experimental C FFI scalar imports alpha. It accepts +top-level imports such as: + +```slo +(import_c c_add ((lhs i32) (rhs i32)) -> i32) +``` + +Imported C functions use conservative C symbol names, accept only `i32` +parameters, and return only `i32` or builtin `unit`. Calls to imported C +functions are allowed only inside lexical `(unsafe ...)`; calls outside unsafe +report `UnsafeRequired`. + +Native builds lower imports to LLVM external declarations and direct calls. +`glagol build --link-c ` links explicit local C source inputs, and +artifact manifests record `foreign_imports` plus `c_link_inputs`. + +### Diagnostics And Limits + +The interpreter-style test runner does not execute imported C calls in exp-6; +tests that reach a C import report `UnsupportedTestExpression`. Raw unsafe +heads, including `ffi_call`, remain unsupported operations. + +Pointers, raw allocation/deallocation, raw unsafe head execution, +ownership/lifetime rules, C exports, callbacks, headers/libraries, broad +linker configuration, and stable ABI/layout are deferred. exp-6 is +experimental, not beta. + +## exp-5 + +Release label: `exp-5` + +Release date: 2026-05-18 + +### Summary + +Glagol exp-5 implements the experimental local packages/workspaces alpha. A +workspace `slovo.toml` can declare `[workspace] members`, and each member can +declare `[package] name`, `version`, `source_root`, `entry`, plus local +`[dependencies]` entries such as `mathlib = { path = "../mathlib" }`. + +The implemented slice builds a deterministic closed local package graph, +accepts package-qualified imports such as `(import mathlib.math (add_one))`, +checks cross-package visibility through existing module export lists, and +records the workspace/package graph in artifact manifests. + +### Diagnostics + +The exp-5 diagnostic gate covers missing package manifests or packages, +duplicate package names, dependency cycles, workspace/dependency/source-root +path escapes, invalid package names, invalid package versions, private +cross-package visibility, and dependency key/name mismatches. + +### Explicit Deferrals + +Registries, lockfiles, semver solving, aliases, globs, re-exports, generated +code, build scripts, publishing, optional/dev/target dependencies, stable +package ABI, and remote dependencies remain outside this experimental slice. +exp-5 is not beta and is not package-manager support. + +## exp-4 + +Release label: `exp-4` + +Release date: 2026-05-18 + +### Summary + +Glagol exp-4 implements experimental payloadless user-defined enum support. +Enums can be declared with one or more unique variants, constructed through +qualified constructors such as `(Signal.Red)`, compared for equality when both +operands have the same enum type, passed through immutable locals, parameters, +returns, calls, and top-level tests, and matched exhaustively by variant. + +The promoted release fixture is `enum-basic.slo`, byte-aligned with Slovo's +supported fixture. Lowering inspector snapshots cover both surface and checked +enum forms, and diagnostics snapshots cover the exp-4 boundary cases. + +### Explicit Deferrals + +Payload variants, generic ADTs, type aliases, traits, methods, unqualified +variant constructors, enum mutation, enum printing, enum ordering, enum values +inside containers, stable layout, and stable ABI claims remain outside this +slice. Unqualified variant constructor calls currently report the generic +`UnknownFunction` diagnostic. + +### Gates + +The exp-4 gate covers Slovo/Glagol fixture equality for `enum-basic.slo`, +formatter and lowering-inspector fixtures, enum LLVM discriminant shape, +top-level enum tests, and golden machine diagnostics for supported boundary +failures. + +## exp-3 + +Release label: `exp-3` + +Release date: 2026-05-17 + +### Summary + +Glagol exp-3 implements the conservative standard IO and host-environment +slice. It adds compiler-known standard-runtime functions: + +- `std.io.eprint : (string) -> unit` +- `std.process.argc : () -> i32` +- `std.process.arg : (i32) -> string` +- `std.env.get : (string) -> string` +- `std.fs.read_text : (string) -> string` +- `std.fs.write_text : (string string) -> i32` + +Missing environment variables return an empty string. `write_text` returns `0` +on success and `1` on host failure. `eprint` writes to stderr without adding a +newline. Native programs initialize a private runtime process view from host +`argc`/`argv`. + +Runtime traps are: + +- `slovo runtime error: process argument index out of bounds` +- `slovo runtime error: file read failed` + +The runtime helper symbols are private implementation details and are reserved +from source/project names. exp-3 does not stabilize host error payloads, file +ownership/lifetime behavior, process layout, runtime ABI, or C helper symbols. + +### Explicit Deferrals + +Networking, async IO, binary IO, directory traversal, terminal control, stdin +full-read/iteration, time, randomness, result-string host errors, and stable +ABI/layout claims remain outside this slice. + +### Gates + +The exp-3 gate covers checker diagnostics, lowering-inspector fixtures, LLVM +runtime-call shape, interpreted host behavior where practical, hosted runtime +smoke tests, and trap behavior for process arguments and file reads. + +## exp-2 + +Release label: `exp-2` + +Release date: 2026-05-17 + +### Summary + +Glagol exp-2 implements the first collections alpha slice from the Slovo +contract. It adds one concrete growable vector type, `(vec i32)`, plus +compiler-known standard-runtime functions: + +- `std.vec.i32.empty : () -> (vec i32)` +- `std.vec.i32.append : ((vec i32), i32) -> (vec i32)` +- `std.vec.i32.len : ((vec i32)) -> i32` +- `std.vec.i32.index : ((vec i32), i32) -> i32` + +`append` returns a new immutable runtime-owned vector. `(vec i32)` values can +flow through immutable locals, parameters, returns, calls, top-level tests, +formatter output, lowering inspection, LLVM, runtime C, and the test runner. +Vector equality with `=` compares `(vec i32)` values by length and element +contents. + +Runtime traps are: + +- `slovo runtime error: vector allocation failed` +- `slovo runtime error: vector index out of bounds` + +The runtime helper symbols are private implementation details and are reserved +from source/project names. exp-2 does not stabilize vector layout, C ABI, +allocator behavior, lifetime, destruction, FFI, or ownership semantics. + +### Explicit Deferrals + +Generic vectors, non-`i32` elements, vector mutation through `var` or `set`, +`push` aliases, nested vectors, vectors inside arrays/structs/options/results, +slices, maps, sets, user-visible allocation/deallocation, and stable layout or +ABI promises remain outside this slice. + +### Gates + +The exp-2 release gate covers checker diagnostics, formatter stability, +lowering-inspector fixtures, LLVM runtime-call shape, top-level test-runner +evaluation, hosted runtime smoke tests, vector trap behavior, allocation-trap +probing, promotion-gate inventory, and Slovo fixture alignment without editing +Slovo fixtures. + +## exp-1 + +Release label: `exp-1` + +Release date: 2026-05-17 + +### Summary + +Glagol exp-1 implements the first owned-runtime-string slice from the Slovo +contract. It adds compiler-known `std.string.concat` with signature +`(string, string) -> string`, returning an immutable runtime-owned `string`. +Existing string equality, `std.string.len`, `std.io.print_string`, string +locals, parameters, returns, and calls can use the result. + +No legacy `string_concat` alias is accepted. The runtime helper is private +implementation detail, not a stable ABI, layout, allocator, lifetime, or FFI +promise. + +### Gates + +The exp-1 release gate covered checker diagnostics, formatter stability, +lowering-inspector fixtures, LLVM runtime-call shape, top-level test-runner +evaluation, hosted runtime smoke tests, and Slovo fixture alignment. + +## v2.0.0-beta.1 + +Release tag: `v2.0.0-beta.1` + +Release date: 2026-05-17 + +### Summary + +Glagol `v2.0.0-beta.1` is an experimental gate/readiness release over the +completed v1.7 toolchain. It does not add new Slovo source syntax or +semantics. The release documents the current supported surface and proves the +integrated experimental workflow through project scaffolding, `fmt --check`, +`test`, `doc`, and hosted `build` when the host toolchain is available. + +### Experimental-Supported Surface + +- v1.7 CLI and project tooling, including `new`, `fmt --check`, `fmt --write`, + `doc`, `test`, and hosted `build`. +- Flat local project mode with explicit imports and exports. +- v1.5 standard-runtime alpha as the basic IO/string subset: + `std.io.print_i32`, `std.io.print_string`, `std.io.print_bool`, and + `std.string.len`. +- Fixed-size immutable `i32` arrays as the current collection subset. +- Existing v1.7 language behavior for strings, structs, option/result values, + exact option/result `match`, and lexical unsafe diagnostics. + +### Explicit Deferrals + +At tag time, the experimental release deferred vectors and growable +collections, packages and registries, stable ABI/layout, FFI, executable +raw-memory behavior, LSP/watch mode, stable debug metadata, source-map files, +direct machine-code output, and self-hosting until future beta work. The later +`exp-2` slice now implements only the narrow `(vec i32)` collections alpha. + +### Release Gates + +The experimental gate runs through `scripts/release-gate.sh`: whitespace checks, +`cargo fmt --check`, full `cargo test`, and the ignored promotion, binary, and +LLVM smoke gates. Full `cargo test` includes the v1.7 DX gate and the +experimental workflow proof. + +## v1.7 + +Release tag: `v1.7` + +Release date: 2026-05-17 + +### Summary + +Glagol v1.7 is a Developer Experience Hardening release. It adds conservative +tooling around the existing Slovo v1.6 language surface and does not introduce +new Slovo syntax or semantics. + +### Tooling Additions + +- `glagol new [--name ]` scaffolds a minimal project with + `slovo.toml` and `src/main.slo`. +- `glagol fmt --check ` verifies canonical formatting without + writes and reports `FormatCheckFailed` when a file would change. +- `glagol fmt --write ` writes canonical formatting in place + and emits no stdout on success. +- `glagol doc -o ` writes deterministic Markdown + documentation to `index.md`. +- `scripts/release-gate.sh` runs the local release gate. + +### Explicit Deferrals + +v1.7 does not add package management, workspaces, nested modules, import aliases, +semantic reflection, LSP/watch mode, new formatter semantics beyond existing +project syntax, or any new Slovo language behavior. + +## v1.6 + +Release tag: `v1.6` + +Release date: 2026-05-17 + +### Summary + +Glagol v1.6 is the Memory And Unsafe Design Slice release. It mirrors Slovo's +conservative v1.6 contract by hardening the reserved unsafe vocabulary and +lexical unsafe diagnostics without promoting executable raw-memory behavior. +The v1.1 through v1.5 sections below are historical release records. + +The compiler-known unsafe operation heads are: + +- `alloc` +- `dealloc` +- `load` +- `store` +- `ptr_add` +- `unchecked_index` +- `reinterpret` +- `ffi_call` + +### Diagnostics And Reservation + +Outside lexical `(unsafe ...)`, each unsafe head reports `UnsafeRequired` +before user-function lookup. Inside lexical `(unsafe ...)`, each head reports +`UnsupportedUnsafeOperation`. + +These names are reserved from user functions, project imports and exports, +parameters, locals, structs, and match payload bindings where the current +reservation architecture applies. This keeps the vocabulary available for a +future contract without allowing accidental shadowing today. + +### Explicit Deferrals + +v1.6 does not introduce raw pointers, allocation/deallocation semantics, +pointer load/store, pointer arithmetic, unchecked indexing execution, +reinterpretation, FFI calls, stable ABI/layout promises, runtime C symbols, or +raw-memory LLVM lowering. + +### Release Gates + +v1.6 verification covers unsafe-required and unsupported-inside-unsafe +diagnostic snapshots for every unsafe head, focused reservation tests for +local source and project visibility paths, existing lexical unsafe formatter +and lowering fixtures, and regressions proving no raw unsafe operation reaches +LLVM lowering. + +## v1.5 + +Release tag: `v1.5` + +Release date: 2026-05-17 + +### Summary + +Glagol v1.5 is the Standard Library Alpha release. It mirrors Slovo's +conservative v1.5 contract by promoting stable source-level standard-runtime +names for existing runtime behavior without adding modules, imports, packages, +new IO breadth, allocation, Unicode length semantics, or ABI promises. +The v1.1 through v1.4 sections below are historical release records. + +v1.5 promotes: + +- `std.io.print_i32` +- `std.io.print_string` +- `std.io.print_bool` +- `std.string.len` + +These are compiler-known source-level standard-runtime names. They do not +require imports, do not name user modules, do not create package dependencies, +are not foreign functions, and are not stable C ABI symbols. + +Legacy `print_i32`, `print_string`, `print_bool`, and `string_len` remain +compatibility aliases. New examples should prefer the promoted `std.*` names. + +### Diagnostics And Deferrals + +Unknown or unpromoted `std.*` calls must produce structured diagnostics, with +`UnsupportedStandardLibraryCall` as the suggested code. Arity and type +mismatches use the existing diagnostics where applicable. The exact promoted +`std.*` names are reserved from user function/export shadowing. + +v1.5 does not support `std.io.print_unit`, file IO, environment variables, +process arguments, time or clocks, collections, allocation, deallocation, +ownership, user-defined standard modules, imports or packages for +standard-runtime names, overloading, generic standard-library APIs, Unicode +scalar/grapheme/display-width string length, stable C ABI symbols, runtime +layout promises, or FFI contracts. + +### Release Gates + +v1.5 verification covers parser/lowerer/checker/formatter support +for the promoted `std.*` calls, aligned `examples/standard-runtime.slo` and +`tests/standard-runtime.slo` fixtures, lowering-inspector snapshots, runtime +smoke tests matching legacy stdout and byte-length behavior, diagnostic +snapshots for the v1.5 boundaries, and regressions proving v1.4, v1.3, and +v1.2 behavior remains unchanged. + +## v1.4 + +Release tag: `v1.4` + +Release date: 2026-05-17 + +### Summary + +Glagol v1.4 is the Core Language Expansion release. It mirrors Slovo's +conservative source-level `match` contract for existing option/result values +without widening into enums, generics, mutation, vectors, or ABI promises. + +- Promotes `match` for exactly `(option i32)` and `(result i32 i32)` subjects. +- Requires exhaustive two-arm forms: `some` and `none` for options, `ok` and + `err` for results. +- Adds immutable `i32` payload binding for `some`, `ok`, and `err`; payload + bindings are visible only inside the selected arm body. +- Treats each arm body as an arm-scoped sequential body with one or more + expressions; the final expression is the arm value. +- Types the `match` expression as the common arm result type. +- Keeps existing `is_some`, `is_none`, `is_ok`, `is_err`, `unwrap_some`, + `unwrap_ok`, and `unwrap_err` supported, while new examples should prefer + `match` for payload-dependent option/result control flow. + +### Diagnostics And Formatting + +The v1.4 diagnostic boundary requires stable codes for match subject type +mismatches, unsupported payload types, non-exhaustive matches, duplicate arms, +malformed patterns, arm result type mismatches, binding collisions, unsupported +mutation, and unsupported option/result container positions. + +The formatter target is a canonical multiline `match` shape: the head and +subject stay on the opening line, each arm starts on its own line, pattern +lists stay inline, and body expressions use the normal nested body indentation. +The formatter preserves arm order and does not repair malformed arm sets. + +### Deferred From v1.4 + +- User-defined enums or algebraic data types. +- Generic, non-`i32`, or nested option/result payload matching beyond the + current v1 limits. +- Options/results inside arrays, structs, or other containers. +- Mutation of option/result values, struct fields, arrays, or vectors. +- `i64` or broader numeric expansion. +- General `(block ...)` expressions outside match arms. +- Pattern guards, wildcard/rest/catch-all patterns, and destructuring. +- Result-oriented standard-library error conventions. +- User-catchable exceptions, ownership, lifetime, layout, or ABI guarantees. + +### Release Gates + +v1.4 verification covers parser/lowerer/checker/formatter support for the +promoted match surface, fixture alignment with Slovo +`examples/supported/option-result-match.slo`, formatter idempotence for Slovo +`examples/formatter/option-result-match.slo`, lowering-inspector snapshots, +LLVM or runtime smoke tests for arm selection and payload binding, diagnostic +snapshots for every required match code, and regressions proving v1.3 project +mode and v1.2 runtime values remain unchanged. + +## v1.3 + +Release tag: `v1.3` + +Release date: 2026-05-17 + +### Summary + +Glagol v1.3 implements Slovo's conservative Project Mode And Modules contract. +The release target is local, manifest-based, flat multi-file projects while +preserving v1.2 single-file `.slo` command behavior. + +- Project manifests are named `slovo.toml` and contain `[project]` with + required `name`, optional `source_root = "src"`, and optional + `entry = "main"`. `name` is lowercase package-shaped text, `source_root` + must remain under the project root, `entry` is a flat module identifier, and + unknown sections or keys are diagnostics. +- `glagol check`, `glagol test`, and `glagol build` enter project mode only + when given a project root or the `slovo.toml` file itself. +- Project source discovery is limited to immediate `.slo` files under the flat + source root. +- Modules can declare explicit exports, for example + `(module math (export add_one Point))`. +- Modules can import explicit exported names, for example + `(import math (add_one Point))`. +- Project diagnostics preserve multi-file source identity and cover manifest + failures, missing imports, import cycles, duplicate names, visibility + failures, ambiguous imports, and unsupported path/module boundaries. +- Project artifact manifests record project roots, manifests, source roots, + modules, import edges, diagnostics, tests, and build outputs in deterministic + order. + +### Deferred From v1.3 + +- Packages, package namespaces, dependencies, remote registries, lockfiles, and + workspaces. +- Import aliases, renames, glob imports, qualified imports, conditional + imports, and re-exports. +- Nested or hierarchical modules. +- Project formatting, write-in-place formatting, and directory formatting. +- Path escapes from the project root or source root, generated sources, watch + mode, LSP/editor protocols, and incremental builds. +- Build profiles, target triples, cross-compilation, object/library/header + outputs, package artifacts, public ABI promises, and stable debug metadata. + +### Release Gates + +v1.3 verification covers manifest parsing and diagnostics, project +`check`/`test`/`build` command behavior, `examples/projects/basic` fixture +alignment, diagnostic-family checks for import/export failures, deterministic +module-order checks, cross-file related-span checks, project artifact-manifest +snapshots for success and failure paths, and single-file regressions proving +v1.2 mode is unchanged. + +## v1.2 + +Release tag: `v1.2` + +Release date: 2026-05-17 + +### Summary + +Glagol v1.2 implements the Slovo v1.2 Practical Runtime Values contract while +preserving the v1.1 single-file CLI/toolchain surface. + +- Immutable string value flow through string `let` locals, parameters, returns, + and calls returning strings. +- String equality through `=`. +- String byte length through temporary compiler/runtime intrinsic `string_len`. +- Boolean printing through temporary compiler/runtime intrinsic `print_bool`. +- Deterministic runtime trap messages and process exit code `1` for array + bounds failures and option/result unwrap failures. + +### Deferred From v1.2 + +- `print_unit`. +- Mutable strings, string `var` locals, and `set` of string locals. +- String concatenation, indexing, slicing, ownership, allocation/free, and + stable ABI/layout/FFI promises. +- Strings inside arrays, structs, options, or results. +- Project mode, imports, package management, and multi-file modules. +- Any change to the v1.1 CLI/toolchain surface. + +### Release Gates + +Final v1.2 verification includes the existing v1.1 gates plus fixture alignment +for `string-value-flow.slo` and `print-bool.slo`, formatter idempotence, +lowering-inspector snapshots, diagnostic snapshots for deferred forms, runtime +smoke tests for strings and traps, and process-exit tests for runtime traps. + +## v1.1 + +Release tag: `v1.1` + +Release date: 2026-05-17 + +## Summary + +Glagol v1.1 productizes the hosted single-file toolchain without expanding the +Slovo v1 language surface. + +### Canonical Commands + +- Added `glagol check ` for parse/lower/check/backend-support + validation with no primary output. +- Added `glagol fmt ` as the canonical formatter command. +- Added `glagol test ` as the canonical top-level test runner. +- Added `glagol build -o ` for hosted native builds through + emitted LLVM IR, `runtime/runtime.c`, and Clang. +- Preserved v1 compatibility aliases: default LLVM emission, `--emit=llvm`, + `--format`, `--print-tree`, `--inspect-lowering=surface`, + `--inspect-lowering=checked`, `--check-tests`, and `--run-tests`. + +### Diagnostics, Manifests, And Exit Codes + +- Added `--json-diagnostics` newline-delimited JSON diagnostics using + `slovo.diagnostic` schema version `1`. +- Added `--no-color` across canonical commands and compatibility modes. +- Extended artifact manifests with command modes, diagnostics encoding, + no-output check manifests, build output artifacts, and failure manifests when + `--manifest ` has been parsed. +- Implemented the v1.1 exit-code table: `0` success, `1` source/input/test + failure, `2` usage error, `3` hosted toolchain failure, `4` internal compiler + error, and `5` manifest or output artifact write failure. + +### Hosted Build + +- `glagol build` locates Clang through `GLAGOL_CLANG` or `PATH`. +- Missing Clang, missing runtime C, and failed Clang execution are reported as + toolchain failures with exit code `3`. +- Native output is staged through a temporary file in the destination directory + before replacing the requested `-o` path on success. +- Project mode, profiles, cross targets, object/library outputs, custom linker + flags, and stable ABI promises remain deferred. + +## v1 + +Release tag: `v1` + +Release date: 2026-05-17 + +## Summary + +Glagol v1 is the compiler release for the Slovo v1 contract. It keeps the +tree-first compiler pipeline visible, expands value flow beyond v0, and hardens +the CLI/tooling surface without claiming direct native executable output. + +The default successful primary output remains LLVM IR text. Native execution is +the explicit external workflow: emitted LLVM IR plus `runtime/runtime.c` and +Clang. + +### Promoted v1 Value Flow + +- Added immutable struct locals, struct parameters, struct returns, calls + returning structs, and stored field access for the promoted Slovo v1 struct + slice. +- Added immutable fixed-size `i32` array locals, parameters, returns, calls + returning arrays, and runtime-checked dynamic indexing with a runtime trap. +- Added option/result value flow for the `i32` subset, including immutable + option/result locals, parameters, calls returning option/result values, tag + observation, and explicit payload extraction with trap-based invalid unwraps. +- Added a narrow string runtime slice: direct string literals lower to borrowed + immutable NUL-terminated static storage as LLVM `ptr`, and the temporary + `print_string` intrinsic prints one string plus a newline through the runtime. +- Kept full option/result matching, full string value flow, broad runtime + printing, raw unsafe memory/FFI operations, stable ABI/layout promises, and + stable debug metadata deferred for the v1 scope freeze. + +### CLI Polish + +- Added `--emit=llvm` as the explicit spelling for the default LLVM IR emission + mode. +- Added `-o ` to route successful primary output to a file instead of + stdout. +- Added `--version`, reported from Cargo package metadata. +- Kept stdin source input deferred until source naming and span behavior are + specified. +- Kept JSON diagnostics and lowering-inspection output deferred until the text + contract needs a versioned machine-readable companion. + +### Slovo v1 Tooling Contract + +- Updated machine diagnostics to the Slovo v1 textual schema with root + `(diagnostic ...)`, `(schema slovo.diagnostic)`, and `(version 1)`. +- Added `--manifest ` to write `slovo.artifact-manifest` version `1` + manifests for successful primary modes and source-diagnostic failures. +- Added `UnsupportedUnitSignatureType` diagnostics for user-written `unit` + parameter and return signatures while keeping `unit` internal to + compiler/runtime unit-producing forms. +- Added golden lowering-inspector snapshots for every promoted v1 fixture in + both surface and checked modes. +- Audited the golden diagnostic matrix and added snapshots for the current + unsupported string escape, string literal, and bare string expression + boundaries. +- Kept primary output on stdout or `-o` and diagnostics on stderr; direct native + executable output remains deferred behind the LLVM-plus-Clang workflow. +- Clarified that artifact manifests are invocation records for textual + artifacts, not native executable output. + +### Formatter And Inspection Stability + +- Added the v1 formatter stability contract for supported comments, nested + promoted forms, and long inline forms. +- Added full-line comment preservation fixtures and structured + `UnsupportedFormatterComment` rejection for unsupported comment positions, + including same-line/trailing comments. +- Added golden `--inspect-lowering=surface` and + `--inspect-lowering=checked` snapshots for every promoted v1 fixture family, + including formatter comments/stability fixtures accepted by inspector mode. +- Expanded the golden diagnostic matrix for Slovo's required rejected v1 + boundaries: struct duplicate/unknown/mutation shapes, non-`i32` + option/result signature payloads, and array promoted-flow type mismatches. + +### Release Gates + +Final v1 verification covers: + +```text +cargo test +cargo fmt --check +cargo test --test promotion_gate -- --ignored +cargo test --test binary_smoke -- --ignored +cargo test --test llvm_smoke -- --ignored +git diff --check +``` + +## v0 + +Release tag: `v0` + +Release date: 2026-05-16 + +Release commit: `4ccd201` + +## Summary + +Glagol v0 is the first released compiler for Slovo. It implements the Slovo v0 +language contract as a Rust binary CLI that parses, formats, checks, tests, +inspects, lowers, and emits LLVM IR for the supported fixture set. + +v0 intentionally keeps the compiler surface small and explicit. The default +output is LLVM IR text; direct executable output remains deferred. + +## CLI Surface + +Supported v0 commands: + +```text +glagol +glagol --format +glagol --print-tree +glagol --inspect-lowering=surface +glagol --inspect-lowering=checked +glagol --check-tests +glagol --run-tests +glagol test --filter +glagol --run-tests --filter +``` + +CLI behavior: + +- success writes primary output to stdout and exits `0` +- source diagnostics write human and machine diagnostics to stderr and exit `1` +- input/read failures exit `1` +- usage errors exit `2` +- `--help` writes usage information to stderr and exits `0` +- experimental exp-7 test filtering selects by case-sensitive test display-name + substring, skips non-selected tests before evaluation, and records + `total_discovered`, `selected`, `passed`, `failed`, `skipped`, and optional + `filter` metadata in test reports/manifests + +## Supported Fixture Set + +Glagol v0 supports these compiler fixtures: + +```text +examples/add.slo +examples/array.slo +examples/if.slo +examples/local-variables.slo +examples/option-result.slo +examples/struct.slo +examples/unsafe.slo +examples/while.slo +tests/top-level-test.slo +``` + +These are aligned with Slovo's `examples/supported/` and +`examples/formatter/` fixtures by the ignored monorepo alignment gate. + +## Compiler Pipeline + +The v0 pipeline is: + +```text +.slo source +-> tokens +-> S-expression tree +-> AST +-> typed AST +-> LLVM IR text +``` + +The compiler keeps source-reachable failures diagnostic-first. Checked source +forms should not reach backend panics. + +## Test Coverage + +The v0 suite covers: + +- supported fixture LLVM shape +- canonical formatter behavior +- parse-tree printing +- surface and checked lowering inspection +- top-level test checking and running +- structured machine diagnostics +- source spans and line/column ranges +- value-producing `if` +- locals and assignment +- `while` +- structs +- arrays and literal indexing +- option/result constructors +- lexical `unsafe` +- CLI usage and exit codes + +## Explicit Deferrals + +Glagol v0 does not implement: + +- direct executable output +- `-o ` +- explicit `--emit=llvm` +- stdin source input +- `--version` +- runtime strings +- dynamic array indexing and runtime bounds traps +- complete struct, array, and option/result value flow +- raw memory operations +- FFI +- stable ABI/layout promises +- package management +- direct machine-code backend + +## Post-v0 Direction + +Post-v0 work starts in `.llm/V1_ROADMAP.md` and should remain locked to Slovo's +`SPEC-v1.md`. + +The recommended first compiler slice is struct value flow after Slovo specifies +the v1 contract for struct locals, parameters, returns, and stored field access. diff --git a/docs/compiler/ROADMAP.md b/docs/compiler/ROADMAP.md new file mode 100644 index 0000000..98470b2 --- /dev/null +++ b/docs/compiler/ROADMAP.md @@ -0,0 +1,1813 @@ +# Glagol Roadmap + +Glagol is the first compiler for Slovo. Slovo language design lives in `../language`. + +Compiler rule: make the tree visible. The pipeline should remain: + +```text +.slo source -> tokens -> S-expression tree -> AST -> typed AST -> LLVM IR text -> hosted Clang/runtime executable step +``` + +Long-horizon compiler planning lives in +`.llm/GENERAL_PURPOSE_LANGUAGE_ROADMAP.md`. It mirrors Slovo's experimental +release train from the historical `v2.0.0-beta.1` tag toward and beyond the +first real general-purpose beta toolchain. + +Release maturity policy lives in `.llm/RELEASE_MATURITY_POLICY.md`. Historical +`exp-*` releases remain experimental maturity. `1.0.0-beta` is the first real +general-purpose beta release. + +## Definition Of Done For A Compiler Feature + +A Glagol feature is done only when it has parser/lowerer support, checker behavior, diagnostics for invalid forms, backend behavior or explicit unsupported diagnostics, and tests. + +Current stage: `1.0.0-beta`, released on 2026-05-21 as the first real +general-purpose beta Glagol toolchain. The beta release integrates the +completed unsigned `u32`/`u64` compiler and stdlib breadth scope from +`exp-125` with the already promoted project/package workflow, explicit +std-source imports, concrete vector families, fixed arrays, structs, enums, +result/option, generated docs, formatter behavior, and structured diagnostics. + +The final experimental precursor scope is `exp-125`. Its unsigned direct-value +flow, parse/format runtime lanes, and matching staged stdlib helper breadth +are now absorbed into `1.0.0-beta`. + +Phases 9 through 84 and exp-1 through exp-124 remain historical experimental +release milestones. `exp-124` Fixed Array Enum And Struct Elements Alpha is +the last tagged experimental compiler slice before beta. exp-123 Owned Vector +Benchmark Suite Extension And Whitepaper Refresh Alpha is the previous tagged +experimental benchmark/publication slice. exp-122 Composite Data Benchmark +Suite Extension And Whitepaper Refresh Alpha is the earlier tagged +experimental benchmark/publication slice. exp-121 Non-Recursive Struct Enum +Payloads Alpha and exp-120 Fixed Array Struct Fields Alpha remain earlier +historical compiler broadening steps in the path to beta. +exp-115 Composite Struct Fields And +Nested Structs Alpha is the previous gated experimental compiler slice. It +broadens direct struct field declarations by allowing the current concrete vec +families, current concrete option/result families, and current non-recursive +struct types alongside the already promoted direct scalar, string, and enum +field families in the checker, formatter, diagnostics, promoted +composite-struct fixtures, and focused Glagol tests, while keeping arrays out +of struct fields and excluding recursive layouts, field mutation, and payload +mutation. exp-114 Mutable Composite Locals Alpha is the previous gated +experimental compiler slice. It broadened the current local-mutation lane by +allowing same-type mutable local reassignment through `var`/`set` for +`string`, the current concrete vec families, the current concrete +option/result families, known struct values, and current enum values in the +checker, formatter local rendering, LLVM emission, diagnostics coverage, +promoted composite-local fixtures, and focused Glagol tests, while keeping +arrays immutable-only and excluding field, element, and payload mutation. +exp-113 Mutable Scalar Locals Alpha is the previous gated +experimental compiler slice. It broadened the current local-mutation lane by +allowing mutable `bool`, `i64`, and `f64` locals through `var`/`set` in the +checker, formatter messaging, LLVM emission, diagnostics coverage, promoted +local-variable fixtures, and focused Glagol tests, while keeping mutable +strings, vectors, option/results, structs, and enums unsupported. exp-112 +Immutable Bool Locals Alpha is the previous gated experimental compiler +slice. It broadened only the immutable local-value lane by allowing +`let`-bound `bool` locals in the checker, LLVM emission, diagnostics +coverage, and focused Glagol tests, while keeping mutable `bool` locals +unsupported. exp-111 Standard IO Stdin Source Helpers Alpha is the previous +gated experimental stdlib/source slice. It +broadens the existing Glagol-owned local and explicit-source `std.io` +helper fixtures with `read_stdin_result`, `read_stdin_option`, +`read_stdin_or`, `read_stdin_i32_result`, `read_stdin_i32_option`, +`read_stdin_i32_or_zero`, `read_stdin_i32_or`, `read_stdin_i64_result`, +`read_stdin_i64_option`, `read_stdin_i64_or_zero`, `read_stdin_i64_or`, +`read_stdin_f64_result`, `read_stdin_f64_option`, +`read_stdin_f64_or_zero`, `read_stdin_f64_or`, +`read_stdin_bool_result`, `read_stdin_bool_option`, +`read_stdin_bool_or_false`, and `read_stdin_bool_or`, while keeping the +slice source-authored over the already promoted `std.io.read_stdin_result`, +`std.string.parse_*_result`, and exp-109 `ok_or_none_*` bridge helpers only. +exp-110 Standard String, Env, Fs, Process, And Cli Option Helpers Alpha is +the previous gated experimental stdlib/source slice. It broadens the +existing Glagol-owned local and explicit-source `std.string`, `std.env`, +`std.fs`, `std.process`, and `std.cli` helper fixtures with +`parse_i32_option`, `parse_i64_option`, `parse_f64_option`, +`parse_bool_option`, `get_option`, `get_i32_option`, `get_i64_option`, +`get_f64_option`, `get_bool_option`, `read_text_option`, +`read_i32_option`, `read_i64_option`, `read_f64_option`, +`read_bool_option`, `arg_option`, `arg_i32_option`, `arg_i64_option`, +`arg_f64_option`, `arg_bool_option`, `arg_text_option`, +`arg_i32_option`, `arg_i64_option`, `arg_f64_option`, and +`arg_bool_option`, while keeping the slice source-authored over the already +promoted result families and the exp-109 `ok_or_none_*` bridge helpers only. +exp-109 Standard Option And Result Bridge Helpers Alpha is the previous +gated experimental stdlib/source slice. It broadens the Glagol-owned local, +explicit-source, and workspace-source `std.option` and `std.result` helper +fixtures with `some_or_err_i32`, `some_or_err_i64`, `some_or_err_f64`, +`some_or_err_bool`, `some_or_err_string`, `ok_or_none_i32`, +`ok_or_none_i64`, `ok_or_none_string`, `ok_or_none_f64`, and +`ok_or_none_bool`, while keeping the slice concrete and source-authored over +the already promoted option/result forms only. exp-108 Standard Vec String, +F64, And Bool Prefix And Suffix Helpers Alpha is the earlier gated +experimental stdlib/source slice. It broadens the Glagol-owned local and +explicit-source `std.vec_string`, `std.vec_f64`, and `std.vec_bool` helper +fixtures with `starts_with`, `without_prefix`, `ends_with`, and +`without_suffix`, while keeping all three lanes recursive, immutable, and +limited to the already promoted vec-string, vec-f64, and vec-bool runtime +names. exp-107 Standard Vec String, F64, And Bool Edit Helpers Alpha is the +earlier gated experimental stdlib/source slice. It broadens the same helper +fixtures with `insert_at`, `insert_range`, `replace_at`, `replace_range`, +`remove_at`, and `remove_range` while keeping all three lanes recursive, +immutable, and limited to the already promoted vec-string, vec-f64, and +vec-bool runtime names. exp-106 Standard Vec F64 And Bool Option Query +Helpers Alpha is the earlier gated experimental stdlib/source slice. It +broadens the `std.vec_f64` and `std.vec_bool` helper fixtures with +`index_option`, `first_option`, `last_option`, `index_of_option`, and +`last_index_of_option` while keeping both lanes recursive, immutable, and +limited to the already promoted vec-f64 and vec-bool runtime names plus the +already promoted concrete option families they return. exp-105 Standard Vec +F64 And Bool Transform Helpers Alpha is the earlier gated experimental +stdlib/source slice. It broadens the same helper fixtures with `concat`, +`take`, `drop`, `reverse`, and `subvec` while keeping both lanes recursive, +immutable, and limited to the already promoted vec-f64 and vec-bool runtime +names. exp-104 Standard Vec Bool Baseline Alpha is the previous gated +experimental compiler/runtime plus stdlib/source slice. It adds concrete +`(vec bool)` support for immutable params, returns, locals, direct +`std.vec.bool.empty/append/len/index` calls, same-family vector equality, +and the scalar bool-equality enablement needed by the source helper lane in +the checker, formatter, backend, runtime, std runtime inventory, and test +runner, plus the matching Glagol-owned local and explicit-source +`std.vec_bool` helper fixtures for `empty`, `append`, `len`, `at`, +`singleton`, `append2`, `append3`, `pair`, `triple`, `is_empty`, `index_or`, +`first_or`, `last_or`, `contains`, and `count_of`. exp-103 Standard Vec F64 +Baseline Alpha is the earlier gated experimental compiler/runtime plus +stdlib/source slice. It adds concrete `(vec f64)` support for immutable +params, returns, locals, direct `std.vec.f64.empty/append/len/index` calls, +and same-family vector equality in the checker, formatter, backend, runtime, +std runtime inventory, and test runner, plus the matching Glagol-owned local +and explicit-source `std.vec_f64` helper fixtures for `empty`, `append`, +`len`, `at`, `singleton`, `append2`, `append3`, `pair`, `triple`, +`is_empty`, `index_or`, `first_or`, `last_or`, `contains`, and `sum`. exp-102 Standard +Option Bool And F64 Baseline Alpha is the earlier gated +experimental compiler/runtime plus stdlib/source slice. It adds concrete +`(option f64)` and `(option bool)` support for immutable params, returns, +locals, `some`/`none`, `is_some`/`is_none`, `unwrap_some`, and source-level +`match` payload binding in the checker, formatter, backend, and test runner, +plus the matching Glagol-owned local, explicit-source, and workspace-source +`std.option` helper fixtures for `some_f64`, `none_f64`, `is_some_f64`, +`is_none_f64`, `unwrap_some_f64`, `unwrap_or_f64`, `some_bool`, +`none_bool`, `is_some_bool`, `is_none_bool`, `unwrap_some_bool`, and +`unwrap_or_bool`. exp-101 Standard Vec String Option And Transform Helpers +Alpha is the earlier gated +experimental stdlib/source slice. It broadens the existing Glagol-owned local +and explicit-source `std.vec_string` helper fixtures with the option-query +helpers `index_option`, `first_option`, `last_option`, `index_of_option`, +and `last_index_of_option`, plus the transform helpers `concat`, `take`, +`drop`, `reverse`, and `subvec`, while staying concrete to `(vec string)` +plus raw current `(option string)` / `(option i32)` forms, recursive, +immutable, and limited to the existing `std.vec.string.*` runtime names. +exp-100 Standard Option String Baseline Alpha is the earlier gated +experimental compiler/runtime plus stdlib/source slice. It adds concrete +`(option string)` support for immutable params, returns, locals, +`some`/`none`, `is_some`/`is_none`, `unwrap_some`, and source-level `match` +payload binding in the checker, formatter, backend, and test runner, plus +the matching Glagol-owned local, explicit-source, and workspace-source +`std.option` helper fixtures for `some_string`, `none_string`, +`is_some_string`, `is_none_string`, `unwrap_some_string`, and +`unwrap_or_string`. exp-99 Standard Vec String Baseline Alpha is the earlier +compiler/runtime plus stdlib/source slice. It adds concrete `(vec string)` +support for immutable params, returns, locals, direct +`std.vec.string.empty/append/len/index` calls, and concrete vec-string +equality in the checker, formatter, backend, runtime, std runtime +inventory, and test runner, plus the matching Glagol-owned local and +explicit-source `std.vec_string` helper fixtures for `empty`, `append`, +`len`, `at`, `singleton`, `append2`, `append3`, `pair`, `triple`, +`is_empty`, `index_or`, `first_or`, `last_or`, `contains`, and `count_of`. +exp-98 Standard Vec I64 Edit Helpers Alpha is the previous +stdlib/source slice. It extends the staged `std.vec_i64` helper surface +through the Glagol-owned local fixture and explicit-source import fixture +with `insert_at`, `insert_range`, `replace_at`, `replace_range`, +`remove_at`, and `remove_range`, while keeping the local lane recursive and +immutable and reusing only the existing `(vec i64)` runtime names plus +existing option support. exp-97 Standard Vec I64 Transform Helpers Alpha is +the previous stdlib/source slice. It extends the same staged helper surface +with `concat`, `take`, `drop`, `reverse`, and `subvec` while keeping the +local lane recursive and immutable and reusing only the existing `(vec i64)` +runtime names plus existing option support. exp-96 Standard Vec I64 Option +Query Helpers Alpha is the earlier stdlib/source slice. It extends the same +staged helper surface with `index_option`, `first_option`, `last_option`, +`index_of_option`, and `last_index_of_option` while keeping the local lane +recursive and immutable and reusing only the existing `(vec i64)` runtime +names plus existing option support. exp-95 Standard Option I64 Baseline Alpha +is the previous compiler/runtime plus stdlib/source slice. It adds concrete +`(option i64)` support for immutable params, returns, locals, `some`, +`none`, `is_some`, `is_none`, `unwrap_some`, and source-level `match`, plus +the matching local and explicit-source `std.option` i64 helper coverage +through `std-layout-local-option/` and `std-import-option/`. exp-94 Standard +Vec I64 Baseline Alpha is the earlier compiler/runtime plus stdlib/source +slice. +exp-93 Standard Vec I32 Count-Of Helper Alpha is the earlier stdlib/source +gate. It extended the staged `std.vec_i32` concrete helper surface with the +source-authored `count_of(values,target)` helper while reusing the existing +`std.vec.i32` runtime names only and staying within the already staged +`len`, `at`, equality, and `while` surface. +exp-92 Standard Vec I32 Without-Prefix Helper Alpha is an earlier +stdlib/source gate. It extended the same staged facade with source-authored +`without_prefix(values,prefix)`. +exp-91 Standard Vec I32 Without-Suffix Helper Alpha is an earlier +stdlib/source gate. It extended the same staged facade with source-authored +`without_suffix(values,suffix)`. +exp-90 Standard Vec I32 Ends-With Helper Alpha is an earlier +stdlib/source gate. It extended the same staged facade with source-authored +`ends_with(values,suffix)`. +exp-89 Standard Vec I32 Starts-With Helper Alpha is an earlier +stdlib/source gate. It extended the same staged facade with source-authored +`starts_with(values,prefix)`. +exp-88 Standard Vec I32 Replace Range Helper Alpha is an earlier +stdlib/source gate. It extended the same staged facade with source-authored +`replace_range(values,start,end_exclusive,replacement)`. +exp-87 Standard Vec I32 Insert Range Helper Alpha is an earlier +stdlib/source gate. It extended the same staged facade with source-authored +`insert_range(values,position,inserted)`. +exp-86 Standard Vec I32 Remove Range Helper Alpha is an earlier stdlib/source +gate. It extended the same staged facade with source-authored +`remove_range(values,start,end_exclusive)`. +exp-85 Standard Vec I32 Subvec Helper Alpha is an earlier stdlib/source gate. +It extended the same staged facade with source-authored +`subvec(values,start,end_exclusive)`. +exp-84 Standard Vec I32 Insert Helper Alpha is an earlier stdlib/source gate. +It extended the same staged facade with source-authored +`insert_at(values,position,value)`. +exp-83 Standard Vec I32 Remove Helper Alpha is an earlier stdlib/source gate. +It extended the same staged facade with source-authored +`remove_at(values,position)`. +exp-82 Standard Vec I32 Replace Helper Alpha is an earlier stdlib/source +gate. It extended the same staged facade with source-authored +`replace_at(values,position,replacement)`. +exp-80 Standard Vec I32 Generated Constructor Helpers Alpha is an earlier +stdlib/source gate. It extended the same staged facade with source-authored +`repeat` and `range_from_zero` helpers. +exp-79 Standard Vec I32 Transform Helpers Alpha is an earlier stdlib/source +gate. It extended the same staged facade with source-authored `concat`, +`take`, `drop`, and `reverse` helpers. +exp-78 Standard CLI Local Source Facade Alpha is an earlier stdlib/source +gate. It keeps the staged `std.cli` helper surface unchanged while adding the +Glagol local `cli/process/string` fixture and alignment coverage for that same +source-authored facade shape. +exp-77 Standard Vec I32 Option Query Helpers Alpha is an earlier stdlib/source +gate. exp-76 Standard Vec I32 Source Helpers Alpha is an earlier stdlib/source +gate. exp-75 Standard Option Constructors Alpha is an earlier stdlib/source +gate. exp-74 Standard Result Constructor Helpers Alpha is an earlier +stdlib/source gate. exp-73 Standard Io Value Helpers Alpha is an earlier +stdlib/source gate. +exp-72 Standard Cli Custom Fallback Helpers Alpha is an earlier stdlib/source +gate. exp-71 Standard Process Custom Fallback Helpers Alpha is an earlier +stdlib/source gate. exp-70 Standard Fs Custom Fallback Helpers Alpha is an +earlier stdlib/source gate. exp-69 Standard Env Custom Fallback Helpers Alpha +is an earlier stdlib/source gate. exp-68 Standard String Custom Fallback +Helpers Alpha is an earlier stdlib/source gate. exp-67 Standard Process Typed +Helpers Alpha is an earlier stdlib/source gate. exp-66 Standard Fs Typed Read +Helpers Alpha is an earlier stdlib/source gate. exp-65 Standard Env Typed +Helpers Alpha is an earlier stdlib/source gate. exp-64 Standard Num Fallback +Helpers Alpha is an earlier stdlib/source gate. +exp-63 Standard Fs Fallback Helpers Alpha is an earlier stdlib/source gate. +exp-62 Standard Env Fallback Helpers Alpha is an earlier stdlib/source gate. +exp-61 Standard Process Fallback Helpers Alpha is an earlier stdlib/source +gate. exp-60 Standard String Fallback Helpers Alpha is an earlier +stdlib/source gate. exp-59 Hosted Build Optimization And Benchmark Publication +Alpha is the previous compiler/tooling gate. exp-58 Boolean Logic Alpha is the +previous compiler gate. exp-57 Integer Bitwise Alpha is the previous +compiler/stdlib gate. exp-56 Integer Remainder Alpha is an earlier +compiler/stdlib gate. exp-55 Result F64 Bool Source Flow Alpha is an earlier +compiler gate. exp-54 Standard CLI Typed Arguments Alpha is an earlier source +facade gate. +exp-53 Standard CLI Source Facade Alpha is an earlier source facade gate. +exp-52 Standard Process Facade Source Search Alpha is an earlier source facade +gate. exp-51 Standard Library Path List Alpha is the previous toolchain +discovery gate. exp-50 Installed Standard Library Discovery Alpha is an +earlier toolchain discovery gate. exp-43 Technical Whitepapers And Skills +Alpha is an earlier released documentation/tooling gate. +exp-42 Hot Loop Benchmark Mode Alpha is the latest released experimental +benchmark tooling gate. +exp-41 Lisp Benchmark Comparison Alpha is an earlier released experimental +benchmark comparison gate. +exp-40 Benchmark Suite And License Alpha is the latest released experimental +license/governance gate. +exp-39 Standard Math Extensions And Benchmark Scaffold Alpha is the latest +released experimental source-helper gate. exp-34 String Parse Bool Result +Alpha is the latest compiler semantic/runtime-operation support slice. + +## Phase 1: Make Current Compiler Trustworthy + +- [x] Add Rust integration tests for `examples/add.slo`. +- [x] Wire `tests/add.expected.ll` into a snapshot or shape-based test. +- [x] Add negative tests for unknown function, arity mismatch, type mismatch, unclosed list, and unknown top-level form. +- [x] Remove source-reachable backend panics. +- [x] Add `UnsupportedBackendFeature` diagnostics or implement lowering for all checked expression variants. +- [x] Add an explicit test for checked `if` not crashing the backend. +- [x] Remove unused imports and warnings in touched files. + +## Phase 2: Close Critical Semantic Gaps + +- [x] Implement LLVM lowering for value-producing `if` with then/else/merge blocks and `phi`. +- [x] Preserve spans on checked expressions. +- [x] Add integer range diagnostics for i32 literals. +- [x] Add severity to diagnostics. +- [x] Add line/column ranges while preserving byte spans. +- [x] Ensure complex placeholder types cannot emit ABI-sensitive LLVM accidentally. + +## Phase 3: Restore Slovo Tooling Promise + +- [x] Add parsed-tree printer. +- [x] Add canonical formatter for supported syntax. +- [x] Add formatter fixtures. +- [x] Add machine-diagnostic snapshots. +- [x] Add lowering inspector from surface AST to checked/core AST. + +## Phase 4: Implement Slovo v0 Core + +- [x] Implement top-level `(test "name" expr)` or explicitly reject it in supported examples. +- [x] Implement `let`, `var`, and `set`. +- [x] Promote value-producing `if`. +- [x] Implement `while`. +- [x] Implement string runtime support or structured unsupported diagnostics. +- [x] Implement first-pass structs and immediate field access. +- [x] Implement arrays and checked indexing. +- [x] Implement first-pass `option` and `result` constructors. +- [x] Implement lexical `unsafe` and unsafe-required diagnostics. + +## Phase 5: Close v0 Compiler Surface + +- [x] Treat `glagol` as the v0 binary CLI. +- [x] Document supported CLI modes, stdout/stderr behavior, and exit codes. +- [x] Test successful help, diagnostic failures, input failures, and usage errors. +- [x] Keep direct native executable output deferred behind the explicit + LLVM-plus-Clang workflow. + +## Phase 6: Start Slovo v1 Value Flow + +- [x] Implement immutable struct locals, struct parameters, struct returns, + calls returning structs, and field access through stored struct values. +- [x] Keep struct field/value mutation deferred for v1. +- [x] Implement option/result value flow and first-pass tag observation. +- [x] Implement option/result payload extraction after Slovo + specifies the contract. +- [x] Keep broader option/result matching deferred until Slovo later specifies + a narrow source-level `match` slice. +- [x] Implement array value flow and runtime-checked dynamic indexing for + fixed-size `i32` arrays. +- [x] Implement the narrow string runtime slice: direct string literals stored + as borrowed static `ptr` values and passed to temporary `print_string`. +- [x] Keep full string value flow and broad runtime printing deferred for v1. + +## Phase 7: Polish The Binary CLI + +- [x] Add explicit `--emit=llvm` while keeping default LLVM emission compatible. +- [x] Add `-o ` for successful primary output. +- [x] Add `--version` from Cargo package metadata. +- [x] Keep direct executable output deferred behind the LLVM-plus-Clang workflow. +- [x] Keep stdin source input deferred until source naming and span behavior are + specified. + +## Phase 8: Start Slovo v1 Tooling Contracts + +- [x] Emit Slovo v1 machine diagnostics with `(schema slovo.diagnostic)`, + `(version 1)`, and a root `(diagnostic ...)` form. +- [x] Add `--manifest ` for Slovo v1 textual artifact manifests on + successful primary modes and source-diagnostic failures. +- [x] Reject user-visible `unit` parameter and return signatures with + structured diagnostics while keeping `unit` available to internal + compiler/runtime forms. +- [x] Preserve existing stdout, stderr, and `-o` behavior while adding + manifest output as a side channel, not as native executable output. +- [x] Expand formatter stability coverage for comments, nested promoted forms, + and long inline forms. +- [x] Expand and audit the v1 golden diagnostic matrix beyond the first schema + migration. +- [x] Add lowering-inspector golden fixtures for every promoted v1 feature + family. +- [x] Keep stable LLVM debug metadata, DWARF, source-map files, and stable + ABI/layout promises deferred for v1. + +## Phase 9: Slovo v1.1 Toolchain Productization + +- [x] Add canonical `check`, `fmt`, `test`, and `build` subcommands. +- [x] Preserve v1 compatibility flags and default LLVM emission. +- [x] Add `--json-diagnostics` with newline-delimited + `slovo.diagnostic` version `1` output. +- [x] Add `--no-color` across canonical commands and compatibility modes. +- [x] Implement hosted native `build` through emitted LLVM IR, + `runtime/runtime.c`, and Clang with `GLAGOL_CLANG` override. +- [x] Implement v1.1 exit codes `0`, `1`, `2`, `3`, and `5`; reserve `4` + for internal compiler errors. +- [x] Extend manifests for no-output `check`, canonical commands, + diagnostics encoding, build artifacts, and parsed failure cases. +- [x] Add focused CLI, JSON diagnostic, manifest failure, missing-Clang, and + ignored hosted-build smoke tests. + +## Phase 10: Slovo v1.2 Practical Runtime Values + +- [x] Implement immutable string `let` locals, parameters, returns, and calls + returning strings. +- [x] Implement string equality with `=`. +- [x] Implement string byte length through temporary intrinsic `string_len`. +- [x] Implement temporary intrinsic `print_bool`. +- [x] Add deterministic runtime trap stderr messages for array bounds and + option/result unwrap failures. +- [x] Make runtime traps exit with code `1` for `glagol test` trapped tests + and produced executables. +- [x] Keep the v1.1 CLI/toolchain surface unchanged. +- [x] Keep `print_unit`, mutable strings, string concatenation, strings in + containers, project mode, imports, and modules deferred. +- [x] Add fixture alignment, formatter idempotence, lowering-inspector, + diagnostic, runtime smoke, and process-exit gates matching the Slovo + v1.2 contract. + +## Phase 11: Slovo v1.3 Project Mode And Modules + +- [x] Read `slovo.toml` project manifests with `[project]`, required `name`, + optional `source_root = "src"`, and optional `entry = "main"`. +- [x] Select project mode for `glagol check`, `glagol test`, and + `glagol build` only when input is a project root or the `slovo.toml` + file itself. +- [x] Keep single-file `.slo` behavior unchanged from v1.2. +- [x] Discover only immediate `.slo` module files under the flat source root. +- [x] Accept module export lists such as + `(module math (export add_one Point))`. +- [x] Accept explicit local imports such as `(import math (add_one Point))`. +- [x] Enforce import/export visibility, module-private top-level names, + duplicate-name rules, and reserved builtin/intrinsic names. +- [x] Compile, test, diagnose, and write manifests in deterministic module + order. +- [x] Preserve source file identity and related spans for cross-file + diagnostics. +- [x] Extend `slovo.artifact-manifest` version `1` output for project roots, + manifests, source roots, module lists, import edges, test counts, and + build outputs. +- [x] Add project fixture, manifest, command, diagnostic, deterministic-order, + artifact-manifest, and single-file regression gates matching the Slovo + v1.3 contract. +- [x] Keep packages/dependencies, workspaces, aliases/globs/qualified imports, + re-exports, nested modules, project formatting, path escapes, watch/LSP, + incremental builds, generated sources, object/library/header outputs, and + ABI promises deferred. + +## Phase 12: Slovo v1.4 Core Language Expansion + +- [x] Implement source-level `match` for exactly `(option i32)` and + `(result i32 i32)`. +- [x] Require exhaustive two-arm forms: `some`/`none` for options and + `ok`/`err` for results. +- [x] Implement immutable payload bindings for `some`, `ok`, and `err`. +- [x] Scope payload bindings and local bindings to one arm body only. +- [x] Type each `match` expression as the common arm result type. +- [x] Add diagnostics for subject type mismatch, unsupported payload type, + non-exhaustive matches, duplicate arms, malformed patterns, arm type + mismatch, binding collisions, unsupported mutation, and unsupported + containers. +- [x] Add canonical multiline formatter support for promoted `match`. +- [x] Add lowering-inspector, LLVM/runtime smoke, fixture-alignment, formatter, + diagnostic, and regression gates matching the Slovo v1.4 contract. +- [x] Keep user-defined enums/ADTs, generics, non-`i32` payload matching, + nested option/result payloads, option/result containers, mutation, + vectors, `i64`, general block expressions, guards, wildcard/rest + patterns, destructuring, standard-library error conventions, ownership, + layout, and ABI guarantees deferred. + +## Phase 13: Slovo v1.5 Standard Library Alpha + +- [x] Implement compiler-known source-level standard-runtime names + `std.io.print_i32`, `std.io.print_string`, `std.io.print_bool`, and + `std.string.len`. +- [x] Preserve legacy `print_i32`, `print_string`, `print_bool`, and + `string_len` as compatibility aliases. +- [x] Reject unknown or unpromoted `std.*` calls with a structured diagnostic, + suggested code `UnsupportedStandardLibraryCall`. +- [x] Reserve the exact promoted `std.*` names from user function/export + shadowing. +- [x] Keep promoted names out of user module, import, package, FFI, and stable + C ABI semantics. +- [x] Add fixture alignment for `examples/standard-runtime.slo`, formatter + idempotence for `tests/standard-runtime.slo`, lowering-inspector, + runtime-smoke, diagnostic, and regression gates matching the Slovo v1.5 + contract. +- [x] Keep `std.io.print_unit`, file IO, environment variables, process + arguments, time, collections, allocation, overloading, generics, Unicode + length semantics, ABI/layout promises, and FFI deferred. + +## Phase 14: Slovo v1.6 Memory And Unsafe Design Slice + +- [x] Centralize compiler-known unsafe operation head metadata for `alloc`, + `dealloc`, `load`, `store`, `ptr_add`, `unchecked_index`, `reinterpret`, + and `ffi_call`. +- [x] Preserve lexical unsafe gating: outside `(unsafe ...)`, these heads + produce `UnsafeRequired` before user-function lookup; inside + `(unsafe ...)`, they produce `UnsupportedUnsafeOperation`. +- [x] Reserve compiler-known unsafe heads from user functions, project + imports/exports, parameters, locals, structs, and match payload bindings + where the current reservation architecture applies. +- [x] Add diagnostics-contract coverage for every unsafe head in both the + unsafe-required and unsupported-inside-unsafe gates. +- [x] Keep raw memory, pointers, allocation/free, unchecked indexing, + reinterpretation, FFI execution, stable ABI/layout promises, and runtime + C symbols deferred. + +## Phase 15: Glagol v1.7 Developer Experience Hardening + +- [x] Add `glagol new [--name ]` for minimal local project + scaffolding with structured diagnostics for blocked targets and invalid + arguments. +- [x] Add `glagol fmt --check` and `glagol fmt --write` for files and project + inputs while preserving plain `glagol fmt ` stdout formatting. +- [x] Extend formatter coverage only for already-supported project module + declarations, exports, imports, and imported calls. +- [x] Add `glagol doc -o ` for deterministic Markdown + summaries of modules, imports, exports, structs, functions, and tests + from current parser/lowerer/source forms. +- [x] Add `scripts/release-gate.sh` for the local release gate from either the + repository root or compiler directory context. +- [x] Keep Slovo language syntax and semantics unchanged. + +## Phase 16: Glagol v2.0.0-beta.1 Experimental Gate + +- [x] Treat the release as an experimental gate/readiness release based on + v1.7, not as a new language expansion. +- [x] Document the experimental-supported CLI, project workflow, language + surface, and explicit deferrals in `.llm/V2_0_0_BETA_1_RELEASE_GATE.md`. +- [x] Verify a scaffolded experimental project can run through `new`, + `fmt --check`, `test`, `doc`, and hosted `build` when the toolchain is + available. +- [x] Keep the standard-runtime alpha scoped to basic IO/string names: + `std.io.print_i32`, `std.io.print_string`, `std.io.print_bool`, and + `std.string.len`. +- [x] Keep fixed-size immutable `i32` arrays as the current collection subset. +- [x] Defer vectors, growable collections, package registries, stable + ABI/layout, FFI, raw-memory execution, LSP, debug metadata, and + source-map files until future beta work. + +## Phase 17: exp-1 Owned String Concat Slice + +- [x] Implement compiler-known `std.string.concat(left right)` without adding + new syntax. +- [x] Keep concat strict to the standard-runtime name; no legacy + `string_concat` alias is promoted. +- [x] Lower concat through an explicit private runtime helper without claiming + stable ABI, layout, allocator, or lifetime semantics. +- [x] Keep string mutation, string containers, user deallocation, and `+` + string concatenation deferred behind existing diagnostics. +- [x] Add checker, formatter, lowering-inspector, test-runner, LLVM-shape, and + hosted-runtime coverage for the narrow slice. + +## Phase 18: exp-2 Collections Alpha Slice + +- [x] Implement the initial growable collection type: `(vec i32)`. +- [x] Extend the concrete vector family with `(vec i64)` in exp-94. +- [x] Promote compiler-known `std.vec.i32.empty`, `std.vec.i32.append`, + `std.vec.i32.len`, and `std.vec.i32.index`. +- [x] Promote compiler-known `std.vec.i64.empty`, `std.vec.i64.append`, + `std.vec.i64.len`, and `std.vec.i64.index`. +- [x] Keep vector values immutable from source; `append` returns a new + runtime-owned vector instead of mutating the input. +- [x] Implement same-family `(vec i32)` and `(vec i64)` equality with `=`. +- [x] Lower vector operations through private runtime helpers and reserve + helper symbols from source/project names. +- [x] Add runtime traps for vector allocation failure and vector index + out-of-bounds. +- [x] Add formatter, lowering-inspector, test-runner, LLVM/runtime, diagnostic + snapshot, fixture-inventory, and hosted-runtime coverage for the slice. +- [x] Keep generic vectors, element families beyond `i32` and `i64`, vector + mutation, push aliases, nested vectors, vectors inside other containers, + stable layout, ABI, FFI, and deallocation deferred behind diagnostics. + +## Phase 19: exp-3 Standard IO And Host Environment Slice + +- [x] Implement the conservative exp-3 host IO standard-runtime calls. +- [x] Keep networking, async IO, binary IO, directory traversal, terminal + control, stdin iteration, time, randomness, and stable host ABI deferred. + +## Phase 20: exp-4 User ADTs Alpha Slice + +- [x] Implement payloadless user-defined enums, qualified constructors, + equality, value flow, top-level tests, and exhaustive variant matches. +- [x] Keep payload variants, generic ADTs, type aliases, traits, methods, + unqualified variants, enum mutation, enum printing, enum ordering, enum + containers, stable layout, and stable ABI deferred. + +## Phase 21: exp-5 Local Packages And Workspaces Alpha + +- [x] Read workspace `[workspace] members` manifests and member package + `[package]` manifests with `name`, `version`, `source_root`, and `entry`. +- [x] Support local `[dependencies]` path records such as + `mathlib = { path = "../mathlib" }`. +- [x] Build a deterministic closed local package graph. +- [x] Accept package-qualified imports such as + `(import mathlib.math (add_one))`. +- [x] Enforce package boundaries through existing module export lists. +- [x] Extend artifact manifests with workspace and package graph information. +- [x] Diagnose missing packages, duplicate package names, dependency cycles, + path escapes, invalid package names, invalid package versions, private + visibility, and dependency key mismatches. +- [x] Keep registries, lockfiles, semver solving, aliases/globs/re-exports, + generated code, build scripts, publishing, optional/dev/target + dependencies, stable package ABI, and remote dependencies deferred. + +## Phase 22: exp-6 C FFI Scalar Imports Alpha + +- [x] Accept top-level imported C declarations such as + `(import_c c_add ((lhs i32) (rhs i32)) -> i32)`. +- [x] Restrict imported C signatures to `i32` parameters and `i32` or builtin + `unit` returns. +- [x] Require conservative C symbol names shared with the source import name. +- [x] Require calls to imported C functions to appear inside lexical + `(unsafe ...)`. +- [x] Preserve raw unsafe head diagnostics; `ffi_call` and the other v1.6 raw + unsafe heads remain unsupported even inside unsafe blocks. +- [x] Lower accepted imports to LLVM external declarations and direct calls. +- [x] Support explicit local C source linking through + `glagol build --link-c `. +- [x] Report `UnsupportedTestExpression` when the interpreter-style test + runner reaches an imported C call. +- [x] Extend artifact manifests with `foreign_imports` and `c_link_inputs`. +- [x] Keep pointers, raw allocation/deallocation, raw unsafe head execution, + ownership/lifetime rules, C exports, callbacks, headers/libraries, + broad linker configuration, and stable ABI/layout deferred. + +## Phase 23: exp-7 Test Selection Alpha + +- [x] Add `--filter ` for `glagol test` and the legacy + single-file `--run-tests` mode. +- [x] Count skipped tests before evaluation and record selected/skipped + metadata in artifact manifests. +- [x] Keep tags, groups, retries, parallelism, benchmark behavior, and watch + mode deferred. + +## Phase 24: exp-8 Host Time And Sleep Alpha + +- [x] Promote compiler-known `std.time.monotonic_ms: () -> i32` and + `std.time.sleep_ms: (i32) -> unit`. +- [x] Lower native calls through private runtime helpers and implement + process-relative monotonic milliseconds plus `nanosleep`. +- [x] Support deterministic `sleep_ms 0` and non-negative monotonic execution + in the test runner; trap negative sleeps with the exact runtime message. +- [x] Keep threads, channels, async, timers, wall-clock time, time zones, + scheduling guarantees, cancellation, high-resolution timing, and stable + runtime ABI deferred. +- [x] Review artifact manifests: no current standard-runtime usage structure + exists, so exp-8 defers time-specific metadata. + +## Phase 25: exp-9 Reliability Performance And Ecosystem Hardening + +- [x] Add Glagol exp-9 documentation/audit state aligned with the Slovo + hardening target. +- [x] Record compatibility inventory for `v2.0.0-beta.1` and exp-1 through + exp-8. +- [x] Record migration guidance that requires no source syntax, standard + runtime, or manifest schema migration by default. +- [x] Record source-reachable panic audit classifications for current source + markers. +- [x] Record benchmark smoke commands and known limits without public numeric + thresholds. +- [x] Add parser, formatter, diagnostic, project graph, runtime API, + test-runner, backend-precondition, and artifact-manifest hardening tests. +- [x] Add exp-9 corpus coverage and panic-audit classifications proving the + current source-reachable panic markers are diagnostic/trap boundaries or + non-source-reachable invariants. +- [x] Run the exp-9 release gate and benchmark smoke evidence. +- [x] Write exp-9 release review evidence. +- [x] Keep exp-9 hardening-only: no source syntax, no new standard-runtime + names, no runtime capability expansion, no manifest schema bump, no ABI + or performance stability claim, and no beta maturity claim. + +## Phase 26: exp-10 Result-Based Host Errors Alpha + +- [x] Implement concrete `(result string i32)` value flow, observers, unwraps, + and exact two-arm `match` without broadening arbitrary result payloads. +- [x] Promote `std.process.arg_result`, `std.env.get_result`, + `std.fs.read_text_result`, and `std.fs.write_text_result`. +- [x] Return ordinary host failures as exactly `err 1`. +- [x] Preserve exp-3 host call behavior unchanged for traps, missing + environment variables, and write status values. +- [x] Add formatter, lowering-inspector, diagnostics, deterministic + test-runner, LLVM/runtime, and fixture-alignment gates for the target + fixture `host-io-result.slo`. +- [x] Keep exp-10 experimental only: no generic results, typed host error ADTs, + error messages, errno domains, result equality/printing/mapping, + manifest schema bump, stable ABI/layout, or beta maturity. + +## Phase 27: exp-11 Basic Randomness Alpha + +- [x] Promote exactly `std.random.i32 : () -> i32`. +- [x] Return a non-negative implementation-owned `i32` in runtime and + deterministic-enough test-runner paths. +- [x] Trap runtime unavailability with exactly + `slovo runtime error: random i32 unavailable`. +- [x] Add fixture alignment, formatter, lowering-inspector, diagnostic, + test-runner, LLVM/runtime, and promotion-gate coverage for + `random.slo`. +- [x] Keep exp-11 experimental only: no seed APIs, crypto suitability, range + arguments, bytes, strings, UUIDs, stable sequences, manifest schema bump, + stable helper ABI, runtime headers/libraries, or beta maturity. + +## Phase 28: exp-12 Standard Input Result Alpha + +- [x] Promote exactly `std.io.read_stdin_result : () -> (result string i32)`. +- [x] Return `ok` with remaining stdin text, including EOF as `ok ""`, and + ordinary host/input failure as `err 1`. +- [x] Align Slovo supported and formatter fixtures with `stdin-result.slo`. +- [x] Cover formatter and lowering-inspector visibility for + `(std.io.read_stdin_result)`. +- [x] Cover diagnostics for arity mismatch, bool-context/type misuse, + source-name shadowing, helper-symbol shadowing, unsupported trap-based + `std.io.read_stdin`, line/read-line APIs, prompt APIs, terminal mode, + binary stdin, streaming, async stdin, and unknown deferred stdin + boundaries named by Slovo. +- [x] Keep exp-12 experimental only: no trap-based stdin, line iteration, + prompt APIs, terminal mode, binary stdin, streaming, async IO, encoding + or Unicode promise, stable helper ABI/layout, manifest schema change, + or beta maturity. + +## Phase 29: exp-13 String Parse I32 Result Alpha + +- [x] Promote exactly + `std.string.parse_i32_result : (string) -> (result i32 i32)`. +- [x] Parse an entire ASCII decimal signed `i32`, returning `ok value` on + success and ordinary parse failure as `err 1`. +- [x] Align Slovo supported and formatter fixtures with + `string-parse-i32-result.slo`. +- [x] Cover formatter and lowering-inspector visibility for + `(std.string.parse_i32_result text)`. +- [x] Cover diagnostics for arity mismatch, invalid argument type, + result/bool-context misuse, source-name shadowing, helper-symbol + shadowing, trap-based `std.string.parse_i32`, unsupported parse + families, string indexing/slicing, tokenizer/scanner APIs, and named + deferred parse boundaries. +- [x] Keep exp-13 experimental only: no trap-based parse, + float/bool/string/bytes parsing, whitespace/locale/base-prefix/ + underscore/plus-sign parsing, generic parse API, richer parse errors, + Unicode digit parsing, string indexing/slicing, tokenizer/scanner APIs, + stdin line APIs, stable helper ABI/layout, runtime headers/libraries, + manifest schema changes, or beta maturity. + +## Phase 30: exp-14 Standard Runtime Conformance Alignment + +- [x] Add Glagol `time-sleep.slo` supported and formatter fixtures over the + released exp-8 `std.time.monotonic_ms` and `std.time.sleep_ms 0` + surface. +- [x] Add lowering-inspector snapshots, promotion-gate inventory, formatter + checks, compile-shape checks, and test-runner checks for the time/sleep + fixture. +- [x] Add a focused conformance gate script separate from + `scripts/release-gate.sh`. +- [x] Add focused integration coverage for projects/imports, tests, docs, + `fmt --check`, `check`, `test`, hosted build/run when available, and + the released standard-runtime surface through exp-13 plus exp-8 + time/sleep. +- [x] Release exp-14 only after focused conformance, full release gate, and + Slovo/Glagol review pass together. +- [x] Keep exp-14 experimental only: no source syntax, type forms, + standard-library operation names, runtime APIs, ABI/layout promises, + manifest schema changes, or beta maturity. + +## Phase 31: exp-15 Result Helper Standard Names Alpha + +- [x] Accept exactly `std.result.is_ok`, `std.result.is_err`, + `std.result.unwrap_ok`, and `std.result.unwrap_err`. +- [x] Map those names to existing result observer/unwrap behavior for exactly + `(result i32 i32)` and `(result string i32)`. +- [x] Preserve source helper spelling in surface and checked lowering output. +- [x] Keep legacy `is_ok`, `is_err`, `unwrap_ok`, and `unwrap_err` working. +- [x] Add `result-helpers.slo` examples/tests, lowering snapshots, + promotion-gate inventory, formatter coverage, compile-shape coverage, + test-runner coverage, and focused negative tests for deferred/misused + names. +- [x] Keep exp-15 experimental only: no `std.result.map`, + `std.result.unwrap_or`, `std.result.and_then`, option standard names, + generic results, new result payload families, runtime/runtime.c changes, + LLVM runtime declarations, manifest schema changes, or beta maturity. + +## Phase 32: exp-16 Unary I32 Enum Payloads Alpha + +- [x] Support user enum declarations with payloadless variants and unary `i32` + payload variants. +- [x] Support qualified payload and payloadless constructors, immutable enum + locals/params/returns/calls/tests/main flow, and same-enum equality. +- [x] Require exhaustive enum matches with no binding on payloadless variants + and one immutable arm-local `i32` binding on payload variants. +- [x] Lower payload enums as compiler-owned tag-plus-`i32` aggregates in LLVM + and the test runner, without runtime/runtime.c changes or ABI/layout + claims. +- [x] Add `enum-payload-i32.slo` examples/tests, surface and checked lowering + snapshots, formatter coverage, focused Rust integration tests, + promotion-gate inventory, and diagnostics for payload boundary cases. +- [x] Keep exp-16 experimental only: no non-`i32` payload types, multiple + payloads, record variants, generic enums/functions, wildcard/rest/nested + enum patterns, guards, enum containers, enum mutation, enum printing, + enum ordering/hash/reflection/discriminants, runtime ABI/layout claims, + manifest schema changes, or beta maturity. + +## Future Beta Next + +- [x] Complete exp-9 hardening gates before adding source semantics. +- [x] Completed exp-10 gates; result-based host errors are released as + experimental compiler support. +- [x] Completed exp-11 gates; basic randomness is released as experimental + compiler support. +- [x] Completed exp-12 gates; standard input result is released as + experimental compiler support. +- [x] Completed exp-13 gates; string parse i32 result is released as + experimental compiler support. +- [x] Complete exp-14 Standard Runtime Conformance Alignment gates before + treating the conformance fixture/gate set as released experimental + support. +- [x] Completed exp-15 gates; result helper standard names are released as + experimental compiler support. +- [x] Completed exp-16 gates; unary `i32` enum payloads are released as + experimental compiler support. +- [x] Completed exp-17 gates; project/workspace enum imports are released as + experimental compiler support. +- [x] Completed exp-18 gates; direct enum struct fields are released as + experimental compiler support. + +## Phase 33: exp-17 Project/Workspace Enum Imports Alpha + +- [x] Treat top-level enum declarations as project/workspace local declarations + that can be exported and imported alongside functions and structs. +- [x] Preserve payloadless and unary `i32` payload variant metadata for imported + enum types in signatures, immutable locals, calls/returns, constructors, + equality, exhaustive matches, `main`, and tests. +- [x] Keep project/workspace symbol rewriting aligned for enum declarations, + enum types, qualified constructors, enum match patterns, and imported enum + symbols. +- [x] Add project and workspace/package tests plus the + `examples/projects/enum-imports/` fixture. +- [x] Keep exp-17 experimental only: no non-`i32` payloads, multi-payloads, + record variants, enum containers, mutation, printing, ordering, generics, + stable ABI/layout, manifest schema changes, registry/package-manager + behavior, or beta maturity. +- [ ] If collections expand later, specify generic vectors/growable + collections in Slovo first, then add Glagol parser/checker/formatter/ + diagnostic/backend/runtime gates. +- [ ] If editor tooling is promoted later, specify LSP/watch/debug/source-map + behavior before claiming support. + +## Phase 34: exp-18 Enum Struct Fields Alpha + +- [x] Allow direct known enum types as struct fields for current payloadless + and unary `i32` payload enums. +- [x] Preserve `UnsupportedEnumContainer` for arrays, vectors, options, and + results containing enum values in struct fields. +- [x] Keep other unsupported struct field types on the existing + `UnsupportedStructFieldType` path. +- [x] Add `enum-struct-fields.slo` examples/tests, surface and checked lowering + snapshots, focused Rust integration tests, promotion-gate inventory, and + diagnostics for enum container fields. +- [x] Keep exp-18 experimental only: no enum containers, nested structs, + mutation, printing, ordering/hash/reflection, broader payloads, generics, + stable ABI/layout, manifest schema changes, package registry behavior, + import aliases/globs/re-exports, or beta maturity. + +## Phase 35: exp-19 Primitive Struct Fields Alpha + +- [x] Allow direct `bool` and immutable `string` field types in top-level + structs, alongside existing direct `i32` fields and exp-18 direct enum + fields. +- [x] Preserve unsupported diagnostics for arrays, vectors, options, and + results containing these field types, nested structs, and unknown named + field types. +- [x] Add `primitive-struct-fields.slo` examples/tests, surface and checked + lowering snapshots, focused Rust integration tests, promotion-gate + inventory, diagnostics, formatter coverage, test-runner coverage, LLVM + shape coverage, and Slovo alignment hooks. +- [x] Keep exp-19 experimental only: no primitive containers as struct fields, + nested structs, mutation, broader string ownership/cleanup promises, + generics, methods/traits, stable ABI/layout, manifest schema changes, + import/package widening, or beta maturity. + +## Phase 36: exp-20 F64 Numeric Primitive Alpha + +- [x] Add direct `f64` type/literal value flow for locals, parameters, + returns, calls, top-level tests, and `main`-reachable helper calls. +- [x] Lower `f64` to LLVM `double`, including constants, function + parameters/returns, arithmetic `+ - * /`, equality, and ordered + comparisons. +- [x] Promote exactly `std.io.print_f64 : (f64) -> unit` with runtime + `%.17g\n` formatting and test-runner coverage. +- [x] Add `f64-numeric-primitive.slo` examples/tests, surface and checked + lowering snapshots, formatter coverage, focused Rust integration tests, + LLVM shape coverage, runtime smoke, and mixed i32/f64 diagnostics. +- [x] Keep exp-20 experimental only: no f32, wider integer families, + characters/bytes/decimal, casts, mixed i32/f64 arithmetic, f64 + arrays/vectors/options/results/enum payloads/struct fields, parse_f64, + random floats, stable ABI/layout, or beta maturity. + +## Phase 37: exp-21 I64 Numeric Primitive Alpha + +- [x] Add direct `i64` type/literal value flow for immutable locals, + parameters, returns, calls, top-level tests, and `main`-reachable helper + calls. +- [x] Require explicit signed decimal `i64` suffix atoms and reject malformed + or out-of-range `i64` literals with structured diagnostics. +- [x] Lower `i64` to LLVM `i64`, including constants, function + parameters/returns, arithmetic `+ - * /`, equality, and ordered + comparisons. +- [x] Promote exactly `std.io.print_i64 : (i64) -> unit` with runtime decimal + output and test-runner coverage. +- [x] Add `i64-numeric-primitive.slo` examples/tests, surface and checked + lowering snapshots, formatter coverage, focused Rust integration tests, + LLVM shape coverage, runtime smoke, fixture alignment hooks, and + diagnostics for mutable i64 locals and mixed numeric operands. +- [x] Keep exp-21 experimental only: no f32, unsigned integers, narrower + signed integers, characters/bytes/decimal, casts, mixed + i32/i64/f64 arithmetic, i64 arrays/vectors/options/results/enum + payloads/struct fields, parse_i64, random i64, stable ABI/layout, or + beta maturity. + +## Phase 38: exp-22 Numeric Widening Conversions Alpha + +- [x] Promote exactly `std.num.i32_to_i64 : (i32) -> i64`, + `std.num.i32_to_f64 : (i32) -> f64`, and + `std.num.i64_to_f64 : (i64) -> f64`. +- [x] Lower the promoted conversions directly to LLVM `sext`/`sitofp` + conversion instructions without adding stable runtime helper ABI + promises. +- [x] Execute the conversions in the deterministic test runner and preserve + them in formatter and lowering-inspector fixtures. +- [x] Add diagnostics for wrong arity, wrong argument type, and explicitly + unsupported narrowing or generic cast names. +- [x] Keep Glagol examples/tests byte-aligned with Slovo supported/formatter + fixtures for `numeric-widening-conversions.slo`. +- [x] Keep exp-22 experimental only: no implicit promotion, mixed numeric + operators, narrowing or checked conversions, cast syntax, broader + numeric families, stable ABI/layout, manifest schema changes, or beta + maturity. + +## Phase 39: exp-23 Checked I64-To-I32 Conversion Alpha + +- [x] Promote exactly + `std.num.i64_to_i32_result : (i64) -> (result i32 i32)`. +- [x] Return `ok value` for signed `i64` inputs in signed `i32` range and + `err 1` for out-of-range inputs, with no traps. +- [x] Lower directly to LLVM signed range checks, `trunc`, `select`, and the + existing `(result i32 i32)` aggregate representation without adding a + stable helper ABI promise. +- [x] Execute the conversion in the deterministic test runner and preserve it + in formatter and lowering-inspector fixtures. +- [x] Add diagnostics for wrong arity, wrong argument type, and explicitly + unsupported deferred checked/cast names. +- [x] Keep Glagol examples/tests byte-aligned with Slovo supported/formatter + fixture hooks for `checked-i64-to-i32-conversion.slo`. +- [x] Keep exp-23 experimental only: no implicit promotion, unchecked + narrowing, cast syntax, additional narrowing conversions, f64 + conversions, stable ABI/layout, manifest schema changes, or beta + maturity. + +## Phase 40: exp-24 Integer To String Alpha + +- [x] Promote exactly `std.num.i32_to_string : (i32) -> string` and + `std.num.i64_to_string : (i64) -> string`. +- [x] Return signed decimal ASCII text with a leading `-` only for negative + values and no leading `+`. +- [x] Lower to private runtime helpers with current implementation-owned + string allocation behavior and no stable helper ABI/layout promise. +- [x] Execute both calls in the deterministic test runner and preserve them + in formatter and lowering-inspector fixtures. +- [x] Add diagnostics for wrong arity, wrong argument type, and explicitly + unsupported deferred formatting names. +- [x] Keep Glagol examples/tests byte-aligned with Slovo supported/formatter + fixture hooks for `integer-to-string.slo`. +- [x] Keep exp-24 experimental only: no f64 formatting, parse APIs, + locale/base/radix/grouping/padding, generic display/format traits, + implicit conversion, manifest schema changes, stable helper ABI/layout, + or beta maturity. + +## Phase 41: exp-25 String Parse I64 Result Alpha + +- [x] Promote exactly + `std.string.parse_i64_result : (string) -> (result i64 i32)`. +- [x] Accept only an entire non-empty signed decimal ASCII string with an + optional leading `-` and signed `i64` bounds. +- [x] Return `ok value` for valid inputs and `err 1` for malformed or + out-of-range inputs without ordinary traps. +- [x] Lower to a private runtime helper returning status plus an `i64` + out-parameter, and keep the result aggregate implementation-owned. +- [x] Execute the call in the deterministic test runner and preserve it in + formatter, lowering-inspector, hosted-runtime, and diagnostic fixtures. +- [x] Keep Glagol examples/tests byte-aligned with Slovo supported/formatter + fixture hooks for `string-parse-i64-result.slo`. +- [x] Keep exp-25 experimental only: no leading `+`, whitespace, locale, + base/radix prefixes, underscores, Unicode digits, suffix/trailing bytes, + f64 parse, generic parse, rich parse errors, manifest schema changes, + stable helper ABI/layout, or beta maturity. + +## Phase 42: exp-26 F64 To String Alpha + +- [x] Promote exactly `std.num.f64_to_string : (f64) -> string`. +- [x] Return finite decimal ASCII text for promoted finite `f64` fixture + values. +- [x] Lower to a private runtime helper with implementation-owned string + allocation behavior and no stable helper ABI/layout promise. +- [x] Execute the call in the deterministic test runner and preserve it in + formatter, lowering-inspector, hosted-runtime, and diagnostic fixtures. +- [x] Keep Glagol examples/tests byte-aligned with Slovo supported/formatter + fixture hooks for `f64-to-string.slo`. +- [x] Keep exp-26 experimental only: no `f32`, f64 parse, generic + format/display/interpolation, locale/base/radix/grouping/padding/ + precision controls, stable NaN/infinity text, implicit conversion, + manifest schema changes, stable helper ABI/layout, or beta maturity. + +## Phase 43: exp-27 Checked F64 To I32 Result Alpha + +- [x] Promote exactly + `std.num.f64_to_i32_result : (f64) -> (result i32 i32)`. +- [x] Return `ok value` only for finite, exactly integral inputs inside the + signed `i32` range and `err 1` for non-finite, fractional, or + out-of-range input. +- [x] Lower directly to LLVM with ordered range checks before `fptosi`, so + non-finite and out-of-range values do not reach undefined conversion. +- [x] Execute the call in the deterministic test runner and preserve it in + formatter, lowering-inspector, LLVM smoke, and diagnostic fixtures. +- [x] Keep Glagol examples/tests byte-aligned with Slovo supported/formatter + fixture hooks for `f64-to-i32-result.slo`. +- [x] Keep exp-27 experimental only: no unchecked `f64` to `i32`, casts or + cast syntax, generic `cast_checked`, `f32`, unsigned/narrower integer + families, f64 parse, mixed numeric arithmetic, numeric containers, + manifest schema changes, stable ABI/layout, or beta maturity. + +## Phase 45: exp-29 Numeric Struct Fields Alpha + +- [x] Allow direct immutable struct fields whose types are exactly `i64` or + finite `f64`, alongside existing direct `i32`, `bool`, immutable + `string`, and current enum fields. +- [x] Preserve constructor, immutable local, parameter, return, call, and field + access flow for `i64`/`f64` fields. +- [x] Allow accessed numeric fields to use existing same-type arithmetic, + comparison, print, and format helper support without adding new APIs. +- [x] Preserve deferred containers, nested structs, mutation, mixed numeric + arithmetic, implicit conversions, ABI/layout claims, manifest schema + changes, and beta maturity. + +## Phase 46: exp-30 Standard Library Source Layout Alpha + +- [x] Recognize the repo `lib/std/` source layout as a contract artifact + with `README.md`, `io.slo`, `string.slo`, `num.slo`, `result.slo`, and + `math.slo`. +- [x] Add Glagol-local project coverage for an explicit local `math.slo` + import through `examples/projects/std-layout-local-math/`. +- [x] Add an ignored sibling Slovo gate for `std/` file inventory and + source-shape and formatter stability. +- [x] Keep exp-30 experimental only: no new source syntax, new + compiler-known standard-runtime functions, automatic std import/search + path, package registry behavior, manifest schema changes, self-hosted + replacement for compiler-known `std.*` calls, or beta maturity. + +## Phase 47: exp-31 Checked F64 To I64 Result Alpha + +- [x] Promote exactly + `std.num.f64_to_i64_result : (f64) -> (result i64 i32)`. +- [x] Return `ok value` only for finite, exactly integral inputs inside the + signed `i64` range and `err 1` for non-finite, fractional, or + out-of-range input. +- [x] Lower directly to LLVM with ordered range checks before `fptosi`, so + non-finite and out-of-range values do not reach undefined conversion. +- [x] Execute the call in the deterministic test runner and preserve it in + formatter, lowering-inspector, LLVM smoke, hosted-runtime, and + diagnostic fixtures. +- [x] Keep exp-31 experimental only: no unchecked `f64` to `i64`, casts or + cast syntax, generic `cast_checked`, `f32`, unsigned/narrower integer + families, additional numeric conversion families, mixed numeric + arithmetic, numeric containers, manifest schema changes, stable + ABI/layout, or beta maturity. + +## Phase 48: exp-32 Standard Math Source Helpers Alpha + +- [x] Gate the narrow source-authored `std/math.slo` helper set without adding + compiler-known runtime names or compiler-loaded std source. +- [x] Extend `examples/projects/std-layout-local-math/` so an ordinary project + explicitly imports local `math.slo` helpers for `i32`, `i64`, and `f64`. +- [x] Cover `abs_i32`, `min_i32`, `max_i32`, `clamp_i32`, `square_i32`, + `abs_i64`, `min_i64`, `max_i64`, `clamp_i64`, `square_i64`, `abs_f64`, + `min_f64`, `max_f64`, `clamp_f64`, and `square_f64`. +- [x] Add focused project-mode and promotion-gate coverage, including sibling + Slovo `lib/std/math.slo` helper alignment in the ignored monorepo gate. +- [x] Keep exp-32 experimental only: no new compiler-known `std.*` runtime + calls, runtime ABI changes, automatic std import/search, broad math + library, trigonometry/sqrt/pow, `f32`, unsigned/narrower integers, + generic math, overloads, traits, mixed numeric arithmetic, manifest + schema changes, stable ABI/layout, or beta maturity. + +## Phase 49: exp-33 Standard Result Source Helpers Alpha + +- [x] Gate the narrow source-authored `std/result.slo` helper-name extension + without adding compiler-known runtime names or compiler-loaded std + source. +- [x] Add `examples/projects/std-layout-local-result/` so an ordinary project + explicitly imports local `result.slo` helpers for existing concrete + result families. +- [x] Cover `is_err_i64`, `unwrap_err_i64`, `is_err_string`, + `unwrap_err_string`, `is_err_f64`, `unwrap_err_f64`, `unwrap_or_i32`, + `unwrap_or_i64`, `unwrap_or_string`, and `unwrap_or_f64`. +- [x] Add focused project-mode and promotion-gate coverage, including sibling + Slovo `lib/std/result.slo` helper alignment in the ignored monorepo gate. +- [x] Keep exp-33 experimental only: no generic result helpers, no + compiler-known `std.result.unwrap_or`, no map/chaining helpers, no + option helpers, no new result payload families, no new compiler-known + `std.*` runtime calls, no runtime ABI changes, no automatic std + import/search, no manifest schema changes, no stable ABI/layout, and no + beta maturity. + +## Phase 44: exp-28 String Parse F64 Result Alpha + +- [x] Promote exactly + `std.string.parse_f64_result : (string) -> (result f64 i32)`. +- [x] Accept complete finite ASCII decimal text in the narrow + `-?digits.digits` shape and return `ok value`; return `err 1` for + ordinary parse failure, non-finite text, unsupported leading/trailing + characters, or out-of-domain input. +- [x] Add concrete `(result f64 i32)` compiler support for direct fixture + flow, result helpers, lowering, and LLVM payload access without making + results generic or opening source-authored `(result f64 i32)` + constructors/matches. +- [x] Lower hosted builds through private runtime helper + `__glagol_string_parse_f64_result` and execute the call in the + deterministic test runner. +- [x] Preserve formatter, lowering-inspector, LLVM smoke, hosted-runtime, + promotion-gate, and diagnostic fixtures. +- [x] Keep Glagol examples/tests byte-aligned with Slovo supported/formatter + fixture hooks for `string-parse-f64-result.slo`. +- [x] Keep exp-28 experimental only: no generic parse, trap-based + `std.string.parse_f64`, leading `+`, exponent notation, locale parsing, + Unicode digits, rich parse errors, f64 containers, mixed numeric + arithmetic, manifest schema changes, stable helper ABI/layout, or beta + maturity. + +## Phase 45: exp-34 String Parse Bool Result Alpha + +- [x] Promote exactly + `std.string.parse_bool_result : (string) -> (result bool i32)`. +- [x] Accept only complete lowercase ASCII `true` and `false`; return + `ok true` or `ok false`, and return `err 1` for empty text, uppercase or + mixed-case text, whitespace, numeric text, and all other text. +- [x] Add concrete `(result bool i32)` compiler support for direct fixture + flow, result helpers, formatter/lowering snapshots, test-runner + execution, and LLVM payload access without making results generic or + opening source-authored `(result bool i32)` constructors/matches. +- [x] Lower hosted builds through private runtime helper + `__glagol_string_parse_bool_result`. +- [x] Preserve diagnostics, promotion-gate, lowering-inspector, LLVM smoke, + hosted-runtime, and sibling Slovo fixture/std-string source alignment. +- [x] Keep exp-34 experimental only: no generic parse, trap-based + `std.string.parse_bool`, case-insensitive parsing, whitespace trimming, + locale/Unicode parsing, numeric bool parsing, string/bytes parsing, rich + parse errors, manifest schema changes, stable helper ABI/layout, or beta + maturity. + +## Phase 50: exp-35 Standard Result Bool Source Helpers Alpha + +- [x] Gate the narrow source-authored `std/result.slo` bool helper-name + extension without adding compiler-known runtime names or + compiler-loaded std source. +- [x] Extend `examples/projects/std-layout-local-result/` so an ordinary + project explicitly imports local `result.slo` helpers for values coming + from `std.string.parse_bool_result`. +- [x] Cover `is_ok_bool`, `is_err_bool`, `unwrap_ok_bool`, `unwrap_err_bool`, + and `unwrap_or_bool`. +- [x] Add focused project-mode and promotion-gate coverage, including sibling + Slovo `lib/std/result.slo` bool helper alignment in the ignored monorepo + gate. +- [x] Keep exp-35 experimental only: no source constructors for bool results, + no generic bool result match, no compiler-known `std.result.unwrap_or`, + no map/chaining helpers, no option helpers, no new compiler-known + `std.*` runtime calls, no runtime ABI changes, no automatic std + import/search, no manifest schema changes, no stable ABI/layout, and no + beta maturity. + +## Phase 51: exp-36 Standard Option Source Helpers Alpha + +- [x] Gate the narrow source-authored `std/option.slo` helper-name facade + without adding compiler-known runtime names or compiler-loaded std + source. +- [x] Add `examples/projects/std-layout-local-option/` so an ordinary + project explicitly imports local `option.slo` helpers for source-authored + `(option i32)` values from `some` and `none`. +- [x] Cover `is_some_i32`, `is_none_i32`, `unwrap_some_i32`, and + `unwrap_or_i32`. +- [x] Add focused project-mode and promotion-gate coverage, including sibling + Slovo `lib/std/option.slo` helper alignment in the ignored monorepo std + source-layout gate. +- [x] Keep exp-36 experimental only: no new compiler-known `std.*` names, no + generic option helpers, no option payload families beyond `i32`, no + automatic std imports/search, no compiler-loaded std source, no + manifest schema changes, no stable ABI/layout, and no beta maturity. + +## Phase 75: exp-95 Standard Option I64 Baseline Alpha + +- [x] Broaden the concrete option family from only `(option i32)` to + `(option i64)` as well. +- [x] Extend checker, formatter, backend layout, and test-runner behavior for + immutable `(option i64)` params, returns, locals, `some`, `none`, + `is_some`, `is_none`, and `unwrap_some`. +- [x] Broaden source-level `match` payload binding and fixture coverage for + `(option i64)` without widening to generic options. +- [x] Broaden the local `std-layout-local-option/` fixture and the explicit + `std-import-option/` fixture to cover both the i32 and i64 helper + families. +- [x] Broaden the staged `std.option` helper surface to `some_i64`, + `none_i64`, `is_some_i64`, `is_none_i64`, `unwrap_some_i64`, and + `unwrap_or_i64`. +- [x] Keep exp-95 experimental only: no compiler-known `std.option.*` runtime + names, no generic option helpers, no broader payload families, no nested + option containers, no stable ABI/layout guarantee, and no beta maturity. + +## Phase 82: exp-102 Standard Option Bool And F64 Baseline Alpha + +- [x] Broaden the concrete option family from only `(option i32)`, + `(option i64)`, and `(option string)` to `(option f64)` and + `(option bool)` as well. +- [x] Add checker, formatter, backend, and test-runner support for immutable + `(option f64)` and `(option bool)` params, returns, locals, `some`, + `none`, `is_some`, `is_none`, `unwrap_some`, and source `match`. +- [x] Broaden the local `std-layout-local-option/`, explicit + `std-import-option/`, and workspace `std-import-option/` fixtures to + cover the same concrete helper families through `some_f64`, `none_f64`, + `is_some_f64`, `is_none_f64`, `unwrap_some_f64`, `unwrap_or_f64`, + `some_bool`, `none_bool`, `is_some_bool`, `is_none_bool`, + `unwrap_some_bool`, and `unwrap_or_bool`. +- [x] Extend focused option-result coverage, diagnostics snapshots, and + promotion-gate inventory to the same concrete `(option f64)` and + `(option bool)` baseline. +- [x] Keep exp-102 experimental only: no compiler-known `std.option.*` + runtime names, no generic option helpers, no broader payload families, + no nested option containers, no stable ABI/layout guarantee, and no + beta maturity. + +## Phase 81: exp-101 Standard Vec String Option And Transform Helpers Alpha + +- [x] Broaden the staged local `std-layout-local-vec_string/` fixture from + the exp-99 baseline with the option-query helpers `index_option`, + `first_option`, `last_option`, `index_of_option`, and + `last_index_of_option`. +- [x] Broaden the same concrete helper lane with the transform helpers + `concat`, `take`, `drop`, `reverse`, and `subvec`. +- [x] Keep the local and explicit-source vec-string fixtures recursive and + immutable with no `var` or `set`. +- [x] Keep the lane concrete to `(vec string)` plus raw current + `(option string)` / `(option i32)` forms only, with no generic + broadening or broader result families. +- [x] Extend focused vec-string source-helper/source-search coverage and + promotion-gate inventory for the same helper package. +- [x] Keep exp-101 experimental only: no new compiler-known `std.*` runtime + names, no edit helpers, no prefix/suffix helpers, no nested vecs, no + mutating/capacity APIs, no stable ABI/layout guarantee, and no beta + maturity. + +## Phase 80: exp-100 Standard Option String Baseline Alpha + +- [x] Broaden the concrete option family from only `(option i32)` and + `(option i64)` to `(option string)` as well. +- [x] Add checker, formatter, backend, and test-runner support for immutable + `(option string)` params, returns, locals, `some`, `none`, `is_some`, + `is_none`, `unwrap_some`, and source `match`. +- [x] Broaden the local `std-layout-local-option/`, explicit + `std-import-option/`, and workspace `std-import-option/` fixtures to + cover the same concrete helper family through `some_string`, + `none_string`, `is_some_string`, `is_none_string`, + `unwrap_some_string`, and `unwrap_or_string`. +- [x] Extend focused option/result coverage and promotion-gate inventory to + the same concrete `(option string)` baseline. +- [x] Keep exp-100 experimental only: no compiler-known `std.option.*` + runtime names, no generic option helpers, no broader payload families, + no nested option containers, no stable ABI/layout guarantee, and no + beta maturity. + +## Phase 79: exp-99 Standard Vec String Baseline Alpha + +- [x] Add concrete `(vec string)` support for immutable params, returns, and + locals in the checker, formatter, and backend. +- [x] Promote the concrete runtime names `std.vec.string.empty`, + `std.vec.string.append`, `std.vec.string.len`, and + `std.vec.string.index` without widening to generics or other vec + families. +- [x] Add concrete vec-string equality and matching runtime/test-runner + behavior. +- [x] Add the Glagol-owned local `std-layout-local-vec_string/` fixture with + the frozen helper inventory `empty`, `append`, `len`, `at`, + `singleton`, `append2`, `append3`, `pair`, `triple`, `is_empty`, + `index_or`, `first_or`, `last_or`, `contains`, and `count_of`. +- [x] Add the explicit `std-import-vec_string/` fixture and focused Glagol + coverage for the same helper surface, while tolerating mixed-repo + states where sibling `lib/std/vec_string.slo` is not present yet. +- [x] Keep exp-99 experimental only: no generics, no other vec element + families, no option-query helpers, no transform/range/edit helpers, no + nested vecs or vecs inside other containers, no mutating or capacity + APIs, no stable ABI/layout guarantee, and no beta maturity. + +## Phase 78: exp-98 Standard Vec I64 Edit Helpers Alpha + +- [x] Extend the staged `std.vec_i64` source-authored helper surface with + `insert_at`, `insert_range`, `replace_at`, `replace_range`, + `remove_at`, and `remove_range`. +- [x] Keep the local `std-layout-local-vec_i64/` lane recursive and immutable + with no `var` or `set` in the expected source shape. +- [x] Update the explicit `std-import-vec_i64/` fixture to exercise the same + helper inventory against repo-root `lib/std/vec_i64.slo`. +- [x] Extend focused integration coverage and promotion-gate inventory without + widening parser, checker, formatter, runtime, backend, or test-runner + semantics. +- [x] Keep exp-98 experimental only: no generics, no new compiler-known + `std.*` runtime names, no mutating or capacity APIs, no iterators, + sorting, mapping, filtering, and no beta maturity. + +## Phase 77: exp-97 Standard Vec I64 Transform Helpers Alpha + +- [x] Extend the staged `std.vec_i64` source-authored helper surface with + `concat`, `take`, `drop`, `reverse`, and `subvec`. +- [x] Keep the local `std-layout-local-vec_i64/` lane recursive and immutable + with no `var` or `set` in the expected source shape. +- [x] Update the explicit `std-import-vec_i64/` fixture to exercise the same + helper inventory against repo-root `lib/std/vec_i64.slo`. +- [x] Extend focused integration coverage and promotion-gate inventory without + widening parser, checker, formatter, runtime, backend, or test-runner + semantics. +- [x] Keep exp-97 experimental only: no vec_i64 insert/replace/remove + helpers, no generics, no new compiler-known `std.*` runtime names, no + mutating or capacity APIs, no iterators, sorting, mapping, filtering, + and no beta maturity. + +## Phase 76: exp-96 Standard Vec I64 Option Query Helpers Alpha + +- [x] Extend the staged `std.vec_i64` source-authored helper surface with + `index_option`, `first_option`, `last_option`, `index_of_option`, and + `last_index_of_option`. +- [x] Keep the local `std-layout-local-vec_i64/` lane recursive and immutable + with no `var` or `set` in the expected source shape. +- [x] Update the explicit `std-import-vec_i64/` fixture to exercise the same + helper inventory against repo-root `lib/std/vec_i64.slo`. +- [x] Extend focused integration coverage and promotion-gate inventory without + widening parser, checker, formatter, runtime, backend, or test-runner + semantics. +- [x] Keep exp-96 experimental only: no vec_i64 transform/range/edit helpers, + no generics, no new compiler-known `std.*` runtime names, no mutating + or capacity APIs, no iterators, sorting, mapping, filtering, and no + beta maturity. + +## Phase 52: exp-37 Standard Time Source Facade Alpha + +- [x] Gate the narrow source-authored `std/time.slo` facade without adding + compiler-known runtime names or compiler-loaded std source. +- [x] Add `examples/projects/std-layout-local-time/` so an ordinary project + explicitly imports local `time.slo` facades for `monotonic_ms` and + `sleep_ms_zero`. +- [x] Keep `sleep_ms_zero` source-shaped as an `i32` facade that calls + `std.time.sleep_ms 0` and returns `0`, because source unit-return + facade functions are not promoted. +- [x] Add focused project-mode and promotion-gate coverage, including sibling + Slovo `lib/std/time.slo` exact inventory and facade alignment in the ignored + monorepo std source-layout gate. +- [x] Keep exp-37 experimental only: no new compiler-known `std.*` names, no + automatic std imports/search, no compiler-loaded std source, no + wall-clock/calendar/timezone/high-resolution timers, no async timers, + no cancellation or scheduling guarantees, no runtime ABI changes, no + stable ABI/layout, and no beta maturity. + +## Phase 53: exp-38 Standard Host Source Facades Alpha + +- [x] Gate narrow source-authored `std/random.slo`, `std/env.slo`, and + `std/fs.slo` facades without adding compiler-known runtime names or + compiler-loaded std source. +- [x] Add `examples/projects/std-layout-local-random/`, + `examples/projects/std-layout-local-env/`, and + `examples/projects/std-layout-local-fs/` so ordinary projects explicitly + import local facades over already released host/runtime calls. +- [x] Add focused project-mode and promotion-gate coverage, including sibling + Slovo std-source exact inventory and facade alignment in the ignored + monorepo std source-layout gate. +- [x] Keep exp-38 experimental only: no new compiler-known `std.*` names, no + automatic std imports/search, no compiler-loaded std source, no + seed/range/bytes/float/UUID/crypto random APIs, no environment + mutation/enumeration, no binary/directory/streaming/async filesystem + APIs, no rich host error ADTs, no runtime ABI changes, no stable + ABI/layout, and no beta maturity. + +## Phase 54: exp-39 Standard Math Extensions And Benchmark Scaffold Alpha + +- [x] Extend the narrow source-authored `std/math.slo` helper set without + adding compiler-known runtime names or compiler-loaded std source. +- [x] Add `neg`, `cube`, `is_zero`, `is_positive`, `is_negative`, and + inclusive `in_range` helper coverage for `i32`, `i64`, and finite + `f64` in `examples/projects/std-layout-local-math/`. +- [x] Add `benchmarks/math-loop/` with Slovo, C, Rust, and Python sources plus + a runner that reports local-machine timing comparisons only. +- [x] Add focused benchmark scaffold and promotion-gate coverage, including + repo `lib/std/math.slo` helper alignment in the ignored monorepo + std source-layout gate. +- [x] Keep exp-39 experimental only: no new compiler-known `std.*` names, no + automatic std imports/search, no compiler-loaded std source, no + broad math APIs, no generic math, no mixed numeric arithmetic, no + optimizer guarantees, no benchmark thresholds, no runtime ABI changes, + no stable ABI/layout, and no beta maturity. + +## Phase 55: exp-40 Benchmark Suite And License Alpha + +- [x] Add a shared benchmark runner and broaden local benchmark scaffolds to + `math-loop`, `branch-loop`, and `parse-loop`. +- [x] Include Slovo, C, Rust, Python, and Clojure source slots, with missing + toolchains skipped and deterministic checksum validation kept mandatory + for implementations that run. +- [x] Add benchmark scaffold and promotion-gate coverage for benchmark + metadata, language-source inventory, format/check commands, and Python + smoke runs. +- [x] Record project licensing as `MIT OR Apache-2.0` and document alpha/beta + naming criteria. +- [x] Keep exp-40 experimental only: no new language semantics, no optimizer + guarantees, no benchmark thresholds, no cross-machine performance + claims, no runtime ABI changes, no stable ABI/layout, no package + registry behavior, and no beta maturity. + +## Phase 56: exp-41 Lisp Benchmark Comparison Alpha + +- [x] Add Common Lisp/SBCL source slots beside Clojure for `math-loop`, + `branch-loop`, and `parse-loop`. +- [x] Extend the shared benchmark runner to detect SBCL through `sbcl`, + `SBCL`, or `--sbcl`. +- [x] Clarify cold-process benchmark timing: Clojure includes JVM/Clojure + startup, and Common Lisp includes SBCL script startup. +- [x] Add benchmark scaffold and promotion-gate coverage for the second Lisp + source slot. +- [x] Keep exp-41 experimental only: no new language semantics, no optimizer + guarantees, no benchmark thresholds, no cross-machine performance + claims, no runtime ABI changes, no stable ABI/layout, no package + registry behavior, no hot-runtime benchmark daemon, and no beta + maturity. + +## Phase 57: exp-42 Hot Loop Benchmark Mode Alpha + +- [x] Add explicit `--mode cold-process` and `--mode hot-loop` runner modes. +- [x] Record hot-loop counts and hot checksums in each benchmark scaffold. +- [x] Report hot-loop total time and normalized median time for the base + `1000000` loop count. +- [x] Let Slovo benchmark fixtures receive the runner loop count through + process arguments with stdin fallback, so Slovo participates in + hot-loop mode. +- [x] Add benchmark scaffold and promotion-gate coverage for hot-loop + metadata and execution. +- [x] Keep exp-42 experimental only: no new language semantics, no optimizer + guarantees, no high-resolution Slovo timing APIs, no stable benchmark + thresholds, no cross-machine performance claims, no runtime ABI + changes, no stable ABI/layout, no package registry behavior, no + hot-runtime benchmark daemon, and no beta maturity. + +## Phase 58: exp-43 Technical Whitepapers And Skills Alpha + +- [x] Add Glagol technical whitepaper Markdown and generated PDF. +- [x] Add concise compiler manifest Markdown and generated PDF. +- [x] Add repository-local beta compiler-design skill. +- [x] Keep exp-43 experimental only: no parser behavior, checker behavior, + backend behavior, runtime API, compiler-known `std.*` names, optimizer + guarantee, benchmark threshold, cross-machine claim, or beta maturity. + +## Phase 59: exp-44 Standard Library Source Search Alpha + +- [x] Add project-mode source search for explicit `std.` imports, + starting with `std.math`. +- [x] Load `std/math.slo` through `SLOVO_STD_PATH` or a `lib/std` + checkout without copying it into the project. +- [x] Add `examples/projects/std-import-math/` plus focused integration and + promotion-gate coverage. +- [x] Keep exp-44 experimental only: no automatic std imports, + workspace/package std imports, package registry, broad standard library, + stable ABI/layout, optimizer guarantee, or beta maturity. + +## Phase 60: exp-45 Standard Result And Option Source Search Alpha + +- [x] Add explicit project-mode `std.result` and `std.option` source-search + fixtures without local copied modules. +- [x] Gate the fixtures with focused integration tests and promotion-gate + coverage. +- [x] Keep exp-45 experimental only: no automatic std imports, + workspace/package std imports, package registry, generic result/option + helpers, broad standard library, stable ABI/layout, optimizer + guarantee, or beta maturity. + +## Phase 61: exp-46 Workspace Standard Source Search Alpha + +- [x] Load explicit `std.` imports inside workspace packages. +- [x] Resolve loaded standard modules before normal package-qualified + dependency lookup. +- [x] Add `examples/workspaces/std-import-option/` plus focused integration + and promotion-gate coverage. +- [x] Keep exp-46 experimental only: no automatic std imports, workspace + dependency syntax for std, package registry, installed stdlib paths, + broad standard library, stable ABI/layout, optimizer guarantee, or beta + maturity. + +## Phase 62: exp-47 Standard Host Facade Source Search Alpha + +- [x] Gate explicit project-mode `std.time`, `std.random`, `std.env`, and + `std.fs` source imports without local copied modules. +- [x] Add focused integration coverage for format, check, and test behavior + across the host facade fixtures. +- [x] Add promotion-gate coverage for the new fixtures and sibling Slovo + fixture alignment. +- [x] Keep exp-47 experimental only: no automatic std imports, `std.slo` + aggregator, package registry, installed stdlib paths, broad host APIs, + stable ABI/layout, optimizer guarantee, or beta maturity. + +## Phase 63: exp-48 Standard Core Facade Source Search Alpha + +- [x] Gate explicit project-mode `std.string` and `std.num` source imports + without local copied modules. +- [x] Add focused integration coverage for format, check, and test behavior + across the core facade fixtures. +- [x] Add promotion-gate coverage for the new fixtures and sibling Slovo + fixture alignment. +- [x] Keep exp-48 experimental only: no automatic std imports, `std.slo` + aggregator, package registry, installed stdlib paths, generic + parse/format APIs, broad numeric casts, stable ABI/layout, optimizer + guarantee, or beta maturity. + +## Phase 64: exp-49 Standard IO Facade Source Search Alpha + +- [x] Gate explicit project-mode `std.io` source imports without a local + copied module. +- [x] Add focused integration coverage for format, check, and deterministic + test behavior. +- [x] Add promotion-gate coverage for the new fixture and sibling Slovo + fixture alignment. +- [x] Keep exp-49 experimental only: no automatic std imports, `std.slo` + aggregator, package registry, installed stdlib paths, new + compiler-known IO names, broad IO APIs, stable ABI/layout, optimizer + guarantee, or beta maturity. + +## Phase 65: exp-50 Installed Standard Library Discovery Alpha + +- [x] Gate executable-relative installed stdlib discovery for explicit + `std.` source imports. +- [x] Add focused integration coverage that copies the compiler binary and + resolves a probe module from `share/slovo/std`. +- [x] Add promotion-gate coverage for discovery candidates and diagnostics. +- [x] Keep exp-50 experimental only: no automatic std imports, `std.slo` + aggregator, package registry, lockfiles, package std dependencies, + stable install layout guarantee, broad stdlib APIs, stable ABI/layout, + optimizer guarantee, or beta maturity. + +## Phase 66: exp-51 Standard Library Path List Alpha + +- [x] Treat `SLOVO_STD_PATH` as an ordered OS path list. +- [x] Add focused integration coverage for an empty first root and a second + root containing the requested standard module. +- [x] Add promotion-gate coverage for path-list handling. +- [x] Keep exp-51 experimental only: no automatic std imports, `std.slo` + aggregator, package registry, lockfiles, package std dependencies, + semantic version solving, stable package manager behavior, broad stdlib + APIs, stable ABI/layout, optimizer guarantee, or beta maturity. + +## Phase 67: exp-52 Standard Process Facade Source Search Alpha + +- [x] Add source-authored `std/process.slo` facade wrappers over the already + promoted process argument runtime calls. +- [x] Gate explicit project-mode `std.process` source imports without a local + copied module. +- [x] Add focused integration coverage and promotion-gate coverage. +- [x] Keep exp-52 experimental only: no automatic std imports, `std.slo` + aggregator, package registry, lockfiles, package std dependencies, + process spawning, exit/status control, current-directory APIs, signal + handling, stable ABI/layout, optimizer guarantee, or beta maturity. + +## Phase 68: exp-53 Standard CLI Source Facade Alpha + +- [x] Add source-authored `std/cli.slo` facade helpers that compose + `std.process` and `std.string`. +- [x] Gate explicit project-mode `std.cli` source imports without a local + copied module. +- [x] Add focused integration coverage and promotion-gate coverage for + transitive standard-source imports. +- [x] Keep exp-53 experimental only: no automatic std imports, `std.slo` + aggregator, package registry, lockfiles, package std dependencies, + shell parsing, option/flag parsing, subcommands, environment-backed + configuration, stable CLI framework APIs, stable ABI/layout, optimizer + guarantee, or beta maturity. + +## Phase 69: exp-54 Standard CLI Typed Arguments Alpha + +- [x] Extend `std/cli.slo` with `arg_i64_result` and `arg_i64_or_zero`. +- [x] Add direct `arg_f64_result` and `arg_bool_result` parse-result wrappers + over `std.process.arg`. +- [x] Update focused integration coverage and promotion-gate coverage for the + expanded helper inventory. +- [x] Keep exp-54 experimental only: no automatic std imports, `std.slo` + aggregator, package registry, lockfiles, package std dependencies, new + compiler-known runtime names, shell parsing, option/flag parsing, + subcommands, environment-backed configuration, stable CLI framework + APIs, stable ABI/layout, optimizer guarantee, or beta maturity. + +## Phase 70: exp-55 Result F64 Bool Source Flow Alpha + +- [x] Promote source constructors for `(result f64 i32)` and + `(result bool i32)`. +- [x] Promote source `match` payload bindings for `(result f64 i32)` and + `(result bool i32)`. +- [x] Add focused coverage for formatter, checker, test runner, and LLVM + aggregate shape. +- [x] Update `std.cli` coverage so `f64` and `bool` missing argument indexes + are result-based. +- [x] Keep exp-55 experimental only: no generic result types, no result + payload families beyond the already promoted concrete families, no + generic result combinators, stable ABI/layout, optimizer guarantee, or + beta maturity. + +## Phase 71: exp-56 Integer Remainder Alpha + +- [x] Add `%` surface/lowering support for same-width `i32` and `i64` + operands. +- [x] Gate checker, formatter, test-runner, and LLVM `srem` behavior. +- [x] Reject `f64` remainder explicitly while keeping mixed numeric operand + diagnostics. +- [x] Add `examples/integer-remainder.slo` and sibling Slovo fixture + alignment. +- [x] Extend `std.math` with `rem_i32`, `is_even_i32`, `is_odd_i32`, + `rem_i64`, `is_even_i64`, and `is_odd_i64`. +- [x] Keep exp-56 experimental only: no floating-point remainder, Euclidean + modulo, unsigned arithmetic, bit operations, generic math, stable + ABI/layout, optimizer guarantee, or beta maturity. + +## Phase 72: exp-57 Integer Bitwise Alpha + +- [x] Add `bit_and`, `bit_or`, and `bit_xor` surface/lowering support for + same-width `i32` and `i64` operands. +- [x] Gate checker, formatter, test-runner, and LLVM `and`/`or`/`xor` + behavior. +- [x] Reject `f64` bitwise operations explicitly while keeping mixed numeric + operand diagnostics. +- [x] Add `examples/integer-bitwise.slo` and sibling Slovo fixture alignment. +- [x] Extend `std.math` with `bit_and_i32`, `bit_or_i32`, `bit_xor_i32`, + `bit_and_i64`, `bit_or_i64`, and `bit_xor_i64`. +- [x] Keep exp-57 experimental only: no shifts, bit-not, unsigned arithmetic, + bit-width-specific integer families, generic math, stable ABI/layout, + optimizer guarantee, or beta maturity. + +## Phase 73: exp-58 Boolean Logic Alpha + +- [x] Add `and`, `or`, and `not` formatter/lowering support. +- [x] Lower `and` and `or` through existing `if` semantics so they + short-circuit. +- [x] Add `examples/boolean-logic.slo` and sibling Slovo fixture alignment. +- [x] Gate checker, test-runner, formatter, and LLVM branch shape. +- [x] Keep exp-58 experimental only: no truthiness, variadic boolean + operators, pattern guards, macro expansion, stable ABI/layout, + optimizer guarantee, or beta maturity. + +## Phase 74: exp-59 Hosted Build Optimization And Benchmark Publication Alpha + +- [x] Build hosted executables through `clang -O2`. +- [x] Lower current immutable numeric locals as SSA values where no stack + storage is required. +- [x] Refresh benchmark methodology and evidence in the publication + documents. +- [x] Add repo-local PDF rendering scripts and wire them into the release + gate. +- [x] Keep exp-59 experimental only: no benchmark thresholds, no + cross-machine performance claims, no broad optimizer guarantees, no + general SSA lowering for every value family, and no beta maturity. + +## Phase 75: exp-119 Benchmark Suite Extension And Whitepaper Refresh Alpha + +- [x] Add `benchmarks/array-index-loop/` with Slovo, C, Rust, Python, + Clojure, and Common Lisp/SBCL scaffold implementations. +- [x] Add `benchmarks/string-eq-loop/` with Slovo, C, Rust, Python, Clojure, + and Common Lisp/SBCL scaffold implementations. +- [x] Keep the shared benchmark runner model unchanged while extending focused + benchmark scaffold and promotion-gate coverage from three kernels to + five. +- [x] Refresh the Glagol whitepaper and publication docs around the expanded + benchmark suite and the current experimental compiler surface. +- [x] Keep exp-119 experimental only: no language-surface change, no runtime + ABI claim, no benchmark thresholds, no cross-machine performance claims, + no broad optimizer guarantee, and no beta maturity. + +## Phase 76: exp-120 Fixed Array Struct Fields Alpha + +- [x] Allow direct struct field declarations over `(array i32 N)`, + `(array i64 N)`, `(array f64 N)`, `(array bool N)`, and + `(array string N)`. +- [x] Keep fixed arrays positive-length only, immutable only, and limited to + the already promoted direct scalar and string element families. +- [x] Add promoted `array-struct-fields` fixture coverage, formatter/lowering + snapshots, focused Glagol tests, and diagnostic snapshot updates. +- [x] Keep exp-120 experimental only: no zero-length arrays, no array or field + mutation, no nested arrays, no arrays of unsupported element kinds, no + array equality/printing, no stable ABI/layout claim, and no beta + maturity. + +## Phase 77: exp-121 Non-Recursive Struct Enum Payloads Alpha + +- [x] Allow unary enum payload variants whose payload type is a current known + non-recursive struct type. +- [x] Keep one payload per payload variant and the existing same-payload-type + rule when payload variants are present. +- [x] Add promoted `enum-payload-structs` fixture coverage, formatter/lowering + snapshots, focused Glagol tests, and diagnostic snapshot updates. +- [x] Keep exp-121 experimental only: no direct array/vec/option/result + payloads, no equality requirement for struct-payload enums, no mutation, + no recursion, no stable ABI/layout claim, and no beta maturity. + +## Phase 78: exp-122 Composite Data Benchmark Suite Extension And Whitepaper Refresh Alpha + +- [x] Add `benchmarks/array-struct-field-loop/` with Slovo, C, Rust, Python, + Clojure, and Common Lisp/SBCL scaffold implementations. +- [x] Add `benchmarks/enum-struct-payload-loop/` with Slovo, C, Rust, Python, + Clojure, and Common Lisp/SBCL scaffold implementations. +- [x] Keep the shared benchmark runner model unchanged while extending focused + benchmark scaffold and promotion-gate coverage from five kernels to + seven. +- [x] Refresh the Glagol whitepaper and publication docs around the expanded + suite and the current experimental compiler surface through `exp-121`. +- [x] Keep exp-122 experimental only: no language-surface change, no runtime + ABI claim, no benchmark thresholds, no cross-machine performance claims, + no broad optimizer guarantee, and no beta maturity. + +## Phase 79: exp-123 Owned Vector Benchmark Suite Extension And Whitepaper Refresh Alpha + +- [x] Add `benchmarks/vec-i32-index-loop/` with Slovo, C, Rust, Python, + Clojure, and Common Lisp/SBCL scaffold implementations. +- [x] Add `benchmarks/vec-string-eq-loop/` with Slovo, C, Rust, Python, + Clojure, and Common Lisp/SBCL scaffold implementations. +- [x] Keep the shared benchmark runner model unchanged while extending focused + benchmark scaffold and promotion-gate coverage from seven kernels to + nine. +- [x] Refresh the Glagol whitepaper and publication docs around the expanded + suite and the current experimental compiler surface through `exp-121`. +- [x] Keep exp-123 experimental only: no language-surface change, no runtime + ABI claim, no benchmark thresholds, no cross-machine performance claims, + no broad optimizer guarantee, and no beta maturity. + +## Deferred + +- [ ] Macros. +- [ ] Generics. +- [ ] Concurrency. +- [ ] Advanced ownership. +- [ ] Package manager. +- [ ] Direct machine-code backend. +- [ ] Direct machine-code backend; hosted native builds use emitted LLVM IR + plus Clang and `runtime/runtime.c`. +- [ ] Stdin source input until source naming and source spans are specified. +- [ ] JSON output for lowering inspection. +- [ ] User-visible `unit` values and function signatures until Slovo specifies + their source semantics. +- [ ] Pointer types, raw memory operations, unchecked indexing, + reinterpretation, raw `ffi_call`, broad FFI, and other raw unsafe + operations. Preserve lexical unsafe diagnostics meanwhile; exp-6 only + promotes explicit `i32` C scalar imports. +- [ ] Mutable arrays, array mutation, arrays of other unsupported element + types, zero-length arrays, slices, equality, + printing, nested arrays, unchecked indexing, and stable ABI/layout + promises. +- [ ] Numeric expansion beyond exp-29 direct `f64`, direct `i64`, explicit + widening conversions, the checked `i64 -> i32` result conversion, and + `i32`/`i64` decimal-to-string calls, the single strict string-to-`i64` + result parse, finite `f64` to string, checked `f64 -> i32` result, and + strict string-to-`f64` result parse, plus direct `i64`/`f64` struct + fields, + including f32, + unsigned/narrower integer + families, char/bytes/decimal, additional narrowing or checked + conversions, cast syntax, mixed numeric arithmetic, numeric containers, + broader parse/format APIs, random numeric APIs, stable ABI/layout, and + beta maturity. +- [ ] Generic vectors, non-`i32` vectors, vector mutation, nested vectors, + vectors inside other containers, and broader growable collections until a + later Slovo contract promotes their syntax, semantics, diagnostics, and + runtime behavior. +- [ ] Option/result matching outside the exact v1.4 `(option i32)`, + `(result i32 i32)`, and exp-25 `(result i64 i32)` two-arm forms, other + non-`i32` payloads, nested + option/result values, option/result printing, and stable ABI/layout + promises. +- [ ] Result helper expansion beyond exp-33 concrete source helpers, including + compiler-known `std.result.unwrap_or`, `std.result.map`, + `std.result.and_then`, generic result helpers, option standard helper + names, and new result payload families beyond the current concrete + families. +- [ ] Mutable strings, string concatenation, string indexing/slicing, strings + in containers, broad runtime printing beyond promoted legacy aliases and + v1.5 `std.io.print_*` names, ownership, allocation/free, Unicode length + semantics, and stable ABI promises. +- [ ] Parsing beyond the released exp-13 + `std.string.parse_i32_result : (string) -> (result i32 i32)`, exp-25 + `std.string.parse_i64_result : (string) -> (result i64 i32)`, and + exp-28 `std.string.parse_f64_result : (string) -> (result f64 i32)`, + and exp-34 + `std.string.parse_bool_result : (string) -> (result bool i32)` slices, + including trap-based parse, string/bytes parse families, + whitespace/locale/base-prefix/underscore/plus-sign parsing, generic + parse APIs, rich parse errors, Unicode digit parsing, string + indexing/slicing, tokenizer/scanner APIs, stdin line APIs, stable helper + ABI/layout, runtime headers/libraries, manifest schema changes, and beta + maturity. +- [ ] `std.io.print_unit`, host IO beyond the exp-3 slice, networking, async + IO, binary file APIs, directory traversal, terminal control, + result-based host errors beyond the exp-10 slice, stdin iteration, + wall-clock time, timers, time zones, generic vectors/maps/sets or other + collections, user-defined standard modules, imports/packages for + standard-runtime names, + overloading, generic standard-library APIs, and FFI contracts. +- [ ] Package registries, lockfiles, semver solving, remote dependencies, + publishing, optional/dev/target dependencies, stable package ABI, + hierarchical modules, import aliases/globs/qualified access, re-exports, + generated sources, project formatting, watch/LSP, incremental builds, + cross-compilation, and public ABI promises beyond the exp-5 local + workspace/package contract. +- [ ] Stable LLVM debug metadata, DWARF emission, and source-map files. diff --git a/docs/language/DESIGN.md b/docs/language/DESIGN.md new file mode 100644 index 0000000..31f1796 --- /dev/null +++ b/docs/language/DESIGN.md @@ -0,0 +1,291 @@ +# Slovo Design Notes + +> **Design is how the manifesto survives contact with implementation.** + +`MANIFEST.md` explains why Slovo exists. + +`DESIGN.md` explains how Slovo should resist drift. + +`SPEC-v0.md` defines the first tiny version. + +--- + +## 1. Design Thesis + +Slovo is a typed structural language aimed at systems programming. + +Its defining promise is continuity of structure: + +```text +source form +↓ +parsed tree +↓ +checked tree +↓ +lowered tree +↓ +LLVM IR +↓ +machine code +``` + +The tree should remain visible across the pipeline. + +The written form should not be a disguise. + +The written form should be the beginning of the compiler's truth. + +--- + +## 2. Design Laws + +### Law 1: The Tree Is the Language + +Source code should map directly to tree structure. + +No operator precedence. + +No grammar puzzles. + +No hidden parse tricks. + +### Law 2: Canonical Core, Possible Surface + +Slovo may eventually have surface conveniences. + +But every convenience must lower into one canonical core meaning. + +Convenience must not hide meaning. + +### Law 3: Explicit Beats Implicit + +No hidden numeric casts. + +No magic imports. + +No invisible control flow. + +No unmarked unsafe behavior. + +### Law 4: Lowering Is a Contract + +Every feature must explain how it lowers. + +A feature is not accepted because it is expressive. + +A feature is accepted when its meaning remains visible after lowering. + +### Law 5: Tooling Is Part of the Language + +Parser, formatter, type checker, structured diagnostics, lowering inspector, test runner, and package layout are part of the language promise. + +A feature that cannot be formatted, diagnosed, lowered, and tested is not ready. + +### Law 6: Safety Must Be Precise + +Safety claims must be specific. + +If Slovo says code is safe, it must define what kind of machine-level danger safe code excludes. + +### Law 7: Hype Must Not Design the Language + +Slovo is LLM-friendly, but not AI-hype-driven. + +The goal is durable structure useful to humans, tools, compilers, and machines. + +--- + +## 3. Safety Model v0 + +Safe Slovo code should guarantee: + +- values are initialized before use +- nullable absence uses `option` +- recoverable failure uses `result` +- casts are explicit +- safe array and slice access is bounds-checked +- raw pointers require `unsafe` +- allocation and deallocation primitives require `unsafe` +- unsafe code is lexically visible +- uninitialized memory is not observable from safe code + +Safe Slovo does **not** initially guarantee: + +- data-race freedom +- deterministic real-time behavior +- absence of panics/traps +- absence of logic errors +- absence of resource leaks +- full memory safety across FFI +- Rust-like borrow checking + +--- + +## 4. Memory Model v0 + +The recommended v0 memory model: + +```text +safe code: +- managed heap values +- no raw pointer operations +- bounds-checked slices +- option/result instead of null/exceptions + +unsafe code: +- raw pointers +- explicit allocation APIs +- explicit deallocation APIs +- pointer load/store +- pointer arithmetic, if added, only in unsafe +``` + +No concurrency memory model is defined in v0. + +Possible v1+ directions: + +- arenas +- regions +- ownership +- affine resources +- FFI memory contracts +- layout annotations +- controlled unsafe abstractions + +The v0 goal is not to beat Rust. + +The v0 goal is to be honest, implementable, and structurally clean. + +--- + +## 5. Type-System Restraint + +Slovo begins with explicit types and limited inference. + +Inference may be allowed only where the expected type is already clear from context. + +Initial type families: + +```text +primitive: + bool + i8 i16 i32 i64 + u8 u16 u32 u64 + f32 f64 + char + string + unit + never + +compound: + (ptr T) + (array T N) + (slice T) + (option T) + (result T E) +``` + +Generics should wait until the core is stable. + +--- + +## 6. Lowering Discipline + +Every feature must have: + +1. surface syntax +2. typed-core representation +3. lowering rule +4. diagnostic behavior +5. formatter behavior +6. test examples + +A feature without a lowering rule is incomplete. + +A feature without diagnostics is incomplete. + +A feature without formatting is incomplete. + +A feature without tests is incomplete. + +--- + +## 7. Diagnostics Contract + +Human-readable diagnostic: + +```text +Cannot call add with argument 2. + +Expected: + i32 + +Found: + string + +At: + (add 3 "hello") + +Hint: + Use an integer value or convert explicitly. +``` + +Machine-readable diagnostic: + +```slo +(error + (code TypeMismatch) + (expected i32) + (found string) + (span "main.slo" 12 8 12 15) + (hint "Use an integer value or convert explicitly.")) +``` + +Diagnostics should include: + +- stable error code +- source span +- expected value/type/form +- found value/type/form +- explanation +- possible repair hint when safe + +Diagnostics are a tool API, not only user messages. + +--- + +## 8. Formatter Contract + +The formatter is canonical. + +A Slovo implementation that accepts code the formatter cannot canonicalize is incomplete. + +The formatter should: + +- normalize indentation +- preserve tree structure +- avoid stylistic alternatives +- make nested forms readable +- never change program meaning + +Formatting is shared understanding. + +--- + +## 9. Feature Admission Checklist + +Before adding a feature, ask: + +1. Does it preserve visible structure? +2. Does it lower into typed core? +3. Can the formatter canonicalize it? +4. Can diagnostics explain it? +5. Can tools inspect it? +6. Can LLMs generate and repair it reliably? +7. Does it keep unsafe behavior explicit? +8. Does it avoid hidden machine costs? +9. Does it strengthen Slovo's identity? +10. Can it wait? + +If it can wait, it probably should. diff --git a/docs/language/MANIFEST.md b/docs/language/MANIFEST.md new file mode 100644 index 0000000..bd7b414 --- /dev/null +++ b/docs/language/MANIFEST.md @@ -0,0 +1,395 @@ +# Slovo Language Manifest: A Typed Structural Source Contract + +Sanjin Gumbarevic
+hermeticum_lab@protonmail.com + +> **The tree is the language.** + +**Slovo** (ⰔⰎⰑⰂⰑ) is a programming language designed for humans, tools, compilers, LLMs, and machines. + +Its name means **word**. + +Its Glagolitic spelling is ⰔⰎⰑⰂⰑ. + +Slovo is built on a simple belief: + +> Source code should already be structure. + +A Slovo program should be easy to read, easy to generate, easy to check, easy to transform, easy to repair, and easy to compile. + +Slovo is not designed to be clever. + +Slovo is designed to be clear. + +--- + +## Purpose + +Slovo exists for an age where programs are written, read, checked, transformed, and repaired by both humans and machines. + +It is designed for: + +- humans who want clarity +- tools that need structure +- compilers that need precision +- machines that need efficient code + +LLMs are treated as tools that generate, repair, and explain code. + +Slovo should allow a human, an artificial intelligence, a static analyzer, and a compiler to inspect the same program structure, and allow the machine to execute code derived from that structure. + +The goal is not only to write programs. + +The goal is to write meaning in a form that can become machine code. + +--- + +## Core Idea + +Most programming languages hide their structure behind syntax. + +Slovo exposes structure directly. + +In Slovo, source code is tree-shaped. + +A form looks like this: + +```slo +(name argument argument argument) +``` + +Example: + +```slo +(+ 2 3) +``` + +A function is also a form: + +```slo +(fn add ((a i32) (b i32)) -> i32 + (+ a b)) +``` + +The source is close to the AST. + +The AST is close to the compiler. + +The compiler is close to the machine. + +> **The tree is the language.** + +--- + +## What Makes Slovo Different + +Slovo is not primarily a Lisp, a scripting language, a macro system, or an academic intermediate representation. + +Slovo is a typed structural language aimed at systems programming, where the written program, the checked program, and the lowered program remain visibly related. + +Its central bet is that a language can make source code look like a tree without giving up native compilation, safety, tooling, or practical systems programming. + +Slovo is built around continuity of structure: + +```text +written tree +↓ +parsed tree +↓ +checked tree +↓ +lowered tree +↓ +LLVM IR +↓ +machine code +``` + +The written form should not be a disguise. + +The written form should be the beginning of the compiler's truth. + +--- + +## The Four Readers + +Slovo serves four readers: + +- the human +- the tool +- the compiler +- the machine + +LLMs are treated as tools that generate, repair, and explain code. + +### The Human + +A human should be able to read Slovo without guessing. + +The language should avoid hidden behavior, ambiguous grammar, precedence traps, and unnecessary stylistic choices. + +### The Tool + +A tool should be able to parse, format, inspect, transform, repair, test, and explain Slovo code reliably. + +Tooling is part of the language. + +### The Compiler + +A Slovo compiler should not need heroic parsing tricks. + +The grammar should be small. + +The core language should be smaller. + +Every advanced feature should lower into a stable typed core. + +### The Machine + +Slovo should not be only symbolic. + +It should compile efficiently. + +The primary compilation target is **LLVM IR**. + +--- + +## Canonical Form, Not Crudeness + +Slovo values consistency over personal style. + +There should not be five competing ways to increment a value. + +Not this: + +```text +x++ +x += 1 +inc x +x = x + 1 +``` + +But this canonical core meaning: + +```slo +(set x (+ x 1)) +``` + +A Slovo formatter is part of the language. + +Formatting is not decoration. + +Formatting is shared understanding. + +Surface conveniences may exist if they lower clearly into one canonical core form and the formatter makes their structure obvious. + +The rule is not that Slovo must be minimal at all costs. + +The rule is that Slovo must not let convenience hide meaning. + +--- + +## Explicitness + +Slovo does not guess silently. + +A value of one type does not secretly become another type. + +This should not be allowed: + +```slo +(+ 1 2.5) +``` + +The conversion must be explicit: + +```slo +(+ (cast f64 1) 2.5) +``` + +Explicitness makes programs easier for humans to review, easier for tools to repair, and easier for compilers to trust. + +--- + +## Safety and Memory + +In safe Slovo code: + +- values are initialized before use +- nullable absence is represented by `option` +- recoverable failure is represented by `result` +- casts are explicit +- array and slice access is bounds-checked +- raw memory operations require `unsafe` +- unsafe code is lexically visible +- uninitialized memory is not observable from safe code + +Safety does not mean the program is correct. + +Safety means ordinary Slovo code avoids classes of machine-level undefined behavior unless the programmer explicitly enters `unsafe`. + +Early Slovo should begin with a simple memory model: + +```text +v0: +- managed heap values for safe code +- raw pointers only in unsafe +- slices are bounds-checked +- option/result instead of null/exceptions +- explicit allocation APIs +- no concurrency memory model yet +``` + +Later Slovo may evolve toward: + +```text +v1+: +- arenas / regions +- ownership or affine resources if needed +- FFI memory contracts +- layout annotations +``` + +Memory must never be hidden magic. + +Allocation, ownership, aliasing, and lifetime should become more visible as code moves closer to the machine. + +--- + +## The Lowering Principle + +Every surface feature must explain how it lowers. + +If a feature cannot be described as a transformation into the typed core, it does not belong in the language yet. + +A feature is not accepted because it is expressive. + +A feature is accepted when its meaning remains visible after lowering. + +Lowering is not an implementation detail. + +Lowering is part of the language contract. + +--- + +## LLVM as the Main Target + +Slovo aims at LLVM. + +The intended compiler pipeline is: + +```text +Slovo source +↓ +S-expression parser +↓ +surface AST +↓ +desugaring +↓ +typed core AST +↓ +Slovo IR +↓ +LLVM IR +↓ +native machine code +``` + +LLVM is not only a backend target. + +It is a constraint that forces Slovo to define types, control flow, memory, and calling conventions precisely. + +--- + +## Tooling Is Part of the Language + +The parser, formatter, type checker, diagnostic format, package layout, test runner, and lowering inspector are part of Slovo's promise. + +They are not optional accessories. + +A Slovo implementation that accepts code the formatter cannot canonicalize is incomplete. + +A Slovo implementation that reports errors only as unstructured text is incomplete. + +A Slovo implementation that cannot show how a surface form lowers is incomplete. + +The toolchain should make the tree visible. + +--- + +## Non-Goals + +Slovo is not trying to: + +- replace Python for quick scripting +- be maximally terse +- support every programming paradigm +- become a macro playground in early versions +- hide machine costs +- optimize for clever one-liners +- compete with Lisp on dynamic metaprogramming +- compete with C on minimal implementation size +- compete with Rust on advanced borrow-checking before Slovo's core is stable + +A language becomes stronger when it knows what it is not trying to be. + +--- + +## Slovo and Lisp + +Slovo honors Lisp. + +It inherits the belief that simple symbolic forms can express deep systems. + +But Slovo is not Lisp renamed. + +Lisp traditionally treats code-as-data as a source of expressive power. + +Slovo treats code-as-tree as a source of compile-time discipline. + +Lisp asks: + +> What can the programmer express? + +Slovo asks: + +> What can the human, tool, compiler, and machine all agree on? + +Slovo takes Lisp's structural beauty and aims it at modern compilation. + +--- + +## The Logo + +The Glagolitic spelling of Slovo is: + +ⰔⰎⰑⰂⰑ + +It is written letter by letter as *slovo*. + +It should be drawn simply, with respect for its origin. + +The symbol should be used with cultural humility, not as exotic ornament. + +A good Slovo logo should feel like an old letter that learned to compile. + +--- + +## Closing Declaration + +Slovo begins with a simple belief: + +A programming language can be symbolic without being vague. + +It can be low-level without being hostile. + +It can be AI-friendly without being gimmicky. + +It can be old in spirit and new in purpose. + +It can treat code as words, words as structure, and structure as something the machine can faithfully execute. + +**Slovo is a language where source is structure.** + +**The tree is the language.** diff --git a/docs/language/MIGRATION_POLICY.md b/docs/language/MIGRATION_POLICY.md new file mode 100644 index 0000000..14c0572 --- /dev/null +++ b/docs/language/MIGRATION_POLICY.md @@ -0,0 +1,57 @@ +# Slovo Migration Policy + +Slovo changes must preserve the support rule: a feature is supported only when +Slovo documentation and Glagol behavior agree across syntax, typed meaning, +lowering, formatter behavior, diagnostics, tests, and examples. + +## Compatibility Baselines + +The v0 compatibility fixtures live under `examples/compat/v0/`. + +- `examples/compat/v0/supported/` freezes the v0 supported program fixtures. +- `examples/compat/v0/formatter/` freezes the v0 formatter fixtures. + +These files are compatibility baselines. Do not edit them for ordinary v1 +feature promotion. Add new promoted examples under `examples/supported/` and +`examples/formatter/` instead. + +## Allowed Change Classes + +Additive changes may add new supported forms when `SPEC-v1.md`, examples, +formatter fixtures, diagnostics, Glagol implementation, and tests are updated +together. + +Clarifying changes may improve wording without changing accepted syntax, +observable behavior, diagnostic codes, or canonical formatting. + +Migration changes alter accepted syntax, diagnostic shape, canonical formatting, +or runtime behavior for an existing supported fixture. They require an explicit +migration note before implementation is treated as supported. + +## Migration Requirements + +A migration change must: + +- name the affected release baseline and fixtures +- explain the old behavior and the new behavior +- update `SPEC-v1.md` or the next release spec before compiler promotion +- update `RELEASE_NOTES.md` +- keep v0 compatibility fixtures intact unless the release explicitly retires + them +- add or update Glagol diagnostics so unsupported old forms fail clearly +- update formatter fixtures when canonical layout changes +- update promotion gates or checklist items when the verification path changes + +Slovo must not silently repurpose an old supported form with new meaning. + +## Diagnostic And Tooling Changes + +Diagnostic code or machine-shape changes require a documented schema migration +and matching Glagol snapshot updates. Formatter changes require before/after +fixture coverage so agents can see whether the change is additive, +clarifying, or migration-level. + +Native executable output, package layout, stable ABI/layout promises, stable +standard-runtime printing APIs, and raw-memory/FFI contracts remain outside the +current compatibility promise until a future release spec defines them. The +promoted v1 `slovo.artifact-manifest` schema is governed by `SPEC-v1.md`. diff --git a/docs/language/RELEASE_NOTES.md b/docs/language/RELEASE_NOTES.md new file mode 100644 index 0000000..80d0bf4 --- /dev/null +++ b/docs/language/RELEASE_NOTES.md @@ -0,0 +1,5704 @@ +# Slovo Release Notes + +## Release Maturity Policy + +Historical `exp-*` releases listed here are experimental maturity milestones. +`1.0.0-beta` is the first real general-purpose beta release. + +The pushed tag `v2.0.0-beta.1` is historical. It is now documented as an +experimental integration/readiness release, not as a beta maturity claim. + +The current release is `1.0.0-beta`, published on 2026-05-21. It absorbs the +final unsigned precursor scope from `exp-125` and promotes the current +project/package, stdlib-source, collection, composite-data, diagnostics, +formatter, docs, and governance surface to beta maturity. + +## 1.0.0-beta + +Release label: `1.0.0-beta` + +Release name: First Real General-Purpose Beta + +Release date: 2026-05-21 + +Status: released beta language contract. + +The normative Slovo-side beta contract is `.llm/V1_0_0_BETA.md`. + +`1.0.0-beta` includes: + +- `i32`, `i64`, `u32`, `u64`, `f64`, `bool`, and `string` +- current fixed immutable arrays, current concrete vector families, structs, + enums, option/result, and lexical `unsafe` gating +- explicit `std/*.slo` imports for strings, numerics, IO, CLI, process args, + env, text FS, time, randomness, result/option, and concrete vectors +- local packages, workspaces, deterministic dependency handling, generated + docs, formatter modes, JSON diagnostics, and the paired local release gate + +`1.0.0-beta` still defers generics, maps/sets, remote registries, +networking/async, and stable ABI/layout/ownership guarantees. + +## exp-125 (final experimental precursor scope) + +Release label: `exp-125` + +Release name: Unsigned U32 U64 Numeric And Stdlib Breadth Alpha + +Target date: 2026-05-21 + +Status: completed experimental precursor scope absorbed into `1.0.0-beta`. + +The normative Slovo-side contract target is +`.llm/EXP_125_UNSIGNED_U32_U64_NUMERIC_AND_STDLIB_BREADTH_ALPHA.md`. + +exp-125 broadens the numeric and staged stdlib surface with one connected +unsigned scope: + +- direct `u32` and `u64` value flow +- decimal suffixed literals `42u32` and `42u64` +- same-type unsigned arithmetic and comparison +- compiler-known `std.io.print_u32`, `std.io.print_u64`, + `std.num.u32_to_string`, `std.num.u64_to_string`, + `std.string.parse_u32_result`, and `std.string.parse_u64_result` +- source helper parity across `std.result`, `std.option`, `std.string`, + `std.num`, `std.io`, `std.env`, `std.fs`, `std.process`, and `std.cli` + +The matching Slovo-side fixtures include +`examples/supported/u32-numeric-primitive.slo`, +`examples/supported/u64-numeric-primitive.slo`, +`examples/supported/unsigned-integer-to-string.slo`, +`examples/supported/string-parse-u32-result.slo`, +`examples/supported/string-parse-u64-result.slo`, their formatter mirrors, +and the widened explicit import fixtures under `examples/projects/std-import-*`. + +exp-125 itself did not claim beta maturity, implicit numeric promotion, mixed +signed/unsigned arithmetic, broader cast families, unsigned widths beyond +`u32`/`u64`, generic numeric helpers, unsigned collections, unsigned enum +payloads or struct fields, stable ABI/layout/ownership, or released compiler +support before the sibling Glagol work landed. + +## exp-124 (released experimental alpha) + +Release label: `exp-124` + +Release name: Fixed Array Enum And Struct Elements Alpha + +Release date: 2026-05-21 + +Status: released experimental alpha language contract, not beta. + +The normative Slovo-side contract target is +`.llm/EXP_124_FIXED_ARRAY_ENUM_AND_STRUCT_ELEMENTS_ALPHA.md`. + +exp-124 broadens the fixed immutable array surface with two connected element +lanes: + +- direct known enum elements +- current known non-recursive struct elements + +The release promotes immutable locals, parameters, returns, calls returning +arrays, and checked `i32` indexing over those new element families. It adds +`examples/supported/array-enum.slo`, +`examples/formatter/array-enum.slo`, +`examples/supported/array-struct-elements.slo`, and +`examples/formatter/array-struct-elements.slo`. + +exp-124 does not add zero-length arrays, mutable arrays, element mutation, +array equality, array printing, nested arrays, slices, generics, stable +ABI/layout/ownership claims, or beta maturity. + +## exp-123 (released experimental alpha) + +Release label: `exp-123` + +Release name: Owned Vector Benchmark Suite Extension And Whitepaper Refresh Alpha + +Release date: 2026-05-21 + +Status: released experimental alpha documentation/tooling contract, not beta. + +The normative Slovo-side contract target is +`.llm/EXP_123_OWNED_VECTOR_BENCHMARK_SUITE_EXTENSION_AND_WHITEPAPER_REFRESH_ALPHA.md`. + +exp-123 keeps the exp-121 language surface unchanged and refreshes the current +publication baseline around one connected benchmark/documentation scope: + +- `docs/SLOVO_WHITEPAPER.md` refreshed from the earlier exp-122 publication + framing to the current language/tooling state through exp-121 +- benchmark methodology widened from seven kernels to nine: + `math-loop`, `branch-loop`, `parse-loop`, `array-index-loop`, + `string-eq-loop`, `array-struct-field-loop`, `enum-struct-payload-loop`, + `vec-i32-index-loop`, and `vec-string-eq-loop` +- paired same-machine benchmark refresh targets widened from seven rows to + nine rows for the controller-owned publication pass +- local-machine-only wording, no threshold claim, and no cross-machine claim + retained +- `docs/README.md`, README release metadata, and spec/roadmap wording aligned + to the refreshed publication baseline + +The matching publication artifacts remain `docs/SLOVO_WHITEPAPER.pdf` and +`WHITEPAPER.pdf`, regenerated after the paired controller-side benchmark data +refresh and release publication pass. + +exp-123 does not add new source syntax, type forms, standard-library APIs, +compiler-known runtime names, optimizer guarantees, benchmark thresholds, +cross-machine performance claims, stable ABI/layout/ownership claims, or beta +maturity. + +## exp-122 (released experimental alpha) + +Release label: `exp-122` + +Release name: Composite Data Benchmark Suite Extension And Whitepaper Refresh Alpha + +Release date: 2026-05-21 + +Status: released experimental alpha documentation/tooling contract, not beta. + +The normative Slovo-side contract target is +`.llm/EXP_122_COMPOSITE_DATA_BENCHMARK_SUITE_EXTENSION_AND_WHITEPAPER_REFRESH_ALPHA.md`. + +exp-122 keeps the exp-121 language surface unchanged and refreshes the current +publication baseline around one connected benchmark/documentation scope: + +- `docs/SLOVO_WHITEPAPER.md` refreshed from the earlier exp-119 publication + framing to the current language/tooling state through exp-121 +- benchmark methodology widened from five kernels to seven: + `math-loop`, `branch-loop`, `parse-loop`, `array-index-loop`, + `string-eq-loop`, `array-struct-field-loop`, and + `enum-struct-payload-loop` +- paired same-machine benchmark refresh targets widened from five rows to + seven rows for the controller-owned publication pass +- local-machine-only wording, no threshold claim, and no cross-machine claim + retained +- `docs/README.md`, README release metadata, and spec/roadmap wording aligned + to the refreshed publication baseline + +The matching publication artifacts remain `docs/SLOVO_WHITEPAPER.pdf` and +`WHITEPAPER.pdf`, regenerated after the paired controller-side benchmark data +refresh and release publication pass. + +exp-122 does not add new source syntax, type forms, standard-library APIs, +compiler-known runtime names, optimizer guarantees, benchmark thresholds, +cross-machine performance claims, stable ABI/layout/ownership claims, or beta +maturity. + +## exp-121 (released experimental alpha) + +Release label: `exp-121` + +Release name: Non-Recursive Struct Enum Payloads Alpha + +Release date: 2026-05-21 + +Status: released experimental alpha language contract, not beta. + +The normative Slovo-side contract target is +`.llm/EXP_121_NON_RECURSIVE_STRUCT_ENUM_PAYLOADS_ALPHA.md`. + +exp-121 broadens the enum payload surface with one connected struct-payload +scope: + +- unary enum payload variants may use current known non-recursive struct types +- payload variants in one enum still share the same payload type when payload + variants are present +- qualified payload constructors over those struct types +- immutable enum locals, parameters, returns, calls, tests, and `main` +- exhaustive `match` with one immutable payload binding +- existing field access behavior on bound struct payloads, including array + field access and checked indexing where already promoted by exp-120 + +The matching supported and formatter fixtures are +`examples/supported/enum-payload-structs.slo` and +`examples/formatter/enum-payload-structs.slo`. + +exp-121 does not add enum equality requirements for struct-payload enums, +direct array/vec/option/result payloads, multiple payload fields, +recursive/cyclic payloads, mutation, generics, stable ABI/layout/ownership +claims, or beta maturity. + +## exp-120 (released experimental alpha) + +Release label: `exp-120` + +Release name: Fixed Array Struct Fields Alpha + +Release date: 2026-05-21 + +Status: released experimental alpha language contract, not beta. + +The normative Slovo-side contract target is +`.llm/EXP_120_FIXED_ARRAY_STRUCT_FIELDS_ALPHA.md`. + +exp-120 broadens the struct-field surface with one connected fixed-array +scope: + +- direct struct field declarations over `(array i32 N)`, `(array i64 N)`, + `(array f64 N)`, `(array bool N)`, and `(array string N)` +- struct construction from matching fixed-array expressions +- immutable struct locals initialized from matching struct-valued expressions +- struct parameters and returns for structs carrying those field types +- calls returning structs carrying those field types +- field access returning the declared fixed-array type +- checked indexing through field access with `i32` expressions only + +The matching supported and formatter fixtures are +`examples/supported/array-struct-fields.slo` and +`examples/formatter/array-struct-fields.slo`. + +The earlier `array*` fixtures remain valid as the underlying fixed-array +lanes. The earlier `enum-struct-fields.slo`, `primitive-struct-fields.slo`, +`numeric-struct-fields.slo`, and `composite-struct-fields.slo` fixtures remain +valid as narrower or sibling struct-field lanes. + +exp-120 does not add zero-length arrays, mutable arrays, element mutation, +field mutation, array equality, array printing, nested arrays, arrays of +unsupported element kinds, arrays inside unsupported containers, slices, +generics, stable ABI/layout/ownership claims, or beta maturity. + +## exp-119 (released experimental alpha) + +Release label: `exp-119` + +Release name: Benchmark Suite Extension And Whitepaper Refresh Alpha + +Release date: 2026-05-21 + +Status: released experimental alpha documentation/tooling contract, not beta. + +The normative Slovo-side contract target is +`.llm/EXP_119_BENCHMARK_SUITE_EXTENSION_AND_WHITEPAPER_REFRESH_ALPHA.md`. + +exp-119 keeps the exp-118 language surface unchanged and refreshes the current +publication baseline around one connected benchmark/documentation scope: + +- `docs/SLOVO_WHITEPAPER.md` refreshed from the older exp-59 framing to the + current language/tooling state through exp-118 +- benchmark methodology widened from three kernels to five: + `math-loop`, `branch-loop`, `parse-loop`, `array-index-loop`, and + `string-eq-loop` +- benchmark result tables widened from three rows to five rows for paired + same-machine publication refresh +- local-machine-only wording, no threshold claim, and no cross-machine claim + retained +- `docs/README.md`, release metadata, and spec/roadmap wording aligned to the + refreshed publication baseline + +The matching publication artifacts remain `docs/SLOVO_WHITEPAPER.pdf` and +`WHITEPAPER.pdf`, regenerated after the paired controller-side benchmark data +refresh and release publication pass. + +exp-119 does not add new source syntax, type forms, standard-library APIs, +compiler-known runtime names, optimizer guarantees, benchmark thresholds, +cross-machine performance claims, stable ABI/layout/ownership claims, or beta +maturity. + +## exp-118 (released experimental alpha) + +Release label: `exp-118` + +Release name: String Fixed Arrays Alpha + +Release date: 2026-05-21 + +Status: released experimental alpha language contract, not beta. + +The normative Slovo-side contract target is +`.llm/EXP_118_STRING_FIXED_ARRAYS_ALPHA.md`. + +exp-118 broadens the fixed-array surface with one connected string-array +scope: + +- fixed array types over `string` +- positive literal lengths only +- direct array constructors over `string` +- immutable array locals initialized from matching array-valued expressions +- fixed-array parameters and returns over `string` +- calls returning arrays over `string` +- checked literal indexing and runtime-checked dynamic indexing +- `i32` index expressions only + +The matching supported and formatter fixtures are +`examples/supported/array-string.slo`, +`examples/formatter/array-string.slo`, +`examples/supported/array-string-value-flow.slo`, and +`examples/formatter/array-string-value-flow.slo`. + +The earlier `examples/supported/array.slo` and +`examples/supported/array-value-flow.slo` fixtures remain valid as narrower +`i32`-only predecessors. The earlier `array-direct-scalars*` fixtures remain +valid as the direct scalar widening lane from exp-117. + +exp-118 does not add zero-length arrays, mutable arrays, element mutation, +array equality, printing arrays, nested arrays, arrays in struct fields, +arrays of other unsupported element types, slices, generics, stable +ABI/layout/ownership claims for string elements, or beta maturity. + +## exp-117 (released experimental alpha) + +Release label: `exp-117` + +Release name: Direct Scalar Fixed Arrays Alpha + +Release date: 2026-05-21 + +Status: released experimental alpha language contract, not beta. + +The normative Slovo-side contract target is +`.llm/EXP_117_DIRECT_SCALAR_FIXED_ARRAYS_ALPHA.md`. + +exp-117 broadens the fixed-array surface with one connected direct-scalar +scope: + +- fixed array types over `i32`, `i64`, `f64`, and `bool` +- positive literal lengths only +- direct array constructors over those element families +- immutable array locals initialized from matching array-valued expressions +- fixed-array parameters and returns over those element families +- calls returning arrays over those element families +- checked literal indexing and runtime-checked dynamic indexing +- `i32` index expressions only + +The matching supported and formatter fixtures are +`examples/supported/array-direct-scalars.slo`, +`examples/formatter/array-direct-scalars.slo`, +`examples/supported/array-direct-scalars-value-flow.slo`, and +`examples/formatter/array-direct-scalars-value-flow.slo`. + +The earlier `examples/supported/array.slo` and +`examples/supported/array-value-flow.slo` fixtures remain valid as narrower +`i32`-only predecessors. + +exp-117 does not add zero-length arrays, mutable arrays, element mutation, +array equality, printing arrays, nested arrays, arrays in struct fields, string +arrays, arrays of other unsupported element types, slices, generics, stable +ABI/layout claims, or beta maturity. + +## exp-116 (released experimental alpha) + +Release label: `exp-116` + +Release name: Direct Scalar And String Enum Payloads Alpha + +Release date: 2026-05-21 + +Status: released experimental alpha language contract, not beta. + +The normative Slovo-side contract target is +`.llm/EXP_116_DIRECT_SCALAR_AND_STRING_ENUM_PAYLOADS_ALPHA.md`. + +exp-116 broadens the user-defined enum payload surface with one connected +direct-payload scope: + +- payloadless variants remain supported +- unary `(Variant i32)` payload variants remain supported +- unary direct `(Variant i64)` payload variants +- unary direct `(Variant f64)` payload variants +- unary direct `(Variant bool)` payload variants +- unary direct `(Variant string)` payload variants +- qualified zero-argument payloadless constructors and qualified one-argument + payload constructors +- immutable enum locals, parameters, returns, calls, top-level tests, and + `main` +- exhaustive enum `match` with one immutable arm-local payload binding for + payload variants +- same-enum equality by tag for payloadless variants and by tag plus payload + for unary direct payload variants + +The matching supported and formatter fixtures are +`examples/supported/enum-payload-direct-scalars.slo` and +`examples/formatter/enum-payload-direct-scalars.slo`. + +exp-116 does not add mixed payload kinds inside one enum, multiple payloads, +record variants, tuple variants beyond one direct payload, vec/array/option/ +result/struct/enum payloads, enum mutation, richer pattern families, generic +enums, methods, traits, new compiler-known runtime names, manifest schema +changes, or beta maturity. + +## exp-115 (released experimental alpha) + +Release label: `exp-115` + +Release name: Composite Struct Fields And Nested Structs Alpha + +Release date: 2026-05-21 + +Status: released experimental alpha language contract, not beta. + +The normative Slovo-side contract target is +`.llm/EXP_115_COMPOSITE_STRUCT_FIELDS_AND_NESTED_STRUCTS_ALPHA.md`. + +exp-115 broadens the struct-field surface with one connected composite-field +scope: + +- direct struct fields over `(vec i32)`, `(vec i64)`, `(vec f64)`, + `(vec bool)`, and `(vec string)` +- direct struct fields over `(option i32)`, `(option i64)`, `(option f64)`, + `(option bool)`, and `(option string)` +- direct struct fields over `(result i32 i32)`, `(result i64 i32)`, + `(result f64 i32)`, `(result bool i32)`, and `(result string i32)` +- direct struct fields over current known non-recursive struct types +- coexistence with the already supported direct scalar, immutable string, and + current enum field families +- immutable struct locals, parameters, returns, calls, top-level tests, and + `main` for structs carrying those fields + +The matching supported and formatter fixtures are +`examples/supported/composite-struct-fields.slo` and +`examples/formatter/composite-struct-fields.slo`. + +exp-115 does not add arrays as struct fields, mutable arrays, field mutation, +vector element mutation, option/result payload mutation, enum payload +mutation, recursive or cyclic struct layouts, new compiler-known runtime +names, manifest schema changes, or beta maturity. + +## exp-114 (released experimental alpha) + +Release label: `exp-114` + +Release name: Mutable Composite Locals Alpha + +Release date: 2026-05-21 + +Status: released experimental alpha language contract, not beta. + +The normative Slovo-side contract target is +`.llm/EXP_114_MUTABLE_COMPOSITE_LOCALS_ALPHA.md`. + +exp-114 broadens the local-value surface with one connected mutable-composite +scope: + +- same-type mutable whole-value `var` / `set` reassignment for `string` +- same-type mutable whole-value `var` / `set` reassignment for `(vec i32)`, + `(vec i64)`, `(vec f64)`, `(vec bool)`, and `(vec string)` +- same-type mutable whole-value `var` / `set` reassignment for current + concrete option/result families +- same-type mutable whole-value `var` / `set` reassignment for current known + struct values and current enum values +- ordinary function bodies, top-level tests, and `main` + +The matching supported and formatter fixtures are +`examples/supported/composite-locals.slo` and +`examples/formatter/composite-locals.slo`. + +exp-114 does not add array mutation or mutable arrays, field mutation, vector +element mutation, option/result payload mutation, enum payload mutation, +mixed-type assignment, new compiler-known runtime names, manifest schema +changes, or beta maturity. + +## exp-113 (released experimental alpha) + +Release label: `exp-113` + +Release name: Mutable Scalar Locals Alpha + +Release date: 2026-05-21 + +Status: released experimental alpha language contract, not beta. + +The normative Slovo-side contract target is +`.llm/EXP_113_MUTABLE_SCALAR_LOCALS_ALPHA.md`. + +exp-113 broadens the local-value surface with one connected mutable-scalar +scope: + +- direct mutable `var` locals declared as `bool`, `i64`, and `f64` +- same-type `set` reassignment from already promoted scalar expressions for + those declared local types +- ordinary function bodies, top-level tests, and `main` + +The matching supported and formatter fixtures remain +`examples/supported/local-variables.slo` and +`examples/formatter/local-variables.slo`, now broadened to include mutable +`bool`, `i64`, and `f64` local var/set flows alongside the earlier i32 local +forms and exp-112 immutable bool locals. + +exp-113 does not add mutable `string` locals, vector mutation, option/result +mutation, struct or enum mutation, mixed-type assignment, new compiler-known +runtime names, manifest schema changes, or beta maturity. + +## exp-112 (released experimental alpha) + +Release label: `exp-112` + +Release name: Immutable Bool Locals Alpha + +Release date: 2026-05-21 + +Status: released experimental alpha language contract, not beta. + +The normative Slovo-side contract target is +`.llm/EXP_112_IMMUTABLE_BOOL_LOCALS_ALPHA.md`. + +exp-112 broadens the local-value surface with one connected immutable-bool +scope: + +- direct immutable `let` locals declared as `bool` +- local initializers from already promoted bool literals, parameters, calls + returning bool, and `if` expressions returning bool +- local references in existing predicate, return, call, top-level test, and + `main` positions + +The matching supported and formatter fixtures remain +`examples/supported/local-variables.slo` and +`examples/formatter/local-variables.slo`, now broadened to include direct +immutable bool locals alongside the earlier i32 local forms. The current +`examples/projects/std-import-io/` explicit import fixture also now uses +direct bool locals in its stdin bool-fallback helper path. + +exp-112 does not add mutable bool locals, bool assignment, `var bool`, new +boolean operators, broader local-type widening, new compiler-known runtime +names, manifest schema changes, or beta maturity. + +## exp-111 (released experimental alpha) + +Release label: `exp-111` + +Release name: Standard IO Stdin Source Helpers Alpha + +Release date: 2026-05-21 + +Status: released experimental alpha stdlib/source contract, not beta. + +The normative Slovo-side contract target is +`.llm/EXP_111_STANDARD_IO_STDIN_SOURCE_HELPERS_ALPHA.md`. + +exp-111 broadens `std/io.slo` with one connected stdin helper package: + +- `read_stdin_result` +- `read_stdin_option` +- `read_stdin_or` +- `read_stdin_i32_result` +- `read_stdin_i32_option` +- `read_stdin_i32_or_zero` +- `read_stdin_i32_or` +- `read_stdin_i64_result` +- `read_stdin_i64_option` +- `read_stdin_i64_or_zero` +- `read_stdin_i64_or` +- `read_stdin_f64_result` +- `read_stdin_f64_option` +- `read_stdin_f64_or_zero` +- `read_stdin_f64_or` +- `read_stdin_bool_result` +- `read_stdin_bool_option` +- `read_stdin_bool_or_false` +- `read_stdin_bool_or` + +The existing zero-returning and value-returning print helper surfaces remain +in place. All new helpers remain ordinary Slovo source over the already +promoted `std.io.read_stdin_result`, the released `std.string.parse_*_result` +helpers, and the exp-109 `std.result.ok_or_none_*` bridge helpers. The +scope adds no new compiler-known `std.*` names, no generics, and no new +runtime calls. The matching explicit import fixture +`examples/projects/std-import-io/` now exercises the expanded stdin helper +lane structurally instead of assuming a fixed runner stdin payload. + +exp-111 does not add trap-based `std.io.read_stdin`, line/read-line APIs, +prompt APIs, terminal mode, binary stdin, streaming stdin, async IO, +generics, compiler-known `std.*` runtime names beyond the already released +lane, or beta maturity. + +## exp-110 (released experimental alpha) + +Release label: `exp-110` + +Release name: Standard String, Env, Fs, Process, And Cli Option Helpers Alpha + +Release date: 2026-05-21 + +Status: released experimental alpha stdlib/source contract, not beta. + +The normative Slovo-side contract target is +`.llm/EXP_110_STANDARD_STRING_ENV_FS_PROCESS_AND_CLI_OPTION_HELPERS_ALPHA.md`. + +exp-110 broadens `std/string.slo`, `std/env.slo`, `std/fs.slo`, +`std/process.slo`, and `std/cli.slo` with one connected option-helper +package: + +- `std.string`: `parse_i32_option`, `parse_i64_option`, + `parse_f64_option`, `parse_bool_option` +- `std.env`: `get_option`, `get_i32_option`, `get_i64_option`, + `get_f64_option`, `get_bool_option` +- `std.fs`: `read_text_option`, `read_i32_option`, `read_i64_option`, + `read_f64_option`, `read_bool_option` +- `std.process`: `arg_option`, `arg_i32_option`, `arg_i64_option`, + `arg_f64_option`, `arg_bool_option` +- `std.cli`: `arg_text_option`, `arg_i32_option`, `arg_i64_option`, + `arg_f64_option`, `arg_bool_option` + +The existing direct/result/fallback helper surfaces remain in place for all +five staged modules. All new helpers remain ordinary Slovo source over the +existing concrete result lanes through the exp-109 `std.result.ok_or_none_*` +bridge helpers. The scope adds no new compiler-known `std.*` names, no +generics, no new runtime calls, no stdin helpers, and no vec work. + +The matching explicit import fixtures, +`examples/projects/std-import-string/`, +`examples/projects/std-import-env/`, +`examples/projects/std-import-fs/`, +`examples/projects/std-import-process/`, and +`examples/projects/std-import-cli/`, now deterministically exercise the new +option helpers alongside the earlier result and fallback lanes. + +exp-110 does not add generic option/result combinators, automatic or +compiler-loaded std imports, stable ABI/layout claims, or beta maturity. + +## exp-109 (released experimental alpha) + +Release label: `exp-109` + +Release name: Standard Option And Result Bridge Helpers Alpha + +Release date: 2026-05-21 + +Status: released experimental alpha stdlib/source contract, not beta. + +The normative Slovo-side contract target is +`.llm/EXP_109_STANDARD_OPTION_AND_RESULT_BRIDGE_HELPERS_ALPHA.md`. + +exp-109 broadens `std/option.slo` and `std/result.slo` with one connected +bridge-helper package between the current concrete option and result +families: + +- `std/option.slo`: `some_or_err_i32`, `some_or_err_i64`, + `some_or_err_f64`, `some_or_err_bool`, `some_or_err_string` +- `std/result.slo`: `ok_or_none_i32`, `ok_or_none_i64`, + `ok_or_none_string`, `ok_or_none_f64`, `ok_or_none_bool` + +The existing concrete constructor, observer, unwrap, and fallback helpers +remain in place for both staged modules. All new helpers remain ordinary +Slovo source. `some_or_err_*` uses the existing raw `is_some`, +`unwrap_some`, `ok`, and `err` forms to produce `(result i32)` +without importing `std.result`. `ok_or_none_*` uses the existing +`std.result.is_ok`, `std.result.unwrap_ok`, `some`, and `none` forms to +produce `(option )` without importing `std.option`. + +The matching explicit import fixtures, +`examples/projects/std-import-option/`, +`examples/workspaces/std-import-option/`, and +`examples/projects/std-import-result/`, now deterministically exercise the +new bridge helpers alongside the earlier concrete helper surfaces. The +project option fixture now passes 26 deterministic tests, the workspace +option fixture passes 1 deterministic test, and the project result fixture +passes 18 deterministic tests. + +exp-109 does not add compiler-known `std.*` names, generic bridge helpers, +generic `map`/`and_then`/`transpose`/`flatten`, new payload families, +automatic or compiler-loaded std imports, stable ABI/layout claims, or beta +maturity. + +## exp-108 (released experimental alpha) + +Release label: `exp-108` + +Release name: Standard Vec String, F64, And Bool Prefix And Suffix Helpers Alpha + +Release date: 2026-05-21 + +Status: released experimental alpha stdlib/source contract, not beta. + +The normative Slovo-side contract target is +`.llm/EXP_108_STANDARD_VEC_STRING_F64_AND_BOOL_PREFIX_AND_SUFFIX_HELPERS_ALPHA.md`. + +exp-108 broadens `std/vec_string.slo`, `std/vec_f64.slo`, and +`std/vec_bool.slo` from their released edit baselines with one connected +immutable prefix/suffix package: + +- `starts_with` +- `without_prefix` +- `ends_with` +- `without_suffix` + +The baseline direct wrappers, builder helpers, query helpers, option-query +helpers, transform helpers, subvec helper, edit helpers, and simple +real-program helpers remain in place for the three concrete vec families. +All new helpers remain ordinary Slovo source over the four already released +runtime calls for each family plus existing helper composition. Empty +prefixes and suffixes behave as identity boundaries, exact whole-vector +prefix/suffix matches collapse to `empty`, and mismatched prefixes or +suffixes leave the input vector unchanged for the corresponding +`without_prefix` or `without_suffix` call. + +The matching explicit import fixtures, +`examples/projects/std-import-vec_string/`, +`examples/projects/std-import-vec_f64/`, and +`examples/projects/std-import-vec_bool/`, now deterministically exercise the +baseline, option-query, prefix/suffix, transform, subvec, edit, and +real-program helper groups through explicit imports. Each project now passes +19 deterministic fixture tests. + +exp-108 does not add compiler-known `std.*` names beyond the already +released vec-string, vec-f64, and vec-bool runtime families, generics, +sorting, mapping, filtering, nested/container vecs, mutating/capacity APIs, +automatic or compiler-loaded std imports, stable ABI/layout claims, or beta +maturity. + +## exp-107 (released experimental alpha) + +Release label: `exp-107` + +Release name: Standard Vec String, F64, And Bool Edit Helpers Alpha + +Release date: 2026-05-21 + +Status: released experimental alpha stdlib/source contract, not beta. + +The normative Slovo-side contract target is +`.llm/EXP_107_STANDARD_VEC_STRING_F64_AND_BOOL_EDIT_HELPERS_ALPHA.md`. + +exp-107 broadens `std/vec_string.slo`, `std/vec_f64.slo`, and +`std/vec_bool.slo` from their released option-query and transform baselines +with one connected immutable edit package: + +- `insert_at` +- `insert_range` +- `replace_at` +- `replace_range` +- `remove_at` +- `remove_range` + +The baseline direct wrappers, builder helpers, query helpers, option-query +helpers, transform helpers, subvec helper, and simple real-program helpers +remain in place for the three concrete vec families. All new helpers remain +ordinary Slovo source over the four already released runtime calls for each +family plus existing helper composition. `insert_at` appends on exact-length +positions and otherwise leaves invalid negative or past-end positions +unchanged; `insert_range` behaves the same for whole-vector insertion; +`replace_at` changes one in-range position or returns the original vector; +`replace_range` replaces a half-open in-range segment with tail saturation; +`remove_at` removes one in-range position or returns the original vector; +and `remove_range` removes a half-open in-range segment with tail saturation. + +The matching explicit import fixtures, +`examples/projects/std-import-vec_string/`, +`examples/projects/std-import-vec_f64/`, and +`examples/projects/std-import-vec_bool/`, now deterministically exercise the +baseline, option-query, transform, subvec, edit, and real-program helper +groups through explicit imports. Each project now passes 15 deterministic +fixture tests. + +exp-107 does not add compiler-known `std.*` names beyond the already +released vec-string, vec-f64, and vec-bool runtime families, generics, +prefix/suffix helpers, sorting, mapping, filtering, nested/container vecs, +mutating/capacity APIs, automatic or compiler-loaded std imports, stable +ABI/layout claims, or beta maturity. + +## exp-106 (released experimental alpha) + +Release label: `exp-106` + +Release name: Standard Vec F64 And Bool Option Query Helpers Alpha + +Release date: 2026-05-21 + +Status: released experimental alpha stdlib/source contract, not beta. + +The normative Slovo-side contract target is +`.llm/EXP_106_STANDARD_VEC_F64_AND_BOOL_OPTION_QUERY_HELPERS_ALPHA.md`. + +exp-106 broadens `std/vec_f64.slo` and `std/vec_bool.slo` from their +released transform baselines with one connected option-query package: + +- `index_option` +- `first_option` +- `last_option` +- `index_of_option` +- `last_index_of_option` + +The baseline direct wrappers, builder helpers, query helpers, transform +helpers, subvec helper, and simple real-program helpers remain in place for +both concrete vec families. All new helpers remain ordinary Slovo source +over the four already released runtime calls for each family and the already +promoted concrete option families they return. `index_option` returns `none` +for negative and out-of-range positions; `first_option` delegates to +`index_option values 0`; `last_option` returns `none` for empty vectors and +`some` of the last element otherwise; `index_of_option` returns the first +matching position or `none`; and `last_index_of_option` returns the last +matching position or `none`. + +The matching explicit import fixtures, +`examples/projects/std-import-vec_f64/` and +`examples/projects/std-import-vec_bool/`, now deterministically exercise the +baseline, option-query, transform, subvec, and real-program helper groups +through explicit `(import std.vec_f64 (...))` and `(import std.vec_bool +(...))`. Each project now passes 9 deterministic fixture tests. + +exp-106 does not add compiler-known `std.*` names beyond the already +released vec-f64 and vec-bool runtime families, generics, edit helpers, +nested/container vecs, mutating/capacity APIs, automatic or compiler-loaded +std imports, stable ABI/layout claims, or beta maturity. + +## exp-105 (released experimental alpha) + +Release label: `exp-105` + +Release name: Standard Vec F64 And Bool Transform Helpers Alpha + +Release date: 2026-05-21 + +Status: released experimental alpha stdlib/source contract, not beta. + +The normative Slovo-side contract target is +`.llm/EXP_105_STANDARD_VEC_F64_AND_BOOL_TRANSFORM_HELPERS_ALPHA.md`. + +exp-105 broadens `std/vec_f64.slo` and `std/vec_bool.slo` from their +released baselines with one connected transform-helper package: + +- `concat` +- `take` +- `drop` +- `reverse` +- `subvec` + +The baseline direct wrappers, builder helpers, query helpers, and simple +real-program helpers remain in place for both concrete vec families. All new +helpers remain ordinary Slovo source over the four already released runtime +calls for each family. `take` treats negative counts as zero and saturates +to the full source vector, `drop` treats negative counts as zero and +saturates at the source tail, `reverse` returns a copied reverse-order +vector, and `subvec` returns a copied half-open range or `empty` for +negative or empty starts. + +The matching explicit import fixtures, +`examples/projects/std-import-vec_f64/` and +`examples/projects/std-import-vec_bool/`, now deterministically exercise the +baseline, transform, subvec, and real-program helper groups through explicit +`(import std.vec_f64 (...))` and `(import std.vec_bool (...))`. Each project +now passes 8 deterministic fixture tests. + +exp-105 does not add compiler-known `std.*` names beyond the already +released vec-f64 and vec-bool runtime families, generics, option/result +helpers, edit helpers, nested/container vecs, mutating/capacity APIs, +automatic or compiler-loaded std imports, stable ABI/layout claims, or beta +maturity. + +## exp-104 (released experimental alpha) + +Release label: `exp-104` + +Release name: Standard Vec Bool Baseline Alpha + +Release date: 2026-05-21 + +Status: released experimental alpha stdlib/source contract, not beta. + +The normative Slovo-side contract target is +`.llm/EXP_104_STANDARD_VEC_BOOL_BASELINE_ALPHA.md`. + +exp-104 stages `std/vec_bool.slo` as the first source-authored `(vec bool)` +facade over the sibling concrete `std.vec.bool` runtime family. + +The frozen source helper surface is: + +- direct wrappers: `empty`, `append`, `len`, `at` +- builder helpers: `singleton`, `append2`, `append3`, `pair`, `triple` +- query helpers: `is_empty`, `index_or`, `first_or`, `last_or` +- simple real-program helpers: `contains`, `count_of` + +All helpers remain ordinary Slovo source over the four released vec-bool +runtime calls. `index_or` returns its fallback for negative and out-of-range +positions, `first_or` delegates to `index_or values 0 fallback`, `last_or` +returns its fallback for empty vectors and the final element otherwise, +`contains` returns `true` exactly when some element equals the target, and +`count_of` returns `0` for empty vectors and counts repeated matches exactly. + +The matching explicit import fixture, +`examples/projects/std-import-vec_bool/`, freezes deterministic direct, +builder, query, and real-program helper coverage through explicit +`(import std.vec_bool (...))`. The matching supported and formatter fixtures, +`examples/supported/vec-bool.slo` and `examples/formatter/vec-bool.slo`, +exercise the intended raw `std.vec.bool.*` runtime surface, append +immutability, concrete `(vec bool)` flow, and same-family vector equality on +the Slovo side. + +exp-104 does not add compiler-known `std.*` names beyond the released vec-bool +runtime family, generics, option/result helpers, transform/range/edit +helpers, nested/container vecs, mutating/capacity APIs, automatic or +compiler-loaded std imports, stable ABI/layout claims, or beta maturity. + +## exp-103 (released experimental alpha) + +Release label: `exp-103` + +Release name: Standard Vec F64 Baseline Alpha + +Release date: 2026-05-21 + +Status: released experimental alpha stdlib/source contract plus concrete +vector-family contract. This is experimental maturity and not a beta +maturity claim. + +The normative release contract is +`.llm/EXP_103_STANDARD_VEC_F64_BASELINE_ALPHA.md`. + +exp-103 adds `std/vec_f64.slo` as the first usable source-authored +`(vec f64)` facade over the sibling concrete `std.vec.f64` runtime family. + +The staged source helper surface is: + +- direct wrappers: `empty`, `append`, `len`, `at` +- builder helpers: `singleton`, `append2`, `append3`, `pair`, `triple` +- query helpers: `is_empty`, `index_or`, `first_or`, `last_or` +- simple real-program helpers: `contains`, `sum` + +All helpers remain ordinary Slovo source over the four promoted vec-f64 +runtime calls. `index_or` returns its fallback for negative and out-of-range +positions, `first_or` delegates to `index_or values 0 fallback`, `last_or` +returns its fallback for empty vectors and the final element otherwise, +`contains` returns `true` exactly when some element equals the target, and +`sum` returns `0.0` for empty vectors. + +The matching explicit import fixture, +`examples/projects/std-import-vec_f64/`, deterministically exercises the +direct wrappers, builder helpers, query helpers, and real-program helpers +through explicit `(import std.vec_f64 (...))`. The matching supported and +formatter fixtures, `examples/supported/vec-f64.slo` and +`examples/formatter/vec-f64.slo`, exercise the compiler-known +`std.vec.f64.*` runtime surface, append immutability, concrete `(vec f64)` +flow, and same-family vector equality. + +exp-103 does not add compiler-known `std.*` names beyond the vec-f64 runtime +family, generics, option-query helpers, transform/range/edit helpers, +nested/container vecs, automatic or compiler-loaded std imports, stable +ABI/layout claims, or beta maturity. + +## exp-102 (released experimental alpha) + +Release label: `exp-102` + +Release name: Standard Option Bool And F64 Baseline Alpha + +Release date: 2026-05-21 + +Status: released experimental alpha source/core-language contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_102_STANDARD_OPTION_BOOL_AND_F64_BASELINE_ALPHA.md`. + +exp-102 broadens the staged `std/option.slo` helper surface and the current +core-language option slice from concrete `(option i32)`, `(option i64)`, and +`(option string)` to those families plus concrete `(option f64)` and +`(option bool)`. + +The staged source helper surface is now: + +- `(option i32)`: `some_i32`, `none_i32`, `is_some_i32`, `is_none_i32`, + `unwrap_some_i32`, `unwrap_or_i32` +- `(option i64)`: `some_i64`, `none_i64`, `is_some_i64`, `is_none_i64`, + `unwrap_some_i64`, `unwrap_or_i64` +- `(option f64)`: `some_f64`, `none_f64`, `is_some_f64`, `is_none_f64`, + `unwrap_some_f64`, `unwrap_or_f64` +- `(option bool)`: `some_bool`, `none_bool`, `is_some_bool`, `is_none_bool`, + `unwrap_some_bool`, `unwrap_or_bool` +- `(option string)`: `some_string`, `none_string`, `is_some_string`, + `is_none_string`, `unwrap_some_string`, `unwrap_or_string` + +All helpers remain ordinary Slovo source over the already promoted option +forms, observers, extraction, `if`, parameters, and explicit signatures. +The release also broadens the supported and formatter `option-result*` +fixtures so raw option returns, immutable value flow, payload extraction, and +source-level `match` now cover `(option f64)` and `(option bool)` as well. + +The matching explicit import fixture, +`examples/projects/std-import-option/`, now deterministically exercises the +`i32`, `i64`, `f64`, `bool`, and `string` helper families, with 16 passed +tests in the explicit project fixture. The matching workspace fixture, +`examples/workspaces/std-import-option/`, broadens the same staged helper +surface through the existing explicit workspace source-search lane. + +exp-102 does not add compiler-known `std.option.*` runtime names, generic +option helpers, option payload families beyond `i32`, `i64`, `f64`, `bool`, +and `string`, option equality, option printing, nested/container option +support, automatic or compiler-loaded std imports, stable ABI/layout claims, +or beta maturity. + +## exp-101 (released experimental alpha) + +Release label: `exp-101` + +Release name: Standard Vec String Option And Transform Helpers Alpha + +Release date: 2026-05-21 + +Status: released experimental alpha stdlib/source contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_101_STANDARD_VEC_STRING_OPTION_AND_TRANSFORM_HELPERS_ALPHA.md`. + +exp-101 broadens `std/vec_string.slo` from the exp-99 baseline with one +connected helper package: + +- option-query helpers: + `index_option`, `first_option`, `last_option`, `index_of_option`, + `last_index_of_option` +- transform helpers: + `concat`, `take`, `drop`, `reverse`, `subvec` + +The baseline direct wrappers, builder helpers, query helpers, and simple +real-program helpers remain in place. The new helpers stay ordinary Slovo +source over the existing `std.vec.string.*` facade operations, string +equality, integer comparisons, and raw current `(option string)` / +`(option i32)` forms. The lane stays recursive and immutable with no `var` +or `set`. + +The matching explicit import fixture, `examples/projects/std-import-vec_string/`, +now deterministically exercises the baseline, option-query, transform, +subvec, and real-program helper groups, with 9 passed tests in the explicit +project fixture. + +exp-101 does not add compiler-known `std.*` runtime names, generics, vec +element families beyond `i32`, `i64`, and `string`, edit helpers, +prefix/suffix helpers, nested vecs or vecs inside other containers, +mutating/capacity APIs, stable ABI/layout claims, or beta maturity. + +## exp-100 (released experimental alpha) + +Release label: `exp-100` + +Release name: Standard Option String Baseline Alpha + +Release date: 2026-05-21 + +Status: released experimental alpha source/core-language contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_100_STANDARD_OPTION_STRING_BASELINE_ALPHA.md`. + +exp-100 broadens the staged `std/option.slo` helper surface and the current +core-language option slice from concrete `(option i32)` plus `(option i64)` +to concrete `(option i32)`, `(option i64)`, and `(option string)`. + +The staged source helper surface is now: + +- `(option i32)`: `some_i32`, `none_i32`, `is_some_i32`, `is_none_i32`, + `unwrap_some_i32`, `unwrap_or_i32` +- `(option i64)`: `some_i64`, `none_i64`, `is_some_i64`, `is_none_i64`, + `unwrap_some_i64`, `unwrap_or_i64` +- `(option string)`: `some_string`, `none_string`, `is_some_string`, + `is_none_string`, `unwrap_some_string`, `unwrap_or_string` + +All helpers remain ordinary Slovo source over the already promoted option +forms, tag observers, extraction, `if`, parameters, and explicit +signatures. The release also broadens the supported and formatter +`option-result*` fixtures so raw option returns, immutable value flow, +payload extraction, and source-level `match` now cover `(option string)` as +well. + +The matching explicit import fixture, +`examples/projects/std-import-option/`, now deterministically exercises the +`i32`, `i64`, and `string` helper families, with 10 passed tests in the +explicit project fixture. The matching workspace fixture, +`examples/workspaces/std-import-option/`, broadens the same staged helper +surface through the existing explicit workspace source-search lane. + +exp-100 does not add compiler-known `std.option.*` runtime names, generic +option helpers, option payload families beyond `i32`, `i64`, and `string`, +option equality, option printing, nested/container option support, automatic +or compiler-loaded std imports, stable ABI/layout claims, or beta maturity. + +## exp-99 (released experimental alpha) + +Release label: `exp-99` + +Release name: Standard Vec String Baseline Alpha + +Release date: 2026-05-21 + +Status: released experimental alpha stdlib/source contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_99_STANDARD_VEC_STRING_BASELINE_ALPHA.md`. + +exp-99 adds `std/vec_string.slo` as the first usable source-authored +`(vec string)` facade over the sibling concrete `std.vec.string` runtime +family. Its public surface is: + +- direct wrappers: `empty`, `append`, `len`, and `at` +- builder helpers: `singleton`, `append2`, `append3`, `pair`, and `triple` +- query helpers: `is_empty`, `index_or`, `first_or`, and `last_or` +- simple real-program helpers: `contains` and `count_of` + +The direct wrappers call only `std.vec.string.empty`, +`std.vec.string.append`, `std.vec.string.len`, and `std.vec.string.index`. +The remaining helpers are ordinary deterministic Slovo source helpers over +that direct surface, string equality, integer comparison, and `i32` +counting. `index_or` returns its fallback for negative and out-of-range +positions, `first_or` delegates to `index_or values 0 fallback`, `last_or` +provides a stable empty-vector fallback, `contains` returns `true` exactly +when any element equals the target, and `count_of` returns `0` for empty +vectors or absent targets while counting repeated matches exactly. + +The matching explicit import fixture, `examples/projects/std-import-vec_string/`, +deterministically exercises the direct facade plus the builder, query, +contains, and count_of helpers, with 6 passed tests in the explicit project +fixture. The release also adds `examples/supported/vec-string.slo` with +matching formatter coverage in `examples/formatter/vec-string.slo` for the raw +`std.vec.string.*` runtime surface, vector equality, and append immutability. + +exp-99 does not add generics, vec element families beyond `i32`, `i64`, and +`string`, option-query helpers, transform/range/edit helpers, nested vecs or +vecs inside other containers, mutating/capacity APIs, stable ABI/layout +claims, or beta maturity. + +## exp-98 (released experimental alpha) + +Release label: `exp-98` + +Release name: Standard Vec I64 Edit Helpers Alpha + +Release date: 2026-05-21 + +Status: released experimental alpha stdlib/source contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_98_STANDARD_VEC_I64_EDIT_HELPERS_ALPHA.md`. + +exp-98 extends `std/vec_i64.slo` with exactly six new public helpers over +the existing concrete vec i64 runtime family: + +- `insert_at` +- `insert_range` +- `replace_at` +- `replace_range` +- `remove_at` +- `remove_range` + +The helper surface stays concrete to `(vec i64)` and remains source-authored +and compositional over the already released vec_i64 helper surface, +especially `concat`, `take`, `drop`, and `append`. The full `vec_i64` +source family stays recursive and immutable: there are no `var` or `set` +locals in `std/vec_i64.slo`. + +`insert_at ((values (vec i64)) (position i32) (value i64)) -> (vec i64)` +inserts before the current element at an in-range `position`, appends at +`position == len(values)`, and returns `values` unchanged for negative or +greater-than-length positions. `insert_range ((values (vec i64)) +(position i32) (inserted (vec i64))) -> (vec i64)` follows the same +position rules while preserving the order of both vectors. `replace_at` +replaces one in-range element and returns `values` unchanged for negative or +out-of-range positions. `replace_range ((values (vec i64)) (start i32) +(end_exclusive i32) (replacement (vec i64))) -> (vec i64)` replaces the +half-open range `[start, end_exclusive)`, returns `values` unchanged for +negative, empty, or past-end starts, and replaces the tail when +`end_exclusive` extends past the source length. `remove_at` removes one +in-range element and otherwise returns `values` unchanged. `remove_range` +removes the half-open range `[start, end_exclusive)`, returns `values` +unchanged for negative, empty, or past-end starts, and removes the tail +when `end_exclusive` extends past the source length. + +The matching explicit import fixture, `examples/projects/std-import-vec_i64/`, +deterministically exercises the new edit helper package alongside the +earlier builder, query, option-query, transform, subvec, and real-program +groups, with 15 passed tests in the explicit project fixture. + +exp-98 does not add generics, new compiler-known `std.*` runtime names, +mutation/capacity APIs, iterators, sort/map/filter, or beta maturity. + +## exp-97 (released experimental alpha) + +Release label: `exp-97` + +Release name: Standard Vec I64 Transform Helpers Alpha + +Release date: 2026-05-21 + +Status: released experimental alpha stdlib/source contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_97_STANDARD_VEC_I64_TRANSFORM_HELPERS_ALPHA.md`. + +exp-97 extends `std/vec_i64.slo` with exactly five new public helpers over +the existing concrete vec i64 runtime family: + +- `concat` +- `take` +- `drop` +- `reverse` +- `subvec` + +The helper surface stays concrete to `(vec i64)` and remains source-authored +over the existing `append`, `len`, `at`, and `empty` facade operations only. +The full `vec_i64` source family stays recursive and immutable: there are no +`var` or `set` locals in `std/vec_i64.slo`. + +`concat ((left (vec i64)) (right (vec i64))) -> (vec i64)` preserves the +order of both inputs and returns a copied left-then-right result. `take` +treats negative counts as zero and returns the original vector when the +requested count reaches or exceeds the current length. `drop` treats +negative counts as zero and returns an empty vector when the requested count +reaches or exceeds the current length. `reverse` returns a copied vector in +reverse order. `subvec ((values (vec i64)) (start i32) (end_exclusive i32)) +-> (vec i64)` returns a copied half-open range, returns `(empty)` for +negative or empty ranges, and returns the remaining tail when +`end_exclusive` extends past the source length. + +The matching explicit import fixture, `examples/projects/std-import-vec_i64/`, +deterministically exercises the new transform helper package alongside the +earlier direct-wrapper, builder, query, option-query, contains, and sum +groups, with 9 passed tests in the explicit project fixture. + +exp-97 does not add insert/replace/remove helpers for `(vec i64)`, generics, +new compiler-known `std.*` runtime names, mutation/capacity APIs, iterators, +sort/map/filter, or beta maturity. + +## exp-96 (released experimental alpha) + +Release label: `exp-96` + +Release name: Standard Vec I64 Option Query Helpers Alpha + +Release date: 2026-05-20 + +Status: released experimental alpha stdlib/source contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_96_STANDARD_VEC_I64_OPTION_QUERY_HELPERS_ALPHA.md`. + +exp-96 extends `std/vec_i64.slo` with exactly five new public helpers over +the existing concrete vec i64 runtime family: + +- `index_option` +- `first_option` +- `last_option` +- `index_of_option` +- `last_index_of_option` + +The option-returning helper surface stays concrete to `(option i64)` and +`(option i32)`. The module stays self-contained and explicit by constructing +raw current `some` and `none` option values directly instead of importing +`std.option`. + +`index_option ((values (vec i64)) (position i32)) -> (option i64)` returns +`(none i64)` for negative and out-of-range positions and `(some i64 ...)` +for in-range positions. `first_option` delegates to `index_option values 0`. +`last_option` returns `(none i64)` for empty vectors and the final element as +`(some i64 ...)` otherwise. `index_of_option` returns the first matching +position as `(some i32 ...)` and `(none i32)` when the target is absent. +`last_index_of_option` returns the last matching position as `(some i32 ...)` +and `(none i32)` when the target is absent. + +The full `vec_i64` source family now stays recursive and immutable: there are +no `var` or `set` locals in `std/vec_i64.slo`. The matching explicit import +fixture, `examples/projects/std-import-vec_i64/`, deterministically exercises +the new option-query helper group alongside the earlier direct-wrapper, +builder, query, contains, and sum groups, with 7 passed tests in the explicit +project fixture. + +exp-96 does not add transform/range/edit helpers for `(vec i64)`, generics, +new compiler-known `std.*` runtime names, mutation/capacity APIs, iterators, +sort/map/filter, or beta maturity. + +## exp-95 (released experimental alpha) + +Release label: `exp-95` + +Release name: Standard Option I64 Baseline Alpha + +Release date: 2026-05-20 + +Status: released experimental alpha source/core-language contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_95_STANDARD_OPTION_I64_BASELINE_ALPHA.md`. + +exp-95 broadens the conservative option slice from only `(option i32)` to +concrete `(option i32)` plus `(option i64)`. + +On the staged standard-library side, `std/option.slo` now includes: + +- constructors: `some_i32`, `none_i32`, `some_i64`, `none_i64` +- observers: `is_some_i32`, `is_none_i32`, `is_some_i64`, `is_none_i64` +- extract/fallback helpers: `unwrap_some_i32`, `unwrap_or_i32`, + `unwrap_some_i64`, and `unwrap_or_i64` + +On the conservative core-language side, exp-95 broadens immutable option value +flow, direct constructor returns, tag observation, trap-based payload +extraction, and source-level `match` to `(option i64)`. The matching Slovo +fixtures now exercise raw `(option i64)` returns, value flow, payload access, +and `match`, and the explicit `std.option` import project now exercises both +the `i32` and `i64` helper families. + +exp-95 does not add compiler-known `std.*` runtime names, automatic `std` +imports/search, generic option helpers, option payload families beyond `i32` +and `i64`, nested/container option support, option equality or printing, +stable ABI/layout claims, manifest schema changes, or beta maturity. + +## exp-94 (released experimental alpha) + +Release label: `exp-94` + +Release name: Standard Vec I64 Baseline Alpha + +Release date: 2026-05-20 + +Status: released experimental alpha stdlib/source contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_94_STANDARD_VEC_I64_BASELINE_ALPHA.md`. + +exp-94 adds `std/vec_i64.slo` as the first usable source-authored `(vec i64)` +facade over the sibling concrete vec runtime family. Its public surface is: + +- direct wrappers: `empty`, `append`, `len`, and `at` +- builder helpers: `singleton`, `append2`, `append3`, `pair`, and `triple` +- query helpers: `is_empty`, `index_or`, `first_or`, and `last_or` +- simple real-program helpers: `contains` and `sum` + +The direct wrappers call only `std.vec.i64.empty`, `std.vec.i64.append`, +`std.vec.i64.len`, and `std.vec.i64.index`. The remaining helpers are +ordinary deterministic Slovo source helpers over that direct surface, current +control flow, integer comparison, equality, and `i64` addition. `index_or` +returns its fallback for negative and out-of-range positions, `first_or` and +`last_or` provide stable empty-vector fallbacks, `contains` returns `true` +exactly when any element equals the target, and `sum` returns `0i64` for empty +vectors. +The matching explicit import fixture, `examples/projects/std-import-vec_i64/`, +deterministically exercises the direct wrappers plus the builder, query, +contains, and sum helpers, with 6 passed tests in the explicit project +fixture. The release also adds `examples/supported/vec-i64.slo` with matching +formatter coverage in `examples/formatter/vec-i64.slo` for the raw +`std.vec.i64.*` runtime surface, vector equality, and append immutability. + +exp-94 does not add generic vectors, `vec f64`, `vec string`, +option/result payload widening, automatic standard-library imports, +compiler-loaded standard-library source, new compiler-known `std.*` runtime +names on the Slovo side, transform/range-edit helper families for `(vec i64)`, +mutating vec APIs, capacity or reserve management, sorting, mapping, +filtering, iterators, stable ABI/layout/ownership, optimizer guarantees, or +beta maturity. + +## exp-93 (released experimental alpha) + +Release label: `exp-93` + +Release name: Standard Vec I32 Count-Of Helper Alpha + +Release date: 2026-05-20 + +Status: released experimental alpha stdlib/source contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_93_STANDARD_VEC_I32_COUNT_OF_HELPER_ALPHA.md`. + +exp-93 extends `std/vec_i32.slo` with exactly one new public helper over the +already promoted concrete vec runtime family: + +- `count_of` + +This is an ordinary Slovo source function over the existing `len`, `at`, +equality, and `while` surface; it adds no new runtime calls, compiler +semantic changes, private recursive helper, or compiler-known `std.*` names. +`count_of ((values (vec i32)) (target i32)) -> i32` returns the number of +elements of `values` equal to `target`. It returns `0` for an empty vector +and when `target` is absent, counts repeated matches exactly, and leaves the +source vector unchanged. The matching explicit import fixture, +`examples/projects/std-import-vec_i32/`, deterministically exercises empty, +repeated-match, single-match, no-match, and unchanged-source cases alongside +the earlier vec helper groups, with 22 passed tests in the explicit project +fixture. + +exp-93 does not add generic collections, vector payload families beyond +`(vec i32)`, slice/view types, automatic standard-library imports, +compiler-loaded standard-library source, new compiler-known `std.*` runtime +names, mutable vec locals, broader vec aggregation or filter families beyond +the current concrete helper surface, mutating vec APIs, capacity or reserve +management, sorting, mapping, filtering, iterators, stable +ABI/layout/ownership, optimizer guarantees, or beta maturity. + +## exp-92 (released experimental alpha) + +Release label: `exp-92` + +Release name: Standard Vec I32 Without-Prefix Helper Alpha + +Release date: 2026-05-20 + +Status: released experimental alpha stdlib/source contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_92_STANDARD_VEC_I32_WITHOUT_PREFIX_HELPER_ALPHA.md`. + +exp-92 extends `std/vec_i32.slo` with exactly one new public helper over the +already promoted concrete vec runtime family: + +- `without_prefix` + +This is an ordinary compositional Slovo source function over the existing +`starts_with`, `drop`, and `len` helpers; it adds no new runtime calls, +compiler semantic changes, private recursive helper, or compiler-known +`std.*` names. `without_prefix ((values (vec i32)) (prefix (vec i32))) -> +(vec i32)` removes a matching leading prefix from `values`. It returns +`values` unchanged for an empty prefix, a longer prefix, or a mismatched +prefix, returns `(empty)` when `values` exactly equals `prefix`, and leaves +both source vectors unchanged. The matching explicit import fixture, +`examples/projects/std-import-vec_i32/`, deterministically exercises empty, +exact, shorter matching, longer non-matching, mismatched, and unchanged-source +prefix cases alongside the earlier vec helper groups, with 21 passed tests in +the explicit project fixture. + +exp-92 does not add generic collections, vector payload families beyond +`(vec i32)`, slice/view types, automatic standard-library imports, +compiler-loaded standard-library source, new compiler-known `std.*` runtime +names, mutable vec locals, broader prefix/suffix helper families beyond +`starts_with`, `without_prefix`, `ends_with`, and `without_suffix`, broader +copied subvector/range-edit helper families beyond `insert_range`, +`replace_range`, `remove_range`, `subvec`, `without_prefix`, and +`without_suffix`, mutating vec APIs, capacity or reserve management, +sorting, mapping, filtering, iterators, stable ABI/layout/ownership, +optimizer guarantees, or beta maturity. + +## exp-91 (released experimental alpha) + +Release label: `exp-91` + +Release name: Standard Vec I32 Without-Suffix Helper Alpha + +Release date: 2026-05-20 + +Status: released experimental alpha stdlib/source contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_91_STANDARD_VEC_I32_WITHOUT_SUFFIX_HELPER_ALPHA.md`. + +exp-91 extends `std/vec_i32.slo` with exactly one new public helper over the +already promoted concrete vec runtime family: + +- `without_suffix` + +This is an ordinary compositional Slovo source function over the existing +`ends_with`, `take`, and `len` helpers; it adds no new runtime calls, +compiler semantic changes, private recursive helper, or compiler-known +`std.*` names. `without_suffix ((values (vec i32)) (suffix (vec i32))) -> +(vec i32)` removes a matching trailing suffix from `values`. It returns +`values` unchanged for an empty suffix, a longer suffix, or a mismatched +suffix, returns `(empty)` when `values` exactly equals `suffix`, and leaves +both source vectors unchanged. The matching explicit import fixture, +`examples/projects/std-import-vec_i32/`, deterministically exercises empty, +exact, shorter matching, longer non-matching, mismatched, and unchanged-source +suffix cases alongside the earlier vec helper groups, with 20 passed tests in +the explicit project fixture. + +exp-91 does not add generic collections, vector payload families beyond +`(vec i32)`, slice/view types, automatic standard-library imports, +compiler-loaded standard-library source, new compiler-known `std.*` runtime +names, mutable vec locals, broader prefix/suffix helper families beyond +`starts_with`, `ends_with`, and `without_suffix`, broader copied +subvector/range-edit helper families beyond `insert_range`, `replace_range`, +`remove_range`, `subvec`, and `without_suffix`, mutating vec APIs, capacity +or reserve management, sorting, mapping, filtering, iterators, stable +ABI/layout/ownership, optimizer guarantees, or beta maturity. + +## exp-90 (released experimental alpha) + +Release label: `exp-90` + +Release name: Standard Vec I32 Ends-With Helper Alpha + +Release date: 2026-05-20 + +Status: released experimental alpha stdlib/source contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_90_STANDARD_VEC_I32_ENDS_WITH_HELPER_ALPHA.md`. + +exp-90 extends `std/vec_i32.slo` with exactly one new public helper over the +already promoted concrete vec runtime family: + +- `ends_with` + +This is an ordinary compositional Slovo source function over the existing +`drop`, `len`, and vec equality helpers; it adds no new runtime calls, +compiler semantic changes, private recursive helper, or compiler-known +`std.*` names. `ends_with ((values (vec i32)) (suffix (vec i32))) -> bool` +returns `true` exactly when `values` ends with all elements of `suffix` in +order. It returns `true` for an empty suffix, `false` when `len(suffix) > +len(values)`, `true` for equal vectors and shorter matching suffixes, and +`false` for mismatched suffixes. The helper leaves both source vectors +unchanged. The matching explicit import fixture, +`examples/projects/std-import-vec_i32/`, deterministically exercises empty, +equal, shorter matching, longer non-matching, mismatched, and unchanged-source +suffix cases alongside the earlier vec helper groups, with 19 passed tests in +the explicit project fixture. + +exp-90 does not add generic collections, vector payload families beyond +`(vec i32)`, slice/view types, automatic standard-library imports, +compiler-loaded standard-library source, new compiler-known `std.*` runtime +names, mutable vec locals, broader prefix/suffix query helpers beyond +`starts_with` and `ends_with`, broader copied subvector/range-edit helper +families beyond `insert_range`, `replace_range`, `remove_range`, and +`subvec`, mutating vec APIs, capacity or reserve management, sorting, +mapping, filtering, iterators, stable ABI/layout/ownership, optimizer +guarantees, or beta maturity. + +## exp-89 (released experimental alpha) + +Release label: `exp-89` + +Release name: Standard Vec I32 Starts-With Helper Alpha + +Release date: 2026-05-20 + +Status: released experimental alpha stdlib/source contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_89_STANDARD_VEC_I32_STARTS_WITH_HELPER_ALPHA.md`. + +exp-89 extends `std/vec_i32.slo` with exactly one new public helper over the +already promoted concrete vec runtime family: + +- `starts_with` + +This is an ordinary compositional Slovo source function over the existing +`take`, `len`, and vec equality helpers; it adds no new runtime calls, +compiler semantic changes, private recursive helper, or compiler-known +`std.*` names. `starts_with ((values (vec i32)) (prefix (vec i32))) -> bool` +returns `true` exactly when `values` begins with all elements of `prefix` in +order. It returns `true` for an empty prefix, `false` when `len(prefix) > +len(values)`, `true` for equal vectors and shorter matching prefixes, and +`false` for mismatched prefixes. The helper leaves both source vectors +unchanged. The matching explicit import fixture, +`examples/projects/std-import-vec_i32/`, deterministically exercises empty, +equal, shorter matching, longer non-matching, mismatched, and unchanged-source +cases alongside the earlier vec helper groups, with 18 passed tests in the +explicit project fixture. + +exp-89 does not add generic collections, vector payload families beyond +`(vec i32)`, slice/view types, automatic standard-library imports, +compiler-loaded standard-library source, new compiler-known `std.*` runtime +names, mutable vec locals, broader prefix/suffix query helpers beyond +`starts_with`, broader copied subvector/range-edit helper families beyond +`insert_range`, `replace_range`, `remove_range`, and `subvec`, mutating vec +APIs, capacity or reserve management, sorting, mapping, filtering, +iterators, stable ABI/layout/ownership, optimizer guarantees, or beta +maturity. + +## exp-88 (released experimental alpha) + +Release label: `exp-88` + +Release name: Standard Vec I32 Replace Range Helper Alpha + +Release date: 2026-05-20 + +Status: released experimental alpha stdlib/source contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_88_STANDARD_VEC_I32_REPLACE_RANGE_HELPER_ALPHA.md`. + +exp-88 extends `std/vec_i32.slo` with exactly one new public helper over the +already promoted concrete vec runtime family: + +- `replace_range` + +This is an ordinary compositional Slovo source function over the existing +`take`, `drop`, and `concat` vec helpers; it adds no new runtime calls, +compiler semantic changes, private recursive helper, or compiler-known +`std.*` names. `replace_range ((values (vec i32)) (start i32) +(end_exclusive i32) (replacement (vec i32))) -> (vec i32)` replaces the +half-open range `[start, end_exclusive)` with all of `replacement` when the +start is valid. If `start < 0`, `end_exclusive <= start`, or +`start >= len(values)`, it returns `values` unchanged. If +`end_exclusive >= len(values)`, it replaces the tail from `start`. The +helper preserves prefix order, then replacement order, then surviving suffix +order, and does not mutate either source vector. The matching explicit +import fixture, `examples/projects/std-import-vec_i32/`, deterministically +exercises middle replacement, tail replacement, unchanged invalid-range, +unchanged-source-vector, unchanged-replacement-vector, and preserved-order +cases alongside the earlier vec helper groups, with 17 passed tests in the +explicit project fixture. + +exp-88 does not add generic collections, vector payload families beyond +`(vec i32)`, slice/view types, automatic standard-library imports, +compiler-loaded standard-library source, new compiler-known `std.*` runtime +names, mutable vec locals, broader copied subvector/range-edit helper +families beyond `insert_range`, `replace_range`, `remove_range`, and +`subvec`, public insert/remove/edit families beyond `insert_range`, +`replace_range`, `remove_range`, `subvec`, `insert_at`, `replace_at`, and +`remove_at`, mutating vec APIs, capacity or reserve management, sorting, +mapping, filtering, iterators, stable ABI/layout/ownership, optimizer +guarantees, or beta maturity. + +## exp-87 (released experimental alpha) + +Release label: `exp-87` + +Release name: Standard Vec I32 Insert Range Helper Alpha + +Release date: 2026-05-20 + +Status: released experimental alpha stdlib/source contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_87_STANDARD_VEC_I32_INSERT_RANGE_HELPER_ALPHA.md`. + +exp-87 extends `std/vec_i32.slo` with exactly one new public helper over the +already promoted concrete vec runtime family: + +- `insert_range` + +This is an ordinary compositional Slovo source function over the existing +`take`, `drop`, and `concat` vec helpers; it adds no new runtime calls, +compiler semantic changes, private recursive helper, or compiler-known +`std.*` names. `insert_range ((values (vec i32)) (position i32) +(inserted (vec i32))) -> (vec i32)` returns a new vector when `position` is +valid: if `0 <= position < len(values)`, it inserts all of `inserted` before +the current element at `position`, and if `position == len(values)`, it +appends `inserted` at the end. The result length is then +`len(values) + len(inserted)`. If `position < 0` or `position > len(values)`, +it returns `values` unchanged. The helper preserves the order of both +vectors and does not mutate either source vector. The matching explicit +import fixture, `examples/projects/std-import-vec_i32/`, deterministically +exercises middle insertion, append-at-end insertion, negative-position, +out-of-range-position, and unchanged-source-vector cases alongside the +earlier vec helper groups. + +exp-87 does not add generic collections, vector payload families beyond +`(vec i32)`, slice/view types, automatic standard-library imports, +compiler-loaded standard-library source, new compiler-known `std.*` runtime +names, mutable vec locals, broader copied subvector/range-edit helper +families beyond `insert_range`, `remove_range`, and `subvec`, public +insert/remove/edit families beyond `insert_range`, `remove_range`, `subvec`, +`insert_at`, `replace_at`, and `remove_at`, mutating vec APIs, capacity or +reserve management, sorting, mapping, filtering, iterators, stable +ABI/layout/ownership, optimizer guarantees, or beta maturity. + +## exp-86 (released experimental alpha) + +Release label: `exp-86` + +Release name: Standard Vec I32 Remove Range Helper Alpha + +Release date: 2026-05-20 + +Status: released experimental alpha stdlib/source contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_86_STANDARD_VEC_I32_REMOVE_RANGE_HELPER_ALPHA.md`. + +exp-86 extends `std/vec_i32.slo` with exactly one new public helper over the +already promoted concrete vec runtime family: + +- `remove_range` + +This is an ordinary compositional Slovo source function over the existing +`take`, `drop`, and `concat` vec helpers; it adds no new runtime calls, +compiler semantic changes, private recursive helper, or compiler-known +`std.*` names. `remove_range ((values (vec i32)) (start i32) +(end_exclusive i32)) -> (vec i32)` removes the half-open range +`[start, end_exclusive)` from `values` while preserving the order of the +remaining elements. If `start < 0`, `end_exclusive <= start`, or +`start >= len(values)`, it returns `values` unchanged. If +`end_exclusive >= len(values)`, it removes the tail from `start`. The helper +leaves the source vector unchanged. The matching explicit import fixture, +`examples/projects/std-import-vec_i32/`, deterministically exercises +middle-range removal, tail removal, `end_exclusive <= start`, +out-of-range-start, negative-start, and unchanged-source cases alongside the +earlier vec helper groups. + +exp-86 does not add generic collections, vector payload families beyond +`(vec i32)`, slice/view types, automatic standard-library imports, +compiler-loaded standard-library source, new compiler-known `std.*` runtime +names, mutable vec locals, broader subvector/range-removal helper families +beyond `remove_range` and `subvec`, public insert/remove/edit families beyond +`remove_range`, `subvec`, `insert_at`, `replace_at`, and `remove_at`, +mutating vec APIs, capacity or reserve management, sorting, mapping, +filtering, iterators, stable ABI/layout/ownership, optimizer guarantees, or +beta maturity. + +## exp-85 (released experimental alpha) + +Release label: `exp-85` + +Release name: Standard Vec I32 Subvec Helper Alpha + +Release date: 2026-05-20 + +Status: released experimental alpha stdlib/source contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_85_STANDARD_VEC_I32_SUBVEC_HELPER_ALPHA.md`. + +exp-85 extends `std/vec_i32.slo` with exactly one new public helper over the +already promoted concrete vec runtime family: + +- `subvec` + +This is an ordinary compositional Slovo source function over the existing +`take` and `drop` vec helpers; it adds no new runtime calls, compiler +semantic changes, private recursive helper, or compiler-known `std.*` names. +`subvec ((values (vec i32)) (start i32) (end_exclusive i32)) -> (vec i32)` +returns a copied contiguous subvector `[start, end_exclusive)`. If +`start < 0`, `end_exclusive <= start`, or `start >= len(values)`, it returns +`(empty)`. If `end_exclusive > len(values)`, it returns the remaining tail +from `start`. The helper preserves source order, leaves the source vector +unchanged, and remains an ordinary copied vec helper rather than a slice/view +type. The matching explicit import fixture, +`examples/projects/std-import-vec_i32/`, deterministically exercises +middle-range, tail-truncation, empty-range, out-of-range, negative-start, +and unchanged-source cases alongside the earlier vec helper groups. + +exp-85 does not add generic collections, vector payload families beyond +`(vec i32)`, slice/view types, automatic standard-library imports, +compiler-loaded standard-library source, new compiler-known `std.*` runtime +names, mutable vec locals, broader copied subvector/window helpers beyond +`subvec`, public insert/remove/edit families beyond `subvec`, `insert_at`, +`replace_at`, and `remove_at`, mutating vec APIs, capacity or reserve +management, sorting, mapping, filtering, iterators, stable +ABI/layout/ownership, optimizer guarantees, or beta maturity. + +## exp-84 (released experimental alpha) + +Release label: `exp-84` + +Release name: Standard Vec I32 Insert Helper Alpha + +Release date: 2026-05-20 + +Status: released experimental alpha stdlib/source contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_84_STANDARD_VEC_I32_INSERT_HELPER_ALPHA.md`. + +exp-84 extends `std/vec_i32.slo` with exactly one new public helper over the +already promoted concrete vec runtime family: + +- `insert_at` + +This is an ordinary compositional Slovo source function over the existing +`append`, `take`, `drop`, and `concat` vec helpers; it adds no new runtime +calls, compiler semantic changes, private recursive helper, or compiler-known +`std.*` names. `insert_at ((values (vec i32)) (position i32) (value i32)) -> +(vec i32)` returns a new vector when `position` is valid: if +`0 <= position < len(values)`, it inserts `value` before the current element +at `position`, and if `position == len(values)`, it appends `value` at the +end. The result length is then `len(values) + 1`. If `position < 0` or +`position > len(values)`, it returns `values` unchanged. The helper does not +mutate the source vector. The matching explicit import fixture, +`examples/projects/std-import-vec_i32/`, deterministically exercises +middle-insert, append-at-end, negative, and out-of-range insertion cases +alongside the earlier vec helper groups. + +exp-84 does not add generic collections, vector payload families beyond +`(vec i32)`, slice types, automatic standard-library imports, +compiler-loaded standard-library source, new compiler-known `std.*` runtime +names, mutable vec locals, public insert/remove/edit families beyond +`insert_at`, `replace_at`, and `remove_at`, mutating vec APIs, capacity or +reserve management, sorting, mapping, filtering, iterators, stable +ABI/layout/ownership, optimizer guarantees, or beta maturity. + +## exp-83 (released experimental alpha) + +Release label: `exp-83` + +Release name: Standard Vec I32 Remove Helper Alpha + +Release date: 2026-05-20 + +Status: released experimental alpha stdlib/source contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_83_STANDARD_VEC_I32_REMOVE_HELPER_ALPHA.md`. + +exp-83 extends `std/vec_i32.slo` with exactly one new public helper over the +already promoted concrete vec runtime family: + +- `remove_at` + +This is an ordinary compositional Slovo source function over the existing +`take`, `drop`, and `concat` vec helpers; it adds no new runtime calls, +compiler semantic changes, private recursive helper, or compiler-known +`std.*` names. `remove_at ((values (vec i32)) (position i32)) -> (vec i32)` +returns a new vector with the element at `position` removed when +`0 <= position < len(values)`, preserving the order of the remaining +elements. The result length is then `len(values) - 1`. If `position < 0` or +`position >= len(values)`, it returns `values` unchanged. The helper does not +mutate the source vector. The matching explicit import fixture, +`examples/projects/std-import-vec_i32/`, deterministically exercises +in-range, negative, and out-of-range removal cases alongside the earlier vec +helper groups. + +exp-83 does not add generic collections, vector payload families beyond +`(vec i32)`, slice types, automatic standard-library imports, +compiler-loaded standard-library source, new compiler-known `std.*` runtime +names, mutable vec locals, public insert/remove/edit families beyond +`replace_at` and `remove_at`, mutating vec APIs, capacity or reserve +management, sorting, mapping, filtering, iterators, stable +ABI/layout/ownership, optimizer guarantees, or beta maturity. + +## exp-82 (released experimental alpha) + +Release label: `exp-82` + +Release name: Standard Vec I32 Replace Helper Alpha + +Release date: 2026-05-20 + +Status: released experimental alpha stdlib/source contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_82_STANDARD_VEC_I32_REPLACE_HELPER_ALPHA.md`. + +exp-82 extends `std/vec_i32.slo` with exactly one new public helper over the +already promoted concrete vec runtime family: + +- `replace_at` + +This is an ordinary compositional Slovo source function over the existing +`append`, `take`, `drop`, and `concat` vec helpers; it adds no new runtime +calls, compiler semantic changes, or compiler-known `std.*` names. +`replace_at ((values (vec i32)) (position i32) (replacement i32)) -> (vec i32)` +returns a new vector with the same length and order as `values` except that +the in-range `position` slot becomes `replacement`. If `position < 0` or +`position >= len(values)`, it returns `values` unchanged. The helper does not +mutate the source vector. The matching explicit import fixture, +`examples/projects/std-import-vec_i32/`, deterministically exercises in-range, +negative, and out-of-range replacement cases alongside the earlier vec helper +groups. + +exp-82 does not add generic collections, vector payload families beyond +`(vec i32)`, slice types, automatic standard-library imports, +compiler-loaded standard-library source, new compiler-known `std.*` runtime +names, mutable vec locals, public insert/remove/edit families beyond +`replace_at`, mutating vec APIs, capacity or reserve management, sorting, +mapping, filtering, iterators, stable ABI/layout/ownership, optimizer +guarantees, or beta maturity. + +## exp-81 (released experimental alpha) + +Release label: `exp-81` + +Release name: Standard Vec I32 Range Helper Alpha + +Release date: 2026-05-20 + +Status: released experimental alpha stdlib/source contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_81_STANDARD_VEC_I32_RANGE_HELPER_ALPHA.md`. + +exp-81 extends `std/vec_i32.slo` with exactly one new public helper over the +already promoted concrete vec runtime family: + +- `range` + +This is an ordinary Slovo source function over the existing `append` and +`empty` vec facade operations, with private recursive generation only. +`range ((start i32) (end_exclusive i32)) -> (vec i32)` produces an ascending +half-open sequence from `start` up to but excluding `end_exclusive`. +Negative bounds are allowed, and it returns an empty vector when +`end_exclusive <= start`. `range_from_zero` remains public and may delegate +to `range 0 count`. The matching explicit import fixture, +`examples/projects/std-import-vec_i32/`, deterministically exercises +negative, empty, and positive ascending range cases alongside the earlier +vec helper groups. + +exp-81 does not add generic collections, vector payload families beyond +`(vec i32)`, slice types, automatic standard-library imports, +compiler-loaded standard-library source, new compiler-known `std.*` runtime +names, mutable vec locals, descending or stepped range variants, inclusive +range variants, edit helpers, mutating vec APIs, capacity or reserve +management, sorting, mapping, filtering, iterators, stable +ABI/layout/ownership, optimizer guarantees, or beta maturity. + +## exp-80 (released experimental alpha) + +Release label: `exp-80` + +Release name: Standard Vec I32 Generated Constructor Helpers Alpha + +Release date: 2026-05-20 + +Status: released experimental alpha stdlib/source contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_80_STANDARD_VEC_I32_GENERATED_CONSTRUCTOR_HELPERS_ALPHA.md`. + +exp-80 extends `std/vec_i32.slo` with narrow generated constructor helpers +over the already promoted concrete vec runtime family: + +- `repeat` +- `range_from_zero` + +These are ordinary Slovo source functions over the existing `append` and +`empty` vec facade operations. `repeat` returns an empty vector when +`count <= 0`. `range_from_zero` produces `0`, `1`, and so on up to but +excluding `count`, and returns an empty vector when `count <= 0`. The +matching explicit import fixture, `examples/projects/std-import-vec_i32/`, +now deterministically exercises this constructor helper group alongside the +earlier direct, builder, fallback query, option-query, transform, and +real-program vec helpers. + +exp-80 does not add generic collections, vector payload families beyond +`(vec i32)`, slice types, automatic standard-library imports, +compiler-loaded standard-library source, new compiler-known `std.*` runtime +names, mutable vec locals, mutating vec APIs, capacity or reserve +management, public `range`, descending or stepped range variants, inclusive +range variants, edit helpers, sorting, mapping, filtering, iterators, stable +ABI/layout/ownership, +optimizer guarantees, or beta maturity. + +## exp-79 (released experimental alpha) + +Release label: `exp-79` + +Release name: Standard Vec I32 Transform Helpers Alpha + +Release date: 2026-05-20 + +Status: released experimental alpha stdlib/source contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_79_STANDARD_VEC_I32_TRANSFORM_HELPERS_ALPHA.md`. + +exp-79 extends `std/vec_i32.slo` with narrow transform helpers over the +already promoted concrete vec runtime family: + +- `concat` +- `take` +- `drop` +- `reverse` + +These are ordinary Slovo source functions over the existing `append`, `len`, +`at`, and `empty` vec facade operations. `concat`, `take`, `drop`, and +`reverse` are expressed with private recursive helpers instead of mutable vec +locals. Negative counts behave as zero; `take` returns the original vector +when the requested count reaches or exceeds the current length, and `drop` +returns an empty vector in that same case. The matching explicit import +fixture, `examples/projects/std-import-vec_i32/`, now deterministically +exercises this transform helper group alongside the earlier direct, builder, +fallback query, option-query, and real-program vec helpers. + +exp-79 does not add generic collections, vector payload families beyond +`(vec i32)`, slice types, automatic standard-library imports, +compiler-loaded standard-library source, new compiler-known `std.*` runtime +names, mutable vec locals, mutating vec APIs, capacity or reserve +management, sorting, mapping, filtering, iterators, stable +ABI/layout/ownership, optimizer guarantees, or beta maturity. + +## exp-78 (released experimental alpha) + +Release label: `exp-78` + +Release name: Standard CLI Local Source Facade Alpha + +Release date: 2026-05-20 + +Status: released experimental alpha stdlib/source contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_78_STANDARD_CLI_LOCAL_SOURCE_FACADE_ALPHA.md`. + +exp-78 records the staged `std/cli.slo` facade as the Slovo-side contract for +the sibling Glagol local-source gate without widening the `std.cli` helper +surface. `std/cli.slo` itself remains unchanged for this release, and the +released helper list stays: + +- `arg_text_result` +- `arg_i32_result` +- `arg_i32_or_zero` +- `arg_i32_or` +- `arg_i64_result` +- `arg_i64_or_zero` +- `arg_i64_or` +- `arg_f64_result` +- `arg_f64_or_zero` +- `arg_f64_or` +- `arg_bool_result` +- `arg_bool_or_false` +- `arg_bool_or` + +The facade remains ordinary Slovo source over existing `std.process` and +`std.string` surface only, with no additional standard-source dependencies. +The existing explicit import fixture, `examples/projects/std-import-cli/`, +remains valid, and the paired Glagol local-source gate is expected to mirror +the same composition pattern. + +exp-78 does not add new `std.cli` helpers, automatic standard-library +imports, compiler-loaded standard-library source, a `std.slo` aggregator, +package std dependency syntax, new compiler-known `std.*` runtime names, +process spawning, exit/status control, current-directory APIs, signal +handling, shell parsing, option or flag parsing, subcommands, +environment-backed configuration, richer CLI framework APIs, stable +ABI/layout/ownership, optimizer guarantees, or beta maturity. + +## exp-77 (released experimental alpha) + +Release label: `exp-77` + +Release name: Standard Vec I32 Option Query Helpers Alpha + +Release date: 2026-05-20 + +Status: released experimental alpha stdlib/source contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_77_STANDARD_VEC_I32_OPTION_QUERY_HELPERS_ALPHA.md`. + +exp-77 extends `std/vec_i32.slo` with concrete option-returning query helpers +over the already promoted concrete vec runtime family: + +- `index_option` +- `first_option` +- `last_option` +- `index_of_option` +- `last_index_of_option` + +These are ordinary Slovo source functions over the existing `len`, `at`, and +`while` vec facade operations plus `std.option.some_i32` and +`std.option.none_i32`. The matching explicit import fixture, +`examples/projects/std-import-vec_i32/`, now deterministically exercises this +option-query helper group alongside the earlier direct, builder, fallback +query, and real-program vec helpers. + +exp-77 does not add generic collections, vector payload families beyond +`(vec i32)`, option payload families beyond `(option i32)`, automatic +standard-library imports, compiler-loaded standard-library source, new +compiler-known `std.*` runtime names, mutating vec APIs, capacity or reserve +management, sorting, mapping, filtering, iterators, stable +ABI/layout/ownership, optimizer guarantees, or beta maturity. + +## exp-76 (released experimental alpha) + +Release label: `exp-76` + +Release name: Standard Vec I32 Source Helpers Alpha + +Release date: 2026-05-20 + +Status: released experimental alpha stdlib/source contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_76_STANDARD_VEC_I32_SOURCE_HELPERS_ALPHA.md`. + +exp-76 extends `std/vec_i32.slo` with a concrete `(vec i32)` source-authored +collection facade over the already promoted `std.vec.i32` runtime family: + +- direct wrappers: + - `empty` + - `append` + - `len` + - `at` +- builder helpers: + - `singleton` + - `append2` + - `append3` + - `pair` + - `triple` +- query helpers: + - `is_empty` + - `index_or` + - `first_or` + - `last_or` +- real-program helpers: + - `contains` + - `sum` + +These are ordinary Slovo source functions over the existing vector runtime +calls plus simple source `while` loops. The matching explicit import fixture, +`examples/projects/std-import-vec_i32/`, now exercises the direct facade and +the staged helper groups through explicit `std.vec_i32` imports. + +exp-76 does not add generic vectors, vector payload families beyond +`(vec i32)`, automatic standard-library imports, compiler-loaded +standard-library source, capacity or reserve management, push/pop helpers, +sorting, mapping, filtering, iterators, new compiler-known `std.*` runtime +names, stable ABI/layout/ownership, optimizer guarantees, or beta maturity. + +## exp-75 (released experimental alpha) + +Release label: `exp-75` + +Release name: Standard Option Constructors Alpha + +Release date: 2026-05-20 + +Status: released experimental alpha stdlib/source contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_75_STANDARD_OPTION_CONSTRUCTORS_ALPHA.md`. + +exp-75 extends `std/option.slo` with concrete source constructors for the +currently promoted `(option i32)` family: + +- `some_i32` +- `none_i32` + +These are ordinary Slovo source functions over the existing `some` and +`none` forms, fixed to the current released `(option i32)` family. The +matching explicit import fixture, `examples/projects/std-import-option/`, now +uses those constructors instead of repeating raw typed `some` and `none` +forms. + +exp-75 does not add generic option helpers, new option payload families, +automatic standard-library imports, compiler-loaded standard-library source, +new compiler-known `std.*` runtime names, stable ABI/layout/ownership, +optimizer guarantees, or beta maturity. + +## exp-74 (released experimental alpha) + +Release label: `exp-74` + +Release name: Standard Result Constructor Helpers Alpha + +Release date: 2026-05-20 + +Status: released experimental alpha stdlib/source contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_74_STANDARD_RESULT_CONSTRUCTOR_HELPERS_ALPHA.md`. + +exp-74 extends `std/result.slo` with concrete source constructors for the +currently promoted result families: + +- `ok_i32` +- `err_i32` +- `ok_i64` +- `err_i64` +- `ok_string` +- `err_string` +- `ok_f64` +- `err_f64` +- `ok_bool` +- `err_bool` + +These are ordinary Slovo source functions over the existing `ok` and `err` +forms, fixed to the current released `(result i32)` families. The +matching explicit import fixture, `examples/projects/std-import-result/`, now +uses those constructors instead of repeating raw typed `ok` and `err` forms. + +exp-74 does not add generic result helpers, new result payload families, +generic `map`/`and_then`, richer error ADTs, automatic standard-library +imports, compiler-loaded standard-library source, new compiler-known `std.*` +runtime names, stable ABI/layout/ownership, optimizer guarantees, or beta +maturity. + +## exp-73 (released experimental alpha) + +Release label: `exp-73` + +Release name: Standard Io Value Helpers Alpha + +Release date: 2026-05-20 + +Status: released experimental alpha stdlib/source contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_73_STANDARD_IO_VALUE_HELPERS_ALPHA.md`. + +exp-73 extends `std/io.slo` with source-authored value-returning print +helpers over the already promoted print runtime calls: + +- `print_i32_value` +- `print_i64_value` +- `print_f64_value` +- `print_string_value` +- `print_bool_value` + +These are ordinary Slovo source functions over `std.io.print_i32`, +`std.io.print_i64`, `std.io.print_f64`, `std.io.print_string`, and +`std.io.print_bool`, followed by returning the original value. The matching +explicit import fixture, `examples/projects/std-import-io/`, now exercises +both the earlier zero-returning wrappers and the new value-preserving cases. + +exp-73 does not add automatic standard-library imports, compiler-loaded +standard-library source, stderr facade helpers, stdin facade helpers, line +iteration, prompt APIs, terminal control, binary IO, streaming IO, async IO, +new compiler-known `std.*` runtime names, stable ABI/layout/ownership, +optimizer guarantees, or beta maturity. + +## exp-72 (released experimental alpha) + +Release label: `exp-72` + +Release name: Standard Cli Custom Fallback Helpers Alpha + +Release date: 2026-05-20 + +Status: released experimental alpha stdlib/source contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_72_STANDARD_CLI_CUSTOM_FALLBACK_HELPERS_ALPHA.md`. + +exp-72 extends `std/cli.slo` with source-authored typed argument +custom-fallback helpers over the already promoted argument lookup and concrete +parse result families: + +- `arg_i32_or` +- `arg_i64_or` +- `arg_f64_or` +- `arg_bool_or` + +These are ordinary Slovo source functions over `arg_result`, +`parse_i32_result`, `parse_i64_result`, `parse_f64_result`, and +`parse_bool_result` plus source `match`. The matching explicit import +fixture, `examples/projects/std-import-cli/`, now exercises both the earlier +zero/false fallbacks and the new typed custom-fallback cases. + +exp-72 does not add automatic standard-library imports, compiler-loaded +standard-library source, process spawning, exit/status control, current +directory APIs, signal handling, shell parsing, subcommands, richer CLI +framework APIs, generic configuration helpers, new compiler-known `std.*` +runtime names, stable ABI/layout/ownership, optimizer guarantees, or beta +maturity. + +## exp-71 (released experimental alpha) + +## exp-71 (released experimental alpha) + +Release label: `exp-71` + +Release name: Standard Process Custom Fallback Helpers Alpha + +Release date: 2026-05-20 + +Status: released experimental alpha stdlib/source contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_71_STANDARD_PROCESS_CUSTOM_FALLBACK_HELPERS_ALPHA.md`. + +exp-71 extends `std/process.slo` with source-authored typed argument +custom-fallback helpers over the already promoted argument lookup and concrete +parse result families: + +- `arg_i32_or` +- `arg_i64_or` +- `arg_f64_or` +- `arg_bool_or` + +These are ordinary Slovo source functions over `arg_result`, +`parse_i32_result`, `parse_i64_result`, `parse_f64_result`, and +`parse_bool_result` plus source `match`. The matching explicit import +fixture, `examples/projects/std-import-process/`, now exercises both the +earlier zero/false fallbacks and the new typed custom-fallback cases. + +exp-71 does not add automatic standard-library imports, compiler-loaded +standard-library source, process spawning, exit/status control, current +directory APIs, signal handling, shell parsing, subcommands, richer CLI +framework APIs, generic configuration helpers, new compiler-known `std.*` +runtime names, stable ABI/layout/ownership, optimizer guarantees, or beta +maturity. + +## exp-70 (released experimental alpha) + +## exp-70 (released experimental alpha) + +Release label: `exp-70` + +Release name: Standard Fs Custom Fallback Helpers Alpha + +Release date: 2026-05-20 + +Status: released experimental alpha stdlib/source contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_70_STANDARD_FS_CUSTOM_FALLBACK_HELPERS_ALPHA.md`. + +exp-70 extends `std/fs.slo` with source-authored typed read custom-fallback +helpers over the already promoted text-read and concrete parse result +families: + +- `read_i32_or` +- `read_i64_or` +- `read_f64_or` +- `read_bool_or` + +These are ordinary Slovo source functions over `read_text_result`, +`parse_i32_result`, `parse_i64_result`, `parse_f64_result`, and +`parse_bool_result` plus source `match`. The matching explicit import +fixture, `examples/projects/std-import-fs/`, now exercises both the earlier +zero/false fallbacks and the new typed custom-fallback cases. + +exp-70 does not add automatic standard-library imports, compiler-loaded +standard-library source, binary file APIs, directory traversal, streaming +file IO, async file IO, typed file writes, rich host errors, new +compiler-known `std.*` runtime names, stable ABI/layout/ownership, optimizer +guarantees, or beta maturity. + +## exp-69 (released experimental alpha) + +## exp-69 (released experimental alpha) + +Release label: `exp-69` + +Release name: Standard Env Custom Fallback Helpers Alpha + +Release date: 2026-05-20 + +Status: released experimental alpha stdlib/source contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_69_STANDARD_ENV_CUSTOM_FALLBACK_HELPERS_ALPHA.md`. + +exp-69 extends `std/env.slo` with source-authored typed parse +custom-fallback helpers over the already promoted environment lookup and +concrete parse result families: + +- `get_i32_or` +- `get_i64_or` +- `get_f64_or` +- `get_bool_or` + +These are ordinary Slovo source functions over `get_result`, +`parse_i32_result`, `parse_i64_result`, `parse_f64_result`, and +`parse_bool_result` plus source `match`. The matching explicit import +fixture, `examples/projects/std-import-env/`, now exercises both the earlier +zero/false fallbacks and the new typed custom-fallback cases. + +exp-69 does not add automatic standard-library imports, compiler-loaded +standard-library source, generic configuration helpers, environment mutation, +environment enumeration, environment writes, rich host errors, new +compiler-known `std.*` runtime names, stable ABI/layout/ownership, optimizer +guarantees, or beta maturity. + +## exp-68 (released experimental alpha) + +Release label: `exp-68` + +Release name: Standard String Custom Fallback Helpers Alpha + +Release date: 2026-05-20 + +Status: released experimental alpha stdlib/source contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_68_STANDARD_STRING_CUSTOM_FALLBACK_HELPERS_ALPHA.md`. + +exp-68 extends `std/string.slo` with source-authored typed parse +custom-fallback helpers over the already promoted concrete parse result +families: + +- `parse_i32_or` +- `parse_i64_or` +- `parse_f64_or` +- `parse_bool_or` + +These are ordinary Slovo source functions over `parse_i32_result`, +`parse_i64_result`, `parse_f64_result`, and `parse_bool_result` plus source +`match`. The matching explicit import fixture, +`examples/projects/std-import-string/`, now exercises both the earlier +zero/false fallbacks and the new typed custom-fallback cases. + +exp-68 does not add automatic standard-library imports, compiler-loaded +standard-library source, generic parse helpers, whitespace trimming, +case-insensitive bool parsing, rich parse errors, new compiler-known `std.*` +runtime names, stable ABI/layout/ownership, optimizer guarantees, or beta +maturity. + +## exp-67 (released experimental alpha) + +Release label: `exp-67` + +Release name: Standard Process Typed Helpers Alpha + +Release date: 2026-05-20 + +Status: released experimental alpha stdlib/source contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_67_STANDARD_PROCESS_TYPED_HELPERS_ALPHA.md`. + +exp-67 extends `std/process.slo` with source-authored typed argument +result/fallback helpers over process arguments plus the already promoted +concrete parse result families: + +- `arg_i32_result` +- `arg_i32_or_zero` +- `arg_i64_result` +- `arg_i64_or_zero` +- `arg_f64_result` +- `arg_f64_or_zero` +- `arg_bool_result` +- `arg_bool_or_false` + +These are ordinary Slovo source functions over `arg_result`, +`parse_i32_result`, `parse_i64_result`, `parse_f64_result`, and +`parse_bool_result` plus source `match`. The matching explicit import +fixture, `examples/projects/std-import-process/`, now exercises the existing +process wrappers, deterministic missing-argument cases, and deterministic +invalid-present fallback behavior through `arg 0`. + +exp-67 does not add automatic standard-library imports, compiler-loaded +standard-library source, process spawning, exit/status control, current +directory APIs, shell parsing, subcommands, richer CLI framework APIs, rich +host errors, new compiler-known `std.*` runtime names, stable +ABI/layout/ownership, optimizer guarantees, or beta maturity. + +## exp-66 (released experimental alpha) + +Release label: `exp-66` + +Release name: Standard Fs Typed Read Helpers Alpha + +Release date: 2026-05-20 + +Status: released experimental alpha stdlib/source contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_66_STANDARD_FS_TYPED_READ_HELPERS_ALPHA.md`. + +exp-66 extends `std/fs.slo` with source-authored typed read +result/fallback helpers over text-file reads plus the already promoted +concrete parse result families: + +- `read_i32_result` +- `read_i32_or_zero` +- `read_i64_result` +- `read_i64_or_zero` +- `read_f64_result` +- `read_f64_or_zero` +- `read_bool_result` +- `read_bool_or_false` + +These are ordinary Slovo source functions over `read_text_result`, +`parse_i32_result`, `parse_i64_result`, `parse_f64_result`, and +`parse_bool_result` plus source `match`. The matching explicit import +fixture, `examples/projects/std-import-fs/`, now exercises the existing fs +wrappers, typed parse success cases, and deterministic invalid/missing +fallback behavior. + +exp-66 does not add automatic standard-library imports, compiler-loaded +standard-library source, binary file APIs, directory traversal, streaming +file IO, async file IO, typed file writes, rich host errors, generic +configuration helpers, new compiler-known `std.*` runtime names, stable +ABI/layout/ownership, optimizer guarantees, or beta maturity. + +## exp-65 (released experimental alpha) + +Release label: `exp-65` + +Release name: Standard Env Typed Helpers Alpha + +Release date: 2026-05-20 + +Status: released experimental alpha stdlib/source contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_65_STANDARD_ENV_TYPED_HELPERS_ALPHA.md`. + +exp-65 extends `std/env.slo` with source-authored typed parse +result/fallback helpers over environment lookup plus the already promoted +concrete parse result families: + +- `get_i32_result` +- `get_i32_or_zero` +- `get_i64_result` +- `get_i64_or_zero` +- `get_f64_result` +- `get_f64_or_zero` +- `get_bool_result` +- `get_bool_or_false` + +These are ordinary Slovo source functions over `get_result`, +`parse_i32_result`, `parse_i64_result`, `parse_f64_result`, and +`parse_bool_result` plus source `match`. The matching explicit import +fixture, `examples/projects/std-import-env/`, now exercises the existing env +wrappers, typed parse success cases, and deterministic invalid/missing +fallback behavior. + +exp-65 does not add automatic standard-library imports, compiler-loaded +standard-library source, environment mutation, environment enumeration, +environment writes, rich host errors, generic configuration helpers, new +compiler-known `std.*` runtime names, stable ABI/layout/ownership, optimizer +guarantees, or beta maturity. + +## exp-64 (released experimental alpha) + +Release label: `exp-64` + +Release name: Standard Num Fallback Helpers Alpha + +Release date: 2026-05-20 + +Status: released experimental alpha stdlib/source contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_64_STANDARD_NUM_FALLBACK_HELPERS_ALPHA.md`. + +exp-64 extends `std/num.slo` with source-authored checked conversion +fallback helpers over the already promoted numeric result families: + +- `i64_to_i32_or` +- `f64_to_i32_or` +- `f64_to_i64_or` + +These are ordinary Slovo source functions over `i64_to_i32_result`, +`f64_to_i32_result`, and `f64_to_i64_result` plus source `match`. The +matching explicit import fixture, `examples/projects/std-import-num/`, now +exercises both the existing conversion wrappers and the new fallback helpers. + +exp-64 does not add automatic standard-library imports, compiler-loaded +standard-library source, unchecked narrowing conversions, saturating +conversions, generic numeric traits, new compiler-known `std.*` runtime +names, stable ABI/layout/ownership, optimizer guarantees, or beta maturity. + +## exp-63 (released experimental alpha) + +Release label: `exp-63` + +Release name: Standard Fs Fallback Helpers Alpha + +Release date: 2026-05-20 + +Status: released experimental alpha stdlib/source contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_63_STANDARD_FS_FALLBACK_HELPERS_ALPHA.md`. + +exp-63 extends `std/fs.slo` with source-authored text fallback/status +helpers over the already promoted filesystem result families: + +- `read_text_or` +- `write_text_ok` + +These are ordinary Slovo source functions over `read_text_result` and +`write_text_result` plus source `match`. The matching explicit import fixture, +`examples/projects/std-import-fs/`, now exercises both the existing fs +wrappers and the new fallback/status helpers. + +exp-63 does not add automatic standard-library imports, compiler-loaded +standard-library source, binary file APIs, directory traversal, streaming +file IO, async file IO, rich host errors, new compiler-known `std.*` runtime +names, stable ABI/layout/ownership, optimizer guarantees, or beta maturity. + +## exp-62 (released experimental alpha) + +Release label: `exp-62` + +Release name: Standard Env Fallback Helpers Alpha + +Release date: 2026-05-20 + +Status: released experimental alpha stdlib/source contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_62_STANDARD_ENV_FALLBACK_HELPERS_ALPHA.md`. + +exp-62 extends `std/env.slo` with source-authored environment fallback +helpers over the already promoted `(result string i32)` environment lookup +family: + +- `has` +- `get_or` + +These are ordinary Slovo source functions over `get_result` plus source +`match`. The matching explicit import fixture, +`examples/projects/std-import-env/`, now exercises both the existing env +wrappers and the new presence/fallback helpers. + +exp-62 does not add automatic standard-library imports, compiler-loaded +standard-library source, environment mutation, environment enumeration, +platform/configuration APIs, rich host errors, new compiler-known `std.*` +runtime names, stable ABI/layout/ownership, optimizer guarantees, or beta +maturity. + +## exp-61 (released experimental alpha) + +Release label: `exp-61` + +Release name: Standard Process Fallback Helpers Alpha + +Release date: 2026-05-20 + +Status: released experimental alpha stdlib/source contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_61_STANDARD_PROCESS_FALLBACK_HELPERS_ALPHA.md`. + +exp-61 extends `std/process.slo` with source-authored fallback helpers over +the already promoted `(result string i32)` process argument family: + +- `arg_or` +- `arg_or_empty` + +These are ordinary Slovo source functions over `arg_result` plus source +`match`. The matching explicit import fixture, +`examples/projects/std-import-process/`, now exercises both the existing +process wrappers and the new fallback helpers. + +exp-61 does not add automatic standard-library imports, compiler-loaded +standard-library source, option/flag parsing, subcommands, process spawning, +exit/status control, shell parsing, rich host errors, new compiler-known +`std.*` runtime names, stable ABI/layout/ownership, optimizer guarantees, or +beta maturity. + +## exp-60 (released experimental alpha) + +Release label: `exp-60` + +Release name: Standard String Fallback Helpers Alpha + +Release date: 2026-05-20 + +Status: released experimental alpha stdlib/source contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_60_STANDARD_STRING_FALLBACK_HELPERS_ALPHA.md`. + +exp-60 extends `std/string.slo` with source-authored fallback helpers over +the already promoted concrete parse result families: + +- `parse_i32_or_zero` +- `parse_i64_or_zero` +- `parse_f64_or_zero` +- `parse_bool_or_false` + +These are ordinary Slovo source functions over the existing `parse_*_result` +helpers plus source `match` on the concrete `i32`, `i64`, `f64`, and `bool` +result families. The matching explicit import fixture, +`examples/projects/std-import-string/`, now exercises both the direct parse +result wrappers and the new fallback helpers. + +exp-60 does not add automatic standard-library imports, compiler-loaded +standard-library source, generic parse helpers, whitespace trimming, +case-insensitive bool parsing, rich parse errors, new compiler-known +`std.*` runtime names, stable ABI/layout/ownership, optimizer guarantees, or +beta maturity. + +## exp-59 (released experimental alpha) + +Release label: `exp-59` + +Release name: Hosted Build Optimization And Benchmark Publication Alpha + +Release date: 2026-05-20 + +Status: released experimental alpha documentation/tooling contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_59_HOSTED_BUILD_OPTIMIZATION_AND_BENCHMARK_PUBLICATION_ALPHA.md`. + +exp-59 does not change the exp-58 source-language surface. It records the +paired Glagol exp-59 hosted-build optimization, refreshes the then-current +benchmark methodology and local benchmark evidence, and adds a repo-local PDF +rendering script for the whitepaper and manifest artifacts. + +exp-59 does not add source syntax, type forms, standard-library APIs, stable +ABI/layout/ownership, optimizer guarantees, benchmark thresholds, +cross-machine performance claims, package registry behavior, or beta +maturity. + +## exp-58 (released experimental alpha) + +Release label: `exp-58` + +Release name: Boolean Logic Alpha + +Release date: 2026-05-19 + +Status: released experimental alpha language contract. This is experimental +maturity and not a beta maturity claim. + +The normative release contract is `.llm/EXP_58_BOOLEAN_LOGIC_ALPHA.md`. + +exp-58 promotes `(and left right)`, `(or left right)`, and `(not value)` for +boolean values. `and` and `or` lower through existing `if` semantics, so they +short-circuit in the deterministic test runner and hosted lowering. + +The new supported fixture is `examples/supported/boolean-logic.slo`, with +matching formatter coverage in `examples/formatter/boolean-logic.slo`. + +exp-58 does not add truthiness, variadic boolean operators, pattern guards, +macro expansion, stable ABI/layout/ownership, optimizer guarantees, benchmark +thresholds, or beta maturity. + +## exp-57 (released experimental alpha) + +Release label: `exp-57` + +Release name: Integer Bitwise Alpha + +Release date: 2026-05-19 + +Status: released experimental alpha language/stdlib contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is `.llm/EXP_57_INTEGER_BITWISE_ALPHA.md`. + +exp-57 promotes explicit `bit_and`, `bit_or`, and `bit_xor` binary heads for +same-width signed integer operands: `i32` with `i32`, and `i64` with `i64`. + +The release also extends `std/math.slo` with `bit_and_i32`, `bit_or_i32`, +`bit_xor_i32`, `bit_and_i64`, `bit_or_i64`, and `bit_xor_i64`. + +The new supported fixture is `examples/supported/integer-bitwise.slo`, with +matching formatter coverage in `examples/formatter/integer-bitwise.slo`. + +exp-57 does not add shifts, bit-not, unsigned arithmetic, +bit-width-specific integer families, floating-point bitwise operations, +generic math, mixed numeric arithmetic, stable ABI/layout/ownership, +optimizer guarantees, benchmark thresholds, or beta maturity. + +## exp-56 (released experimental alpha) + +Release label: `exp-56` + +Release name: Integer Remainder Alpha + +Release date: 2026-05-19 + +Status: released experimental alpha language/stdlib contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is `.llm/EXP_56_INTEGER_REMAINDER_ALPHA.md`. + +exp-56 promotes the `%` binary operator for same-width signed integer +operands: `i32 % i32 -> i32` and `i64 % i64 -> i64`. The deterministic +fixture covers positive and negative dividends for both widths. + +The release also extends `std/math.slo` with `rem_i32`, `is_even_i32`, +`is_odd_i32`, `rem_i64`, `is_even_i64`, and `is_odd_i64`. + +The new supported fixture is `examples/supported/integer-remainder.slo`, with +matching formatter coverage in `examples/formatter/integer-remainder.slo`. + +exp-56 does not add floating-point remainder, Euclidean modulo, unsigned +arithmetic, bit operations, generic math, mixed numeric arithmetic, stable +ABI/layout/ownership, optimizer guarantees, benchmark thresholds, or beta +maturity. + +## exp-55 (released experimental alpha) + +Release label: `exp-55` + +Release name: Result F64 Bool Source Flow Alpha + +Release date: 2026-05-19 + +Status: released experimental alpha language/tooling contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_55_RESULT_F64_BOOL_SOURCE_FLOW_ALPHA.md`. + +exp-55 promotes source constructors and source `match` payload bindings for +the already existing concrete `(result f64 i32)` and `(result bool i32)` +families. It also lets `std/cli.slo` propagate missing argument indexes as +`err 1` for `arg_f64_result` and `arg_bool_result`. + +The new supported fixture is +`examples/supported/result-f64-bool-match.slo`. + +exp-55 does not add generic result types, result payload families beyond the +already promoted concrete families, generic `map`/`and_then`, exception +handling, automatic standard-library imports, stable ABI/layout/ownership, +optimizer guarantees, benchmark thresholds, or beta maturity. + +## exp-54 (released experimental alpha) + +Release label: `exp-54` + +Release name: Standard CLI Typed Arguments Alpha + +Release date: 2026-05-19 + +Status: released experimental alpha language/tooling contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_54_STANDARD_CLI_TYPED_ARGUMENTS_ALPHA.md`. + +exp-54 extends `std/cli.slo` with `i64`, `f64`, and `bool` typed argument +parse helpers while keeping the facade source-authored. The matching fixture +imports the expanded `std.cli` helper list explicitly without a local copied +module. + +`arg_i64_result` and `arg_i64_or_zero` propagate missing argument indexes +through current source-supported result constructors. `arg_f64_result` and +`arg_bool_result` return parse results over `std.process.arg`, so +out-of-range argument indexes still follow the existing trap behavior. + +exp-54 does not add automatic standard-library imports, a `std.slo` +aggregator, package registry behavior, lockfiles, package std dependencies, +new compiler-known runtime names, `err f64 i32` or `err bool i32` source +constructors, source `match` over `(result f64 i32)` or `(result bool i32)`, +shell parsing, option/flag parsing, subcommands, environment-backed +configuration, stable CLI framework APIs, stable ABI/layout/ownership, +optimizer guarantees, benchmark thresholds, or beta maturity. + +## exp-53 (released experimental alpha) + +Release label: `exp-53` + +Release name: Standard CLI Source Facade Alpha + +Release date: 2026-05-19 + +Status: released experimental alpha language/tooling contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_53_STANDARD_CLI_SOURCE_FACADE_ALPHA.md`. + +exp-53 adds `std/cli.slo`, a narrow source-authored CLI facade that composes +`std.process` and `std.string` source modules. The matching fixture imports +`std.cli` explicitly without a local copied module. + +The new fixture is `examples/projects/std-import-cli/`. + +exp-53 does not add automatic standard-library imports, a `std.slo` +aggregator, package registry behavior, lockfiles, package std dependencies, +new compiler-known runtime names, shell parsing, option/flag parsing, +subcommands, environment-backed configuration, stable CLI framework APIs, +stable ABI/layout/ownership, optimizer guarantees, benchmark thresholds, or +beta maturity. + +## exp-52 (released experimental alpha) + +Release label: `exp-52` + +Release name: Standard Process Facade Source Search Alpha + +Release date: 2026-05-19 + +Status: released experimental alpha language/tooling contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_52_STANDARD_PROCESS_FACADE_SOURCE_SEARCH_ALPHA.md`. + +exp-52 adds `std/process.slo`, a narrow source-authored facade over already +promoted process argument runtime calls. The matching fixture imports +`std.process` explicitly without a local copied module. + +The new fixture is `examples/projects/std-import-process/`. + +exp-52 does not add automatic standard-library imports, a `std.slo` +aggregator, package registry behavior, lockfiles, package std dependencies, +new compiler-known process runtime names, process spawning, exit/status +control, current-directory APIs, signal handling, stable ABI/layout/ownership, +optimizer guarantees, benchmark thresholds, or beta maturity. + +## exp-51 (released experimental alpha) + +Release label: `exp-51` + +Release name: Standard Library Path List Alpha + +Release date: 2026-05-19 + +Status: released experimental alpha language/tooling contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_51_STANDARD_LIBRARY_PATH_LIST_ALPHA.md`. + +exp-51 refines explicit standard-source discovery so `SLOVO_STD_PATH` may +name an ordered OS path list of standard-library roots. This allows an +override root to sit before an installed or checkout root without copying the +entire `std/` tree. + +exp-51 does not add automatic standard-library imports, a `std.slo` +aggregator, package registry behavior, lockfiles, package std dependencies, +semantic version solving, stable package manager behavior, broad +standard-library APIs, stable ABI/layout/ownership, optimizer guarantees, +benchmark thresholds, or beta maturity. + +## exp-50 (released experimental alpha) + +Release label: `exp-50` + +Release name: Installed Standard Library Discovery Alpha + +Release date: 2026-05-19 + +Status: released experimental alpha language/tooling contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_50_INSTALLED_STANDARD_LIBRARY_DISCOVERY_ALPHA.md`. + +exp-50 extends the explicit standard-source import contract to installed +toolchain discovery. Glagol may now discover staged `std/*.slo` files from an +installed `share/slovo/std` layout near the compiler executable, while keeping +`SLOVO_STD_PATH` and checkout discovery available. + +exp-50 does not add automatic standard-library imports, a `std.slo` +aggregator, package registry behavior, lockfiles, package std dependencies, +new standard-library modules, stable install layout guarantees, stable +ABI/layout/ownership, optimizer guarantees, benchmark thresholds, or beta +maturity. + +## exp-49 (released experimental alpha) + +Release label: `exp-49` + +Release name: Standard IO Facade Source Search Alpha + +Release date: 2026-05-19 + +Status: released experimental alpha language/tooling contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_49_STANDARD_IO_FACADE_SOURCE_SEARCH_ALPHA.md`. + +exp-49 extends explicit project-mode standard-library source search to +`std.io`. `std/io.slo` now carries an export list, and the matching fixture +imports the module without a local copy. + +The new fixture is `examples/projects/std-import-io/`. + +exp-49 does not add automatic standard-library imports, a `std.slo` +aggregator, package registry behavior, installed toolchain stdlib paths, new +compiler-known IO names, formatted output APIs, stable ABI/layout/ownership, +optimizer guarantees, benchmark thresholds, or beta maturity. + +## exp-48 (released experimental alpha) + +Release label: `exp-48` + +Release name: Standard Core Facade Source Search Alpha + +Release date: 2026-05-19 + +Status: released experimental alpha language/tooling contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_48_STANDARD_CORE_FACADE_SOURCE_SEARCH_ALPHA.md`. + +exp-48 extends explicit project-mode standard-library source search to the +pure source facade modules `std.string` and `std.num`. `std/string.slo` and +`std/num.slo` now carry export lists, and the matching fixtures import those +modules without local copies. + +The new fixtures are `examples/projects/std-import-string/` and +`examples/projects/std-import-num/`. + +exp-48 does not add automatic standard-library imports, a `std.slo` +aggregator, package registry behavior, installed toolchain stdlib paths, +generic parse/format APIs, broad numeric casts, stable ABI/layout/ownership, +optimizer guarantees, benchmark thresholds, or beta maturity. + +## exp-47 (released experimental alpha) + +Release label: `exp-47` + +Release name: Standard Host Facade Source Search Alpha + +Release date: 2026-05-19 + +Status: released experimental alpha language/tooling contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_47_STANDARD_HOST_FACADE_SOURCE_SEARCH_ALPHA.md`. + +exp-47 extends explicit project-mode standard-library source search to the +host facade modules `std.time`, `std.random`, `std.env`, and `std.fs`. +`std/time.slo`, `std/random.slo`, `std/env.slo`, and `std/fs.slo` now carry +export lists, and the matching fixtures import those modules without local +copies. + +The new fixtures are `examples/projects/std-import-time/`, +`examples/projects/std-import-random/`, `examples/projects/std-import-env/`, +and `examples/projects/std-import-fs/`. + +exp-47 does not add automatic standard-library imports, a `std.slo` +aggregator, workspace dependency syntax for std, package registry behavior, +installed toolchain stdlib paths, broad host APIs, stable ABI/layout/ownership, +optimizer guarantees, benchmark thresholds, or beta maturity. + +## exp-46 (released experimental alpha) + +Release label: `exp-46` + +Release name: Workspace Standard Source Search Alpha + +Release date: 2026-05-19 + +Status: released experimental alpha language/tooling contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_46_WORKSPACE_STANDARD_SOURCE_SEARCH_ALPHA.md`. + +exp-46 extends explicit standard-library source search from single projects to +workspace packages. The new fixture `examples/workspaces/std-import-option/` +imports `(import std.option (...))` without a local copied `option.slo`. + +exp-46 does not add automatic standard-library imports, workspace dependency +syntax for std, package registry behavior, installed toolchain stdlib paths, +a `std.slo` aggregator, generic option helpers, broad standard-library APIs, +stable ABI/layout/ownership, optimizer guarantees, benchmark thresholds, or +beta maturity. + +## exp-45 (released experimental alpha) + +Release label: `exp-45` + +Release name: Standard Result And Option Source Search Alpha + +Release date: 2026-05-19 + +Status: released experimental alpha language/tooling contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_45_STANDARD_RESULT_OPTION_SOURCE_SEARCH_ALPHA.md`. + +exp-45 extends the explicit standard-library source-search path to +`std.result` and `std.option`. Ordinary project code may import +`(import std.result (...))` and `(import std.option (...))`, and Glagol +resolves those imports to repo-root `std/result.slo` and `std/option.slo` +without local module copies. + +`std/result.slo` and `std/option.slo` now carry their own export lists. The +matching contract fixtures are `examples/projects/std-import-result/` and +`examples/projects/std-import-option/`. + +exp-45 does not add automatic standard-library imports, a `std.slo` +aggregator, package registry behavior, workspace/package `std` imports, +generic result helpers, generic option helpers, broad standard-library APIs, +stable ABI/layout/ownership, optimizer guarantees, benchmark thresholds, or +beta maturity. + +## exp-44 (released experimental alpha) + +Release label: `exp-44` + +Release name: Standard Library Source Search Alpha + +Release date: 2026-05-19 + +Status: released experimental alpha language/tooling contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_44_STANDARD_LIBRARY_SOURCE_SEARCH_ALPHA.md`. + +exp-44 promotes the first explicit standard-library source search path: +ordinary project code may import the staged math helpers with +`(import std.math (...))` and Glagol resolves that import to repo-root +`std/math.slo` without copying `math.slo` into the project. + +`std/math.slo` now carries its own export list so it can be imported as real +source. The matching contract fixture is +`examples/projects/std-import-math/`. + +exp-44 does not add automatic standard-library imports, a `std.slo` +aggregator, package registry behavior, workspace/package `std` imports, +broad standard-library APIs, stable ABI/layout/ownership, optimizer +guarantees, benchmark thresholds, or beta maturity. + +## exp-43 (released experimental alpha) + +Release label: `exp-43` + +Release name: Technical Whitepapers And Skills Alpha + +Release date: 2026-05-19 + +Status: released experimental alpha documentation/tooling contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_43_TECHNICAL_WHITEPAPERS_AND_SKILLS_ALPHA.md`. + +exp-43 publishes `docs/SLOVO_WHITEPAPER.md`, generated PDF publication +artifacts, and a repository-local beta language-design skill under +`.llm/skills/`. + +exp-43 does not add source syntax, type forms, standard-library APIs, stable +ABI/layout/ownership, optimizer guarantees, benchmark thresholds, +cross-machine performance claims, package registry behavior, or beta maturity. + +## exp-42 (released experimental alpha) + +Release label: `exp-42` + +Release name: Hot Loop Benchmark Mode Alpha + +Release date: 2026-05-19 + +Status: released experimental alpha tooling contract. This is experimental +maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_42_HOT_LOOP_BENCHMARK_MODE_ALPHA.md`. + +exp-42 keeps the Slovo language surface unchanged and pairs with Glagol +benchmark-suite gates that separate `cold-process` timing from +startup-amortized `hot-loop` timing. + +exp-42 does not add source syntax, type forms, standard-library APIs, stable +ABI/layout/ownership, high-resolution timing APIs, optimizer guarantees, +benchmark thresholds, cross-machine performance claims, package registry +behavior, or beta maturity. + +## exp-41 (released experimental alpha) + +Release label: `exp-41` + +Release name: Lisp Benchmark Comparison Alpha + +Release date: 2026-05-19 + +Status: released experimental alpha tooling contract. This is experimental +maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_41_LISP_BENCHMARK_COMPARISON_ALPHA.md`. + +exp-41 keeps the Slovo language surface unchanged and pairs with Glagol +benchmark-suite gates that compare two Lisp-family implementations: +Clojure and Common Lisp/SBCL. + +exp-41 does not add source syntax, type forms, standard-library APIs, stable +ABI/layout/ownership, optimizer guarantees, benchmark thresholds, +cross-machine performance claims, package registry behavior, or beta maturity. + +## exp-40 (released experimental alpha) + +Release label: `exp-40` + +Release name: Benchmark Suite And License Alpha + +Release date: 2026-05-19 + +Status: released experimental alpha governance/tooling contract. This is +experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_40_BENCHMARK_SUITE_AND_LICENSE_ALPHA.md`. + +exp-40 adds project licensing as `MIT OR Apache-2.0`, records alpha/beta +release naming criteria, and pairs with Glagol benchmark-suite gates for +Slovo/C/Rust/Python/Clojure local timing comparisons. + +exp-40 does not add source syntax, type forms, standard-library APIs, stable +ABI/layout/ownership, optimizer guarantees, benchmark thresholds, +cross-machine performance claims, package registry behavior, or beta maturity. + +## exp-39 (released experimental alpha) + +Release label: `exp-39` + +Release name: Standard Math Extensions And Benchmark Scaffold Alpha + +Release date: 2026-05-19 + +Status: released experimental alpha Slovo contract. Matching Glagol source +checking, formatter, benchmark scaffold, gates, and review workflow are +required before broader compiler-support claims. This is experimental maturity +and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_39_STANDARD_MATH_EXTENSIONS_AND_BENCHMARK_SCAFFOLD_ALPHA.md`. + +exp-39 extends staged `std/math.slo` with source-authored helpers for `i32`, +`i64`, and finite `f64`: `neg_*`, `cube_*`, `is_zero_*`, `is_positive_*`, +`is_negative_*`, and inclusive `in_range_*`. The existing exp-32 `abs`, +`min`, `max`, `clamp`, and `square` helpers remain. + +The matching Glagol release adds a local `math-loop` benchmark scaffold for +comparing Slovo executable timing against C, Rust, and Python on the same +machine. Those timings are local tooling evidence only, not performance +thresholds or optimizer guarantees. + +exp-39 does not add automatic `std` imports, repo-root `std/` search, +compiler-loaded standard-library source, new compiler-known `std.*` operation +names, trigonometry, `sqrt`, `pow`, modulo, bit operations, generic math, +overloads, traits, mixed numeric arithmetic, optimizer guarantees, benchmark +thresholds, stable standard-library APIs, stable ABI/layout/ownership, +manifest schema changes, or beta maturity. + +## exp-38 (released experimental alpha) + +Release label: `exp-38` + +Release name: Standard Host Source Facades Alpha + +Release date: 2026-05-19 + +Status: released experimental alpha Slovo contract. Matching Glagol source +checking, formatter, gates, and review workflow are required before broader +compiler-support claims. This is experimental maturity and not a beta maturity +claim. + +The normative release contract is +`.llm/EXP_38_STANDARD_HOST_SOURCE_FACADES_ALPHA.md`. + +exp-38 adds staged source facades over already released host/runtime calls: + +- `std/random.slo` with `random_i32 : () -> i32` and + `random_i32_non_negative : () -> bool` +- `std/env.slo` with `get : (string) -> string` and + `get_result : (string) -> (result string i32)` +- `std/fs.slo` with `read_text`, `read_text_result`, `write_text_status`, and + `write_text_result` + +The standard-library organization policy remains the exp-30 staged layout: +flat `std/*.slo` facades now, with a future `std.slo` aggregator/reexport layer +only after import/search semantics exist. This follows the Zig-like flat +standard facade policy adapted to current Slovo. + +exp-38 does not add automatic `std` imports, repo-root `std/` search, +compiler-loaded standard-library source, new compiler-known `std.*` operation +names, standard-runtime catalog entries, seed/range/bytes/floats/UUID/crypto +random APIs, environment mutation/enumeration, rich host error ADTs, +binary/directory/streaming/async filesystem APIs, stable standard-library APIs, +stable ABI/layout/ownership, manifest schema changes, or beta maturity. + +## exp-37 (released experimental alpha) + +Release label: `exp-37` + +Release name: Standard Time Source Facade Alpha + +Release date: 2026-05-19 + +Status: released experimental alpha Slovo contract. Matching Glagol source +checking, formatter, gates, and review workflow are required before broader +compiler-support claims. This is experimental maturity and not a beta maturity +claim. + +The normative release contract is +`.llm/EXP_37_STANDARD_TIME_SOURCE_FACADE_ALPHA.md`. + +exp-37 adds the staged `std/time.slo` source facade with narrow wrappers over +the already released exp-8 host time calls: + +- `monotonic_ms : () -> i32` +- `sleep_ms_zero : () -> i32` + +`monotonic_ms` returns `std.time.monotonic_ms`. `sleep_ms_zero` calls +`std.time.sleep_ms 0` and then returns `0`, because user-defined unit-return +functions are not generally supported today and positive-duration timing +assertions remain deferred. exp-37 adds no compiler-known runtime names and no +entries to `STANDARD_RUNTIME.md`. + +The standard-library organization policy remains the exp-30 staged layout: +flat `std/*.slo` facades now, with a future `std.slo` aggregator/reexport layer +only after import/search semantics exist. This follows the Zig-like flat +standard facade policy adapted to current Slovo. + +exp-37 does not add automatic `std` imports, repo-root `std/` search, +compiler-loaded standard-library source, wall-clock/calendar/timezone APIs, +high-resolution timers, async timers, cancellation, scheduling guarantees, new +compiler-known `std.*` operation names, stable standard-library APIs, stable +ABI/layout/ownership, manifest schema changes, or beta maturity. + +## exp-36 (released experimental alpha) + +Release label: `exp-36` + +Release name: Standard Option Source Helpers Alpha + +Release date: 2026-05-19 + +Status: released experimental alpha Slovo contract. Matching Glagol source +checking, formatter, gates, and review workflow are required before broader +compiler-support claims. This is experimental maturity and not a beta maturity +claim. + +The normative release contract is +`.llm/EXP_36_STANDARD_OPTION_SOURCE_HELPERS_ALPHA.md`. + +exp-36 adds the staged `std/option.slo` source facade with concrete helpers for +the already supported `(option i32)` family: + +- `is_some_i32` +- `is_none_i32` +- `unwrap_some_i32` +- `unwrap_or_i32` + +These are ordinary Slovo source functions over existing `is_some`, `is_none`, +`unwrap_some`, and `if` behavior. `unwrap_or_i32` returns the `some` payload +when present and otherwise returns the supplied fallback. exp-36 adds no +compiler-known runtime names and no entries to `STANDARD_RUNTIME.md`. + +The standard-library organization policy remains the exp-30 staged layout: +flat `std/*.slo` facades now, with a future `std.slo` aggregator/reexport layer +only after import/search semantics exist. This is inspired by Zig's +facade/reexport approach but adapted to current Slovo. + +exp-36 does not add automatic `std` imports, repo-root `std/` search, +compiler-loaded standard-library source, generic option helpers, option +payloads beyond `i32`, new compiler-known `std.*` operation names, stable +standard-library APIs, stable ABI/layout/ownership, manifest schema changes, +or beta maturity. exp-45 later promotes only explicit `std.option` source +search for this helper module. + +## exp-35 (released experimental alpha) + +Release label: `exp-35` + +Release name: Standard Result Bool Source Helpers Alpha + +Release date: 2026-05-19 + +Status: released experimental alpha Slovo contract. Matching Glagol source +checking, formatter, gates, and review workflow are required before broader +compiler-support claims. This is experimental maturity and not a beta maturity +claim. + +The normative release contract is +`.llm/EXP_35_STANDARD_RESULT_BOOL_SOURCE_HELPERS_ALPHA.md`. + +exp-35 extends the staged `std/result.slo` source file with concrete bool +helpers for the exp-34 returned `(result bool i32)` family: + +- `is_ok_bool` +- `is_err_bool` +- `unwrap_ok_bool` +- `unwrap_err_bool` +- `unwrap_or_bool` + +These are ordinary Slovo source functions over existing compiler-known +`std.result.is_ok`, `std.result.is_err`, `std.result.unwrap_ok`, and +`std.result.unwrap_err` support for the exp-34 bool parse result flow. +`unwrap_or_bool` returns the ok payload when present and otherwise returns the +supplied fallback. exp-35 adds no compiler-known runtime names and no entries +to `STANDARD_RUNTIME.md`. + +At exp-35, Slovo still did not promote automatic `std` imports, a repo-root +`std/` search path, compiler-loaded standard library source, source-authored +generic result helpers, source constructors for `(result bool i32)`, or +general source `match` over `(result bool i32)` beyond the +compiler-supported fixture flow. exp-45 later promotes only explicit +`std.result` source search for this helper module. + +exp-35 does not add supported/formatter example fixtures. It does not add +generic `std.result.map`, generic `std.result.unwrap_or`, +`std.result.and_then`, option helper names, broad result payload families, +automatic standard-library imports, replacement of compiler-known `std.*` +calls, new compiler-known `std.*` operation names, stable standard-library +APIs, stable ABI/layout/ownership, manifest schema changes, or beta maturity. + +## exp-34 (released experimental alpha) + +Release label: `exp-34` + +Release name: String Parse Bool Result Alpha + +Release date: 2026-05-19 + +Status: released experimental alpha Slovo contract. Matching Glagol +implementation, tests, gates, and review workflow are required before broader +compiler-support claims. This is experimental maturity and not a beta maturity +claim. + +The normative release contract is +`.llm/EXP_34_STRING_PARSE_BOOL_RESULT_ALPHA.md`. + +exp-34 promotes exactly one string parse standard-runtime call: + +- `std.string.parse_bool_result : (string) -> (result bool i32)` + +The call parses exactly complete ASCII lowercase `true` and `false`. Success +returns `ok true` or `ok false`; the empty string, uppercase or mixed-case +text, leading or trailing whitespace, numeric text, and any other text return +`err 1`. It does not trap for ordinary parse failure. + +The release adds byte-identical fixtures +`examples/supported/string-parse-bool-result.slo` and +`examples/formatter/string-parse-bool-result.slo`. + +exp-34 does not add generic parse APIs, trap-based parse, bool parsing beyond +exact lowercase `true`/`false`, string/bytes parse, whitespace trimming, +case-insensitive parsing, locale or Unicode boolean parsing, numeric boolean +parsing, rich parse errors, source-authored `std/result.slo` bool wrappers, +automatic `std` imports, compiler-loaded `std` source, stable helper +ABI/layout/ownership, manifest schema changes, or beta maturity. + +## exp-33 (released experimental alpha) + +Release label: `exp-33` + +Release name: Standard Result Source Helpers Alpha + +Release date: 2026-05-19 + +Status: released experimental alpha Slovo contract. Matching Glagol source +checking, formatter, gates, and review workflow are required before broader +compiler-support claims. This is experimental maturity and not a beta maturity +claim. + +The normative release contract is +`.llm/EXP_33_STANDARD_RESULT_SOURCE_HELPERS_ALPHA.md`. + +exp-33 extends the staged `std/result.slo` source file with source helpers for +already promoted concrete result families. It fills out missing +`is_err_i64`, `unwrap_err_i64`, `is_err_string`, `unwrap_err_string`, +`is_err_f64`, and `unwrap_err_f64` wrappers alongside the existing i32 +helpers, and adds concrete `unwrap_or_i32`, `unwrap_or_i64`, +`unwrap_or_string`, and `unwrap_or_f64` helpers. + +The numeric `unwrap_or_*` helpers are ordinary Slovo source functions +implemented with `std.result.is_ok`, `std.result.unwrap_ok`, `if`, +parameters, and explicit signatures. `unwrap_or_string` uses the existing +`(result string i32)` `match` shape to return the payload or fallback. +exp-33 adds no compiler-known runtime names and no entries to +`STANDARD_RUNTIME.md`. + +At exp-33, Slovo still did not promote automatic `std` imports, a repo-root +`std/` search path, or compiler-loaded standard library source. exp-45 later +promotes explicit `std.result` source search while keeping automatic imports +deferred. + +exp-33 does not add supported/formatter example fixtures. It does not add +generic `std.result.map`, generic `std.result.unwrap_or`, +`std.result.and_then`, option helper names, result payload families beyond the +already promoted concrete ones, automatic standard-library imports, replacement +of compiler-known `std.*` calls, new compiler-known `std.*` operation names, +stable standard-library APIs, stable ABI/layout/ownership, manifest schema +changes, or beta maturity. + +## exp-32 (released experimental alpha) + +Release label: `exp-32` + +Release name: Standard Math Source Helpers Alpha + +Release date: 2026-05-19 + +Status: released experimental alpha Slovo contract. Matching Glagol source +checking, formatter, gates, and review workflow are required before broader +compiler-support claims. This is experimental maturity and not a beta maturity +claim. + +The normative release contract is +`.llm/EXP_32_STANDARD_MATH_SOURCE_HELPERS_ALPHA.md`. + +exp-32 extends the staged `std/math.slo` source file with source-authored +helpers expressible in current Slovo: + +- `abs_i64`, `min_i64`, `max_i64`, `clamp_i64`, and `square_i64` +- `abs_f64`, `min_f64`, `max_f64`, and `clamp_f64` + +The existing exp-30 `i32` helpers and `square_f64` remain in place. The +helpers use existing same-type arithmetic/comparison, `if`, literals, calls, +and explicit signatures. They add no compiler-known runtime names and no +entries to `STANDARD_RUNTIME.md`. + +At exp-32, Slovo did not yet promote automatic `std` imports, a repo-root +`std/` search path, or compiler-loaded standard library source. exp-44 later +promotes the first explicit `std.math` source-search path while keeping +automatic imports deferred. + +exp-32 does not add supported/formatter example fixtures. It does not add +automatic standard-library imports, replacement of compiler-known `std.*` +calls, new compiler-known `std.*` operation names, a broad math library, +trigonometry, `sqrt`, `pow`, `f32`, unsigned or narrower integers, generics, +traits, overloads, mixed numeric arithmetic, numeric containers, stable +standard-library APIs, stable ABI/layout/ownership, manifest schema changes, +or beta maturity. + +## exp-31 (released experimental alpha) + +Release label: `exp-31` + +Release name: Checked F64 To I64 Result Alpha + +Release date: 2026-05-19 + +Status: released experimental alpha Slovo contract. Matching Glagol +implementation, tests, gates, and review workflow are required before broader +compiler-support claims. This is experimental maturity and not a beta maturity +claim. + +The normative release contract is +`.llm/EXP_31_CHECKED_F64_TO_I64_RESULT_ALPHA.md`. + +exp-31 promotes exactly one checked `f64` to `i64` result standard-runtime +call: + +- `std.num.f64_to_i64_result : (f64) -> (result i64 i32)` + +The call returns `ok value` only when the input is finite, exactly integral, +and within the signed `i64` range `-9223372036854775808` through +`9223372036854775807`. It returns `err 1` for non-finite, fractional, or +out-of-range input, without trapping for ordinary conversion failure. + +The release adds byte-identical fixtures +`examples/supported/f64-to-i64-result.slo` and +`examples/formatter/f64-to-i64-result.slo`, plus a `std/num.slo` wrapper over +the promoted compiler-known call. The out-of-range fixture value is the +formatter-stable conservative `9223372036854776000.0`; the accepted exact +`9223372036854775808.0` spelling formats to that value. Detailed edge behavior +near the `f64`/`i64` limits remains implementation-owned Glagol test coverage. + +exp-31 does not add unchecked casts, unchecked `f64` to `i64`, cast syntax, +generic `cast_checked`, `f32`, unsigned or narrower integer families, +additional numeric conversion families, mixed numeric arithmetic, broad math, +numeric containers, stable helper ABI/layout/ownership, manifest schema +changes, or beta maturity. + +## exp-30 (released experimental alpha) + +Release label: `exp-30` + +Release name: Standard Library Source Layout Alpha + +Release date: 2026-05-19 + +Status: released experimental alpha Slovo contract. Matching Glagol +implementation, tests, gates, and review workflow are required before broader +compiler-support claims. This is experimental maturity and not a beta maturity +claim. + +The normative release contract is +`.llm/EXP_30_STANDARD_LIBRARY_SOURCE_LAYOUT_ALPHA.md`. + +exp-30 establishes `std/` as the Slovo source home for staged standard library +modules and examples. It adds `std/README.md`, `std/io.slo`, +`std/string.slo`, `std/num.slo`, `std/result.slo`, and `std/math.slo`. + +`std/math.slo` contains narrow source-authored helpers expressible with the +current language: `abs_i32`, `min_i32`, `max_i32`, `clamp_i32`, +`square_i32`, and `square_f64`. The other `std/*.slo` files provide small +source wrappers over already promoted compiler-known standard-runtime calls +where current Slovo can express the wrapper cleanly. + +The release adds the conservative formatter example +`examples/formatter/std-source-layout-alpha.slo`. It demonstrates the helper +shape and layout contract without claiming repo-root `std` import/search +support. + +exp-30 does not add automatic standard-library imports, replace +compiler-known `std.*` calls with source implementations, add compiler-known +`std.*` operation names, change `STANDARD_RUNTIME.md`, add generics, traits, +overloads, package registry behavior, manifest schema changes, stable +standard-library APIs, stable ABI/layout/ownership, broad math, trigonometry, +`pow`, `sqrt`, `f32`, unsigned or narrower integers, mixed numeric +arithmetic, numeric containers, or beta maturity. + +## exp-29 (released experimental alpha) + +Release label: `exp-29` + +Release name: Numeric Struct Fields Alpha + +Release date: 2026-05-19 + +Status: released experimental alpha Slovo contract. Matching Glagol +implementation, tests, gates, and review workflow are required before broader +compiler-support claims. This is experimental maturity and not a beta maturity +claim. + +The normative release contract is +`.llm/EXP_29_NUMERIC_STRUCT_FIELDS_ALPHA.md`. + +exp-29 promotes direct immutable struct field declarations whose field types +are exactly `i64` or `f64`, alongside already supported direct `i32`, `bool`, +immutable `string`, and current enum fields. + +It supports struct constructors with `i64` and finite `f64` field +initializers, immutable value flow through locals, parameters, returns, and +calls, field access returning `i64` or `f64`, and use of accessed fields with +existing same-type arithmetic/comparison and existing numeric print/format +helpers where already supported. + +The release adds byte-identical fixtures +`examples/supported/numeric-struct-fields.slo` and +`examples/formatter/numeric-struct-fields.slo`. + +exp-29 keeps the exp-20 finite-only `f64` policy. It does not add struct field +mutation, nested structs, arrays/vectors/options/results as fields, enum +payload widening, numeric containers, `f32`, unsigned or narrower integer +families, mixed numeric arithmetic, implicit conversions, new parse/format +APIs, generic structs, methods, traits, stable ABI/layout/ownership, manifest +schema changes, FFI layout claims, or beta maturity. + +## exp-28 (released experimental alpha) + +Release label: `exp-28` + +Release name: String Parse F64 Result Alpha + +Release date: 2026-05-19 + +Status: released experimental alpha Slovo contract. Matching Glagol +implementation, tests, gates, and review workflow are required before broader +compiler-support claims. This is experimental maturity and not a beta maturity +claim. + +The normative release contract is +`.llm/EXP_28_STRING_PARSE_F64_RESULT_ALPHA.md`. + +exp-28 promotes exactly one string parse standard-runtime call: + +- `std.string.parse_f64_result : (string) -> (result f64 i32)` + +The call parses complete finite ASCII decimal `f64` text. Success returns +`ok value`; ordinary parse failure, non-finite text, trailing or leading +unsupported characters, and out-of-domain input return `err 1`. It does not +trap for ordinary parse or domain failure. + +The release adds byte-identical fixtures +`examples/supported/string-parse-f64-result.slo` and +`examples/formatter/string-parse-f64-result.slo`. + +exp-28 does not add generic parse, bool/string/bytes parse, locale parsing, +Unicode digit parsing, underscores, rich parse errors, stable helper +ABI/layout/ownership, result genericity, f64 containers, mixed numeric +arithmetic, manifest schema changes, or beta maturity. + +## exp-27 (released experimental alpha) + +Release label: `exp-27` + +Release name: Checked F64 To I32 Result Alpha + +Release date: 2026-05-19 + +Status: released experimental alpha Slovo contract. Matching Glagol +implementation, tests, gates, and review workflow are required before broader +compiler-support claims. This is experimental maturity and not a beta maturity +claim. + +The normative release contract is +`.llm/EXP_27_F64_TO_I32_RESULT_ALPHA.md`. + +exp-27 promotes exactly one checked `f64` to `i32` result standard-runtime +call: + +- `std.num.f64_to_i32_result : (f64) -> (result i32 i32)` + +The call returns `ok value` only when the input is finite, exactly integral, +and within the signed `i32` range `-2147483648` through `2147483647`. It +returns `err 1` for non-finite, fractional, or out-of-range input, without +trapping for ordinary conversion failure. + +The release adds byte-identical fixtures +`examples/supported/f64-to-i32-result.slo` and +`examples/formatter/f64-to-i32-result.slo`. + +exp-27 does not add unchecked `f64` to `i32`, casts or cast syntax, generic +`cast_checked`, `f32`, unsigned or narrower integer families, additional +numeric conversion families, `f64` parse, mixed numeric arithmetic, numeric +containers, stable helper ABI/layout/ownership, manifest schema changes, or +beta maturity. + +## exp-26 (released experimental alpha) + +Release label: `exp-26` + +Release name: F64 To String Alpha + +Release date: 2026-05-18 + +Status: released experimental alpha Slovo contract. Matching Glagol +implementation, tests, gates, and review workflow are required before broader +compiler-support claims. This is experimental maturity and not a beta maturity +claim. + +The normative release contract is `.llm/EXP_26_F64_TO_STRING_ALPHA.md`. + +exp-26 promotes exactly one finite `f64` formatting standard-runtime call: + +- `std.num.f64_to_string : (f64) -> string` + +The release adds byte-identical fixtures +`examples/supported/f64-to-string.slo` and +`examples/formatter/f64-to-string.slo`. + +exp-26 does not add `f32`, `f64` parse, generic format/display/interpolation, +locale/base/radix/grouping/padding/precision controls, stable NaN/infinity +text, implicit conversion, stable helper ABI/layout/ownership, manifest schema +changes, or beta maturity. + +## exp-25 (released experimental alpha) + +Release label: `exp-25` + +Release name: String Parse I64 Result Alpha + +Release date: 2026-05-18 + +Status: released experimental alpha Slovo contract. Matching Glagol +implementation, tests, gates, and review workflow are required before broader +compiler-support claims. This is experimental maturity and not a beta maturity +claim. + +The normative release contract is +`.llm/EXP_25_STRING_PARSE_I64_RESULT_ALPHA.md`. + +exp-25 promotes exactly one string parse standard-runtime call: + +- `std.string.parse_i64_result : (string) -> (result i64 i32)` + +The call parses an entire non-empty ASCII signed decimal `i64` string with an +optional leading `-`. Success returns `ok value`; parse failure or +out-of-range input returns `err 1`. It does not trap for ordinary parse or +range failure. + +The release also adds byte-identical fixtures +`examples/supported/string-parse-i64-result.slo` and +`examples/formatter/string-parse-i64-result.slo`. + +exp-25 does not add `f64` parse, generic parse APIs, a leading `+`, +whitespace trimming, underscores, base/radix prefixes, locale-aware parsing, +Unicode digit parsing, rich parse errors, stable helper ABI/layout/ownership, +manifest schema changes, or beta maturity. + +## exp-24 (released experimental alpha) + +Release label: `exp-24` + +Release name: Integer To String Alpha + +Release date: 2026-05-18 + +Status: released experimental alpha Slovo contract. Matching Glagol +implementation, tests, gates, and review workflow are required before broader +compiler-support claims. This is experimental maturity and not a beta maturity +claim. + +The normative release contract is +`.llm/EXP_24_INTEGER_TO_STRING_ALPHA.md`. + +exp-24 promotes exactly two integer formatting standard-runtime calls: + +- `std.num.i32_to_string : (i32) -> string` +- `std.num.i64_to_string : (i64) -> string` + +Each call returns a decimal signed ASCII string for the input value. Negative +values include a leading `-`; non-negative values have no leading `+`. The +operations are compiler/runtime-backed for now and may later be rewritten in +Slovo `std` source once the language can host them. + +The release also adds byte-identical fixtures +`examples/supported/integer-to-string.slo` and +`examples/formatter/integer-to-string.slo`. + +exp-24 does not add `f64` formatting, parse APIs, locale/base/radix/grouping +or padding controls, generic format/display traits, implicit conversions, +stable standard-library implementation source, stable helper ABI/layout and +ownership, manifest schema changes, or beta maturity. + +## exp-23 (released experimental alpha) + +Release label: `exp-23` + +Release name: Checked I64-To-I32 Conversion Alpha + +Release date: 2026-05-18 + +Status: released experimental alpha Slovo contract. Matching Glagol +implementation, tests, gates, and review workflow are required before broader +compiler-support claims. This is experimental maturity and not a beta maturity +claim. + +The normative release contract is +`.llm/EXP_23_CHECKED_I64_TO_I32_CONVERSION_ALPHA.md`. + +exp-23 promotes exactly one checked narrowing standard-runtime call: + +- `std.num.i64_to_i32_result : (i64) -> (result i32 i32)` + +The call returns `ok value` when the signed `i64` input is within the signed +`i32` range `-2147483648` through `2147483647`, and returns `err 1` when the +input is outside that range. It does not trap on range failure. + +The release also adds byte-identical fixtures +`examples/supported/checked-i64-to-i32-conversion.slo` and +`examples/formatter/checked-i64-to-i32-conversion.slo`. + +The operation is an explicit call returning the existing concrete +`(result i32 i32)` family. exp-23 does not add implicit numeric promotion, +cast syntax, `std.num.cast`, checked cast generics, mixed numeric operators, +any other narrowing conversion, `f64` conversions, unsigned or narrower +integer families, numeric parse/format APIs, standard-library implementation +promises, stable ABI/layout, manifest schema changes, or beta maturity. + +## exp-22 (released experimental alpha) + +Release label: `exp-22` + +Release name: Numeric Widening Conversions Alpha + +Release date: 2026-05-18 + +Status: released experimental alpha Slovo contract. Matching Glagol +implementation, tests, gates, and review workflow are required before broader +compiler-support claims. This is experimental maturity and not a beta maturity +claim. + +The normative release contract is +`.llm/EXP_22_NUMERIC_WIDENING_CONVERSIONS_ALPHA.md`. + +exp-22 promotes exactly three explicit standard-runtime numeric widening +calls: + +- `std.num.i32_to_i64 : (i32) -> i64` +- `std.num.i32_to_f64 : (i32) -> f64` +- `std.num.i64_to_f64 : (i64) -> f64` + +The release also adds byte-identical fixtures +`examples/supported/numeric-widening-conversions.slo` and +`examples/formatter/numeric-widening-conversions.slo`. + +These operations are explicit calls. exp-22 does not add implicit numeric +promotion, mixed `i32`/`i64`/`f64` arithmetic or comparison, narrowing +conversions, checked numeric conversion result APIs, numeric cast syntax, +`f32`, unsigned or narrower integer families, numeric parse/format APIs, +stable floating rounding or formatting guarantees, stable ABI/layout, manifest +schema changes, or beta maturity. + +## exp-21 (released experimental contract) + +Release label: `exp-21` + +Release name: I64 Numeric Primitive Alpha + +Release date: 2026-05-18 + +Status: released experimental Slovo contract. Matching Glagol implementation, +tests, gates, and review workflow are required before broader +compiler-support claims. This is experimental maturity and not a beta maturity +claim. + +The normative release contract is +`.llm/EXP_21_I64_NUMERIC_PRIMITIVE_ALPHA.md`. + +exp-21 allows direct `i64` value flow, explicit signed decimal `i64` literal +atoms, same-type arithmetic/comparison, and one print operation: + +```slo +(fn adjust ((value i64) (delta i64)) -> i64 + (+ value delta)) +``` + +It promotes: + +- direct `i64` function parameters and returns +- immutable `let` locals declared as `i64` +- explicit signed decimal `i64` literal atoms such as `2147483648i64`, + `42i64`, and `-7i64` +- calls passing and returning `i64` +- same-type `i64` `+`, `-`, `*`, and `/` +- same-type `i64` `=`, `<`, `>`, `<=`, and `>=` +- `std.io.print_i64` +- byte-identical fixtures + `examples/supported/i64-numeric-primitive.slo` and + `examples/formatter/i64-numeric-primitive.slo` + +Unsuffixed integer literals remain the existing `i32` surface, and `main` +continues to return `i32`. + +exp-21 does not add `f32`, unsigned integers, narrower integer widths beyond +existing `i32`, `char`, `bytes`, `decimal`, numeric casts, implicit promotion, +mixed `i32`/`i64`/`f64` arithmetic, generic numeric operators, i64 +arrays/vectors/options/results/enum payloads/struct fields, mutable `i64` +locals, `parse_i64`, random `i64`, broader math APIs, stable divide-by-zero or +overflow diagnostics, stable ABI/layout, manifest schema changes, or beta +maturity. + +## exp-20 (released experimental contract) + +Release label: `exp-20` + +Release name: F64 Numeric Primitive Alpha + +Release date: 2026-05-18 + +Status: released experimental Slovo contract. Matching Glagol implementation, +tests, gates, and review workflow are required before broader +compiler-support claims. This is experimental maturity and not a beta maturity +claim. + +The normative release contract is +`.llm/EXP_20_F64_NUMERIC_PRIMITIVE_ALPHA.md`. + +exp-20 allows direct `f64` value flow, same-type arithmetic/comparison, and +one print operation: + +```slo +(fn half ((value f64)) -> f64 + (/ value 2.0)) +``` + +It promotes: + +- direct `f64` function parameters and returns +- immutable `let` locals declared as `f64` +- decimal `f64` literals in `f64` contexts +- calls passing and returning `f64` +- same-type `f64` `+`, `-`, `*`, and `/` +- same-type `f64` `=`, `<`, `>`, `<=`, and `>=` +- `std.io.print_f64` +- byte-identical fixtures + `examples/supported/f64-numeric-primitive.slo` and + `examples/formatter/f64-numeric-primitive.slo` + +exp-20 does not add `f32`, `i64/u64/u32/u16/u8/i16/i8`, `char`, `bytes`, +`decimal`, numeric casts, implicit promotion, mixed `i32`/`f64` arithmetic, +generic numeric operators, f64 arrays/vectors/options/results/enum payloads/ +struct fields, mutable `f64` locals, `parse_f64`, random floats, broader math +library calls, stable NaN/infinity/rounding semantics, stable print text +formatting beyond newline-terminated output, stable ABI/layout, manifest +schema changes, or beta maturity. + +## exp-19 (released experimental contract) + +Release label: `exp-19` + +Release name: Primitive Struct Fields Alpha + +Release date: 2026-05-18 + +Status: released experimental Slovo contract. Matching Glagol implementation, +tests, gates, and review workflow are required before broader compiler-support +claims. This is experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_19_PRIMITIVE_STRUCT_FIELDS_ALPHA.md`. + +exp-19 allows direct `bool` and immutable `string` field types alongside +already supported direct `i32` fields and exp-18 direct enum fields: + +```slo +(struct PrimitiveRecord + (id i32) + (active bool) + (label string)) +``` + +It promotes: + +- struct field declarations using direct `bool` and immutable `string` + field types +- struct construction with `i32`, `bool`, and immutable `string` field values +- field access returning the declared primitive field type +- bool field access in existing predicate positions +- string field access in existing string equality and `std.string.len` +- immutable local, parameter, return, call, test, and `main` flow for structs + carrying primitive fields +- byte-identical fixtures + `examples/supported/primitive-struct-fields.slo` and + `examples/formatter/primitive-struct-fields.slo` + +exp-19 does not add arrays, vectors, options, or results containing +primitive-field structs, primitive fields inside containers, nested structs, +struct mutation, string mutation, ownership/cleanup guarantees beyond existing +string behavior, broader string operations, printing beyond existing calls, +generics, methods, traits, manifest schema changes, package/import widening, +stable ABI/layout, or beta maturity. + +## exp-18 (released experimental contract) + +Release label: `exp-18` + +Release name: Enum Struct Fields Alpha + +Release date: 2026-05-18 + +Status: released experimental Slovo contract. Matching Glagol implementation, +tests, gates, and review workflow are required before broader compiler-support +claims. This is experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_18_ENUM_STRUCT_FIELDS_ALPHA.md`. + +exp-18 allows current user-defined enum types as direct struct field types: + +```slo +(enum Status Ready Blocked) + +(enum Reading + Missing + (Value i32)) + +(struct TaggedReading + (status Status) + (reading Reading)) +``` + +It promotes: + +- struct field declarations using current enum type names directly +- struct construction with payloadless and unary `i32` enum field values +- field access returning the declared enum type +- same-enum equality on field access +- exhaustive enum `match` on field access +- immutable local, parameter, return, call, test, and `main` flow for structs + carrying enum fields +- byte-identical fixtures + `examples/supported/enum-struct-fields.slo` and + `examples/formatter/enum-struct-fields.slo` + +The enum surface is exactly exp-4 payloadless variants plus exp-16 unary +`i32` payload variants. exp-18 does not add payload types other than `i32`, +multiple payloads, record variants, tuple variants beyond one `i32`, arrays, +vectors, options, or results containing enum values, nested structs, struct +mutation, enum mutation, printing, ordering, hashing, reflection, stable +ABI/layout, manifest schema changes, import aliases/globs/re-exports, +registry/package-manager claims, generics, or beta maturity. + +## exp-17 (released experimental contract) + +Release label: `exp-17` + +Release name: Project Enum Imports Alpha + +Release date: 2026-05-18 + +Status: released experimental Slovo contract. Matching Glagol implementation, +tests, gates, and review workflow are required before broader compiler-support +claims. This is experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_17_PROJECT_ENUM_IMPORTS_ALPHA.md`. + +exp-17 extends the existing explicit project/workspace module export and +import lists so top-level user-defined enum names can cross local module +boundaries: + +```slo +(module readings (export Reading)) + +(import readings (Reading)) +``` + +It promotes imported enum type use in: + +- function signatures +- immutable locals +- calls and returns +- same-enum equality +- exhaustive enum matches +- qualified constructors + +The enum surface is exactly exp-4 payloadless variants plus exp-16 unary +`i32` payload variants. Payloadless constructors remain zero-argument +qualified constructors, and unary payload constructors still take exactly one +`i32` argument. + +The exp-17 project fixture is: + +```text +examples/projects/enum-imports/slovo.toml +examples/projects/enum-imports/src/readings.slo +examples/projects/enum-imports/src/main.slo +``` + +exp-17 does not add payload types other than `i32`, multiple payloads, record +variants, tuple variants beyond one `i32`, generics, containers, mutation, +printing, ordering, hashing, reflection, unqualified variant constructors, +stable ABI/layout, manifest schema changes, registry/package-manager claims, +or beta maturity. + +## exp-16 (released experimental contract) + +Release label: `exp-16` + +Release name: Unary I32 Enum Payloads Alpha + +Release date: 2026-05-18 + +Status: released experimental Slovo contract. Matching Glagol implementation, +tests, gates, and review workflow are required before broader compiler-support +claims. This is experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_16_UNARY_I32_ENUM_PAYLOADS_ALPHA.md`. + +exp-16 extends user-defined enums to allow payloadless variants plus unary +`i32` payload variants: + +```slo +(enum Reading + Missing + (Value i32) + (Offset i32)) +``` + +It promotes: + +- qualified unary payload constructors such as `(Reading.Value value)` +- payloadless constructors such as `(Reading.Missing)` unchanged +- enum `match` arms with payloadless patterns and one immutable arm-local + payload binding for payload variants +- immutable local, parameter, return, call, test, and `main` flow for enum + values +- same-enum equality where payload variants compare by tag and payload, and + payloadless variants compare by tag +- byte-identical fixtures + `examples/supported/enum-payload-i32.slo` and + `examples/formatter/enum-payload-i32.slo` + +exp-16 does not add payload types other than `i32`, multiple payloads, record +variants, tuple variants beyond one `i32`, string/bool/struct/enum payloads, +generic enums, generic functions, type aliases, traits, methods, derives, +wildcard/rest/nested enum patterns, pattern guards, enum payload mutation, +enum values inside arrays/structs/options/results/vectors, enum printing, +ordering, hashing, reflection, discriminants, stable ABI/layout, manifest +schema changes, runtime ABI/layout claims, moving option/result to ordinary +standard-library types, or beta maturity. + +## exp-15 (released experimental contract) + +Release label: `exp-15` + +Release name: Result Helper Standard Names Alpha + +Release date: 2026-05-18 + +Status: released experimental Slovo contract with matching Glagol +implementation, tests, gates, and review workflow. This is experimental +maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_15_RESULT_HELPER_STANDARD_NAMES_ALPHA.md`. + +exp-15 is a narrow result helper standard-name alpha: + +- promotes preferred source-level names `std.result.is_ok`, + `std.result.is_err`, `std.result.unwrap_ok`, and `std.result.unwrap_err` +- accepts those names only for `(result i32 i32)` and `(result string i32)` +- keeps legacy unqualified `is_ok`, `is_err`, `unwrap_ok`, and `unwrap_err` + documented as compatibility syntax where already supported +- reuses existing result observer and trap-based unwrap semantics +- adds byte-identical fixtures + `examples/supported/result-helpers.slo` and + `examples/formatter/result-helpers.slo` +- keeps `slovo.artifact-manifest` schema version `1` + +exp-15 does not add `std.result.map`, `std.result.unwrap_or`, +`std.result.and_then`, option helper standard names, new payload families, +generic result, user-defined error payloads, runtime ABI/layout claims, +manifest schema changes, enum payloads, or beta maturity. + +## exp-14 (released experimental contract) + +Release label: `exp-14` + +Release name: Standard Runtime Conformance Alignment + +Release date: 2026-05-18 + +Status: released experimental conformance alignment after matching Glagol +exp-14 implementation, tests, focused conformance gate, full release gate, and +review passed. This is experimental maturity and not a beta maturity claim. + +The normative release contract is +`.llm/EXP_14_STANDARD_RUNTIME_CONFORMANCE_ALIGNMENT.md`. + +exp-14 is a conformance/readiness alignment seed over the released exp-13 +surface: + +- catalogs every promoted compiler-known `std.*` operation through exp-13 in + `STANDARD_RUNTIME.md` +- keeps the supported fixture inventory explicit +- adds byte-identical canonical fixtures for the already released exp-8 time + operations: + `examples/supported/time-sleep.slo` and + `examples/formatter/time-sleep.slo` +- requires Slovo/Glagol supported and formatter fixtures to stay byte-aligned + where matching files exist +- requires a fresh-project workflow covering `new`, `check`, `fmt --check`, + `test`, `doc`, and `build` where the hosted toolchain exists +- requires an integrated workflow over already released features: modules/ + imports, tests, strings, `std.string.concat`, + `std.string.parse_i32_result`, result `match`, `(vec i32)`, enum `match`, + and `std.io.print_i32` +- keeps the release gate strict: diff checks, `cargo fmt --check`, + `cargo test`, ignored promotion gate, binary smoke, LLVM smoke, and review + `PASS` + +The exp-14 release gate passed with Slovo/Glagol diff checks, +`cargo fmt --check`, `cargo test`, ignored promotion gate, binary smoke, LLVM +smoke, focused conformance gate, and review `PASS`. + +exp-14 adds no new source syntax, type forms, runtime APIs, compiler-known +`std.*` names, standard library functions, manifest schema version, +ABI/layout promise, runtime headers/libraries, or beta maturity. + +## exp-13 (released experimental contract) + +Release label: `exp-13` + +Release date: 2026-05-18 + +Status: released experimental compiler support after matching Glagol exp-13 +implementation, tests, review, and full release gate coverage passed. This is +not a beta maturity claim. + +The normative release contract is +`.llm/EXP_13_STRING_PARSE_I32_RESULT_ALPHA.md`. + +exp-13 is a narrow string parse i32 result alpha: + +- `std.string.parse_i32_result: (string) -> (result i32 i32)` +- parses the entire input string as ASCII decimal signed `i32` +- accepts an optional leading `-` and at least one ASCII digit +- success returns `(ok i32 i32 value)` +- empty input, non-digits, plus signs, whitespace, trailing bytes, + non-ASCII digits, and out-of-range values return `(err i32 i32 1)` +- only ordinary parse error code `err 1` is promised +- the operation composes structurally with + `std.io.read_stdin_result` by matching the stdin result and parsing only the + `ok` string payload + +Release fixtures: + +```text +examples/supported/string-parse-i32-result.slo +examples/formatter/string-parse-i32-result.slo +``` + +exp-13 does not add trap-based `std.string.parse_i32`, parsing floats, bools, +strings, or bytes, whitespace/locale/base-prefix/underscore/plus-sign parsing, +generic parse APIs, parse error messages or richer codes, Unicode digit +parsing, string indexing or slicing, tokenizer/scanner APIs, stdin line APIs, +stable helper ABI/layout, manifest schema fields, or beta maturity. + +## exp-12 (released experimental contract) + +Release label: `exp-12` + +Release date: 2026-05-18 + +Status: released experimental compiler support after matching Glagol exp-12 +implementation, tests, review, and release gate coverage passed. This is not a +beta maturity claim. + +The normative release contract is +`.llm/EXP_12_STDIN_RESULT_ALPHA.md`. + +exp-12 is a narrow standard input result alpha: + +- `std.io.read_stdin_result: () -> (result string i32)` +- reads remaining standard input as text +- success returns `(ok string i32 text)` +- ordinary EOF with no bytes is success with the empty string +- ordinary host/input failure returns `(err string i32 1)` +- only ordinary failure code `err 1` is promised +- Glagol test-runner behavior may use an implementation-owned fixed input + string as `ok` +- existing exp-3 and exp-10 host calls stay unchanged + +Release fixtures: + +```text +examples/supported/stdin-result.slo +examples/formatter/stdin-result.slo +``` + +exp-12 does not add trap-based `std.io.read_stdin`, line iteration, prompt +APIs, terminal mode, binary stdin, streaming, async, encoding/Unicode promises +beyond existing string bytes, stable helper ABI/layout, manifest schema +changes, or beta maturity. + +## exp-11 (released experimental contract) + +Release label: `exp-11` + +Release date: 2026-05-18 + +Status: released experimental compiler support after matching Glagol exp-11 +implementation, tests, review, and release gate coverage passed. This is not a +beta maturity claim. + +The normative release contract is +`.llm/EXP_11_BASIC_RANDOMNESS_ALPHA.md`. + +exp-11 is a narrow basic randomness alpha: + +- `std.random.i32: () -> i32` +- returns a non-negative implementation-owned pseudo-random or host-random + `i32` +- suitable only for basic CLI use +- deterministic-enough Glagol test-runner behavior through an + implementation-owned non-negative sample or sequence +- runtime/host inability to produce a value traps exactly as + `slovo runtime error: random i32 unavailable` + +Release fixtures: + +```text +examples/supported/random.slo +examples/formatter/random.slo +``` + +exp-11 does not claim seed APIs, cryptographic/security suitability, bytes +APIs, ranges or bounds arguments, floats, random strings, UUIDs, broad +`std.random.*`, randomness-specific manifest fields, stable ABI/layout/helper +symbols, or beta maturity. + +## exp-10 (released experimental contract) + +Release label: `exp-10` + +Release date: 2026-05-18 + +Status: released experimental compiler support after matching Glagol exp-10 +implementation, tests, review, and release gate coverage passed. This is not a +beta maturity claim. + +The normative release contract is +`.llm/EXP_10_RESULT_BASED_HOST_ERRORS_ALPHA.md`. + +exp-10 is a narrow result-based host errors alpha: + +- concrete `(result string i32)` only +- `std.process.arg_result: (i32) -> (result string i32)` +- `std.env.get_result: (string) -> (result string i32)` +- `std.fs.read_text_result: (string) -> (result string i32)` +- `std.fs.write_text_result: (string string) -> (result i32 i32)` +- ordinary host failure as `err 1` +- exp-3 calls unchanged + +Release fixtures: + +```text +examples/supported/host-io-result.slo +examples/formatter/host-io-result.slo +``` + +exp-10 does not claim general host error ADTs, `result string Error`, +platform-specific codes, error messages, result equality/printing/mapping, +broader host APIs, stable ABI/layout/helper symbols, manifest schema changes, +or beta maturity. + +## exp-9 (released experimental contract) + +Release label: `exp-9` + +Release date: 2026-05-18 + +Status: released experimental compiler support after matching Glagol exp-9 +implementation, tests, review, and release gate coverage passed. This is not a +beta maturity claim. + +The normative release contract is +`.llm/EXP_9_RELIABILITY_PERFORMANCE_ECOSYSTEM_HARDENING.md`. + +exp-9 is hardening-only over the released exp-8 baseline. It requires +reliability, performance, and ecosystem gates for the existing promoted +experimental surface: property/fuzz-style parser, formatter, diagnostic, +project graph, and runtime API tests where feasible; a source-reachable panic +audit; benchmark fixtures or documented benchmark smoke; compatibility +inventory for promoted experimental features; and a migration guide from +`v2.0.0-beta.1` and exp releases. + +exp-9 adds no source language syntax, type-system behavior, standard-runtime +names, package features, manifest schema versions, stable ABI/layout promises, +public performance guarantees, or beta maturity. + +## exp-8 (released experimental contract) + +Release label: `exp-8` + +Release date: 2026-05-18 + +Status: released experimental compiler support after matching Glagol exp-8 +implementation, tests, review, and release gate coverage passed. This is not a +beta maturity claim. + +## Summary + +exp-8 narrows the broad concurrency and long-running programs roadmap category +to host time and sleep alpha for long-running command-line programs: + +- compiler-known `std.time.monotonic_ms: () -> i32` +- compiler-known `std.time.sleep_ms: (i32) -> unit` +- host monotonic elapsed milliseconds with implementation-owned epoch +- valid `sleep_ms 0` behavior +- negative sleep durations rejected or trapped with + `slovo runtime error: sleep_ms negative duration` +- deterministic test-runner handling for `sleep_ms 0` +- conservative structural or non-negative host-value test-runner support for + `monotonic_ms` +- standard-runtime time usage recorded in artifact manifests only if existing + manifest patterns already support standard-runtime usage metadata + +The normative release contract is `.llm/EXP_8_HOST_TIME_SLEEP_ALPHA.md`. +exp-8 adds no source language syntax and does not claim threads, channels, +async, cancellation, actors, shared memory, data-race freedom, scheduling, +timers, wall-clock/calendar/timezone APIs, high-resolution timers, signal +handling, stable ABI/layout, or stable runtime helper symbols. + +## exp-7 (released experimental contract) + +Release label: `exp-7` + +Release date: 2026-05-18 + +Status: released experimental compiler support after matching Glagol exp-7 +implementation, tests, review, and release gate coverage. This is not a beta +maturity claim. + +## Summary + +exp-7 is deliberately limited to test selection and richer test-run metadata: + +- `glagol test --filter ` +- legacy `glagol --run-tests --filter ` while Glagol + keeps the legacy route +- deterministic substring selection against test display names +- selected tests execute in the existing deterministic order +- non-selected discovered tests are skipped and counted +- zero matches is a successful run with explicit discovered/selected/passed/ + failed/skipped counts +- additive text-output and artifact-manifest test-run metadata + +The normative contract is `.llm/EXP_7_TEST_SELECTION_ALPHA.md`. exp-7 adds no +source language syntax and does not claim LSP, debug metadata, source maps, +SARIF, watch/daemon protocols, documentation comments, lint categories, or +benchmarks. + +## exp-6 (released experimental contract) + +Release label: `exp-6` + +Release date: 2026-05-18 + +Status: released experimental compiler support after matching Glagol exp-6 +implementation, tests, review, and release gate coverage. This is not a beta +maturity claim. + +## Summary + +exp-6 is deliberately limited to C FFI scalar imports: + +- top-level imported C function declarations such as + `(import_c c_add ((lhs i32) (rhs i32)) -> i32)` +- `i32`-only scalar parameters and `i32` or internal builtin `unit` returns +- a conservative C symbol name subset matching the Slovo import name +- lexical `(unsafe ...)` required at every imported C call site +- explicit local C source linking through `glagol build --link-c ` +- artifact-manifest recording of `foreign_imports` and `c_link_inputs` +- an interpreter-style test-runner diagnostic when a test reaches an imported + C call + +The normative contract is `.llm/EXP_6_C_FFI_SCALAR_IMPORTS_ALPHA.md`. The +minimal fixture target is `examples/ffi/exp-6-c-add/`. + +## exp-6 Deferrals + +exp-6 does not support pointer types, allocation/deallocation, raw unsafe head +execution, ownership or lifetime rules, C exports, callbacks, headers, +libraries, broad linker configuration, foreign globals, foreign structs, +unions, enums, layout annotations, stable ABI, or stable layout. + +## exp-5 (released experimental contract) + +Release label: `exp-5` + +Release date: 2026-05-18 + +Status: released experimental compiler support after matching Glagol exp-5 +implementation, tests, review, and release gate coverage. This is not a beta +maturity claim. + +## Summary + +exp-5 is deliberately limited to local packages and workspace hygiene: + +- workspace `slovo.toml` manifests with explicit local member lists +- package `slovo.toml` manifests with package name/version metadata +- local path dependencies between workspace members +- deterministic package graph ordering +- explicit package-qualified imports for dependency modules +- package graph recording in artifact manifests +- diagnostics for missing package, dependency cycle, duplicate package name, + path escape, invalid package name/version, and private visibility across + package boundaries + +The normative contract is `.llm/EXP_5_LOCAL_PACKAGES_ALPHA.md`. The minimal +fixture target is `examples/workspaces/exp-5-local/`. + +## exp-5 Deferrals + +exp-5 does not support remote registries, version solving, lockfiles beyond +deterministic local graph recording, build scripts, generated code, published +packages, semver compatibility promises, macros, package-level generics, +cross-package ABI stability, package re-exports, package archives, package +publishing, registry authentication, feature flags, optional dependencies, dev +dependencies, or target dependencies. + +## exp-4 (released experimental contract) + +Release label: `exp-4` + +Release date: 2026-05-18 + +Status: released experimental compiler support after matching Glagol exp-4 +implementation, tests, review, and release gate coverage. This is not a beta +maturity claim. + +## Summary + +exp-4 narrows user-defined data types to payloadless enums only: + +```slo +(enum Name VariantA VariantB) +``` + +Variant values are constructed through zero-argument qualified calls such as +`(Name.VariantA)`. Enum values may flow through immutable locals, parameters, +returns, calls, equality with `=`, and top-level tests. + +Enum `match` requires exhaustive payloadless variant arms: + +```slo +(match value + ((Name.VariantA) + body...) + ((Name.VariantB) + body...)) +``` + +Arms have one or more expression bodies and a common final result type, +following the existing option/result match rule. + +## Runtime And Layout + +The runtime/backend representation is compiler-owned. exp-4 makes no stable +layout, discriminant value, ABI, helper-symbol, reflection, or conversion to +`i32` promise. + +## Fixtures + +exp-4 adds these current compiler-supported fixtures: + +```text +examples/supported/enum-basic.slo +examples/formatter/enum-basic.slo +``` + +They are current exp-4 fixtures. + +## exp-4 Deferrals + +exp-4 does not support payload variants, tuple variants, record variants, +generic enums, generic functions, type aliases, traits, interfaces, protocols, +methods, derives, variant payload binding, wildcard patterns, rest patterns, +guards, nested patterns, enum values in arrays, enum values in struct fields, +enum values in options, enum values in results, enum values in vectors, enum +mutation, enum printing, enum ordering, enum hashing, reflection, explicit +discriminants, enum constructors with arguments, unqualified variant +constructors, stable ABI/layout, stable helper symbols, or moving +option/result to ordinary standard-library types. + +## exp-3 (released experimental contract) + +Release label: `exp-3` + +Release date: 2026-05-17 + +Status: released experimental compiler support after matching Glagol exp-3 +implementation, tests, review, and release gate coverage. This is not a beta +maturity claim. + +## Summary + +exp-3 narrows standard IO and host environment support to exactly six +compiler-known functions: + +```text +std.io.eprint: (string) -> unit +std.process.argc: () -> i32 +std.process.arg: (i32) -> string +std.env.get: (string) -> string +std.fs.read_text: (string) -> string +std.fs.write_text: (string string) -> i32 +``` + +`std.io.eprint` writes to stderr without adding a newline. `std.process.argc` +returns the process argument count, and `std.process.arg` returns a zero-based +argument string. `std.env.get` returns the environment variable value, with a +missing variable returning the empty string in this stage. +`std.fs.read_text` reads a whole text file, and `std.fs.write_text` writes a +whole string to a path. + +## Runtime Behavior + +Out-of-range process argument access traps with: + +```text +slovo runtime error: process argument index out of bounds +``` + +Host file read failure traps with: + +```text +slovo runtime error: file read failed +``` + +Both messages are written to stderr with a trailing newline and exit with code +`1`. + +`std.fs.write_text` returns `0` on success and `1` on host failure for this +stage. + +## Fixture Targets + +exp-3 adds these current compiler-supported fixture targets: + +```text +examples/supported/host-io.slo +examples/formatter/host-io.slo +``` + +They are current exp-3 fixtures. + +## exp-3 Deferrals + +exp-3 does not support networking, async IO, binary file APIs, directory +traversal, terminal control, platform abstraction, general host error ADTs, +`result string Error`, package interaction, stdin full read or line iteration, +randomness, time, stable ABI/layout, or stable runtime helper symbols. + +## exp-2 (released experimental contract) + +Release label: `exp-2` + +Release date: 2026-05-17 + +Status: released experimental compiler support after matching Glagol exp-2 +implementation, tests, review, and release gate coverage. This is not a beta +maturity claim. + +## Summary + +exp-2 narrows collections to one concrete growable vector type: + +```text +(vec i32) +``` + +The only promoted operations are compiler-known standard-runtime names: + +```text +std.vec.i32.empty: () -> (vec i32) +std.vec.i32.append: ((vec i32), i32) -> (vec i32) +std.vec.i32.len: ((vec i32)) -> i32 +std.vec.i32.index: ((vec i32), i32) -> i32 +``` + +`std.vec.i32.append` returns a new immutable runtime-owned vector and does not +mutate its input. `(vec i32)` values may flow through immutable locals, +parameters, returns, calls, and top-level tests. Vector equality with `=` +compares lengths and `i32` elements in order. + +## Runtime Behavior + +Allocation failure traps with: + +```text +slovo runtime error: vector allocation failed +``` + +Index failure traps with: + +```text +slovo runtime error: vector index out of bounds +``` + +Both messages are written to stderr with a trailing newline and exit with code +`1`. Vector cleanup is compiler/runtime-owned and not source-visible. + +## Fixture Targets + +exp-2 adds these current compiler-supported fixture targets: + +```text +examples/supported/vec-i32.slo +examples/formatter/vec-i32.slo +``` + +They are current exp-2 fixtures. + +## exp-2 Deferrals + +exp-2 does not support generic vectors, element types other than `i32`, vector +mutation, vector `var` or `set`, vector literals beyond empty/append +construction, a `push` alias, nested vectors, vectors in arrays, structs, +options, or results, iterators, slices, maps, sets, user-visible deallocation, +stable ABI/layout/helper symbols, packages, or IO expansion. + +## exp-1 (released experimental contract) + +Release label: `exp-1` + +Release date: 2026-05-17 + +Status: current experimental compiler support after matching Glagol exp-1 +implementation, tests, review, and release gate coverage. + +## Summary + +exp-1 is the first narrow step toward the future `1.0.0-beta` general-purpose +language threshold. It promotes one heap-created string operation: + +```slo +(std.string.concat left right) +``` + +The exact signature is `(string, string) -> string`. The result is an +immutable runtime-owned `string`. Existing string equality, `std.string.len`, +`std.io.print_string`, string locals, parameters, returns, and calls returning +`string` apply to runtime-owned strings as they do to literal-backed strings. + +No legacy `string_concat` alias is introduced. `std.string.concat` is a +compiler-known standard-runtime name, not a user module, import, package +dependency, foreign function, or stable C ABI symbol. + +## Runtime Behavior + +Allocation failure traps with: + +```text +slovo runtime error: string allocation failed +``` + +The message is written to stderr with a trailing newline and exits with code +`1`. Runtime-owned string cleanup is compiler/runtime-owned and not +source-visible. + +## Fixture Targets + +exp-1 adds these current compiler-supported fixture targets: + +```text +examples/supported/owned-string-concat.slo +examples/formatter/owned-string-concat.slo +``` + +They are current exp-1 fixtures. + +## exp-1 Deferrals + +exp-1 does not support mutable strings, string containers, string indexing or +slicing, interpolation, formatting APIs, Unicode scalar/grapheme/display-width +APIs, user-visible allocation or deallocation, file IO, environment variables, +process arguments, time, randomness, packages, workspaces, generics, +overloading, user-defined standard modules, growable collections, raw-memory +execution, FFI, stable ABI, or stable layout. + +## v2.0.0-beta.1 + +Release tag: `v2.0.0-beta.1` + +Release date: 2026-05-17 + +## Summary + +Slovo `v2.0.0-beta.1` is an experimental integration/readiness release based +on v1.7. It does not add new source language syntax or semantics unless that +behavior is already implemented and gate-proven in Glagol. + +Experimental readiness means Slovo is usable for small real flat local +projects with: + +- `glagol new` +- `glagol check` +- `glagol fmt --check` +- `glagol fmt --write` +- `glagol test` +- `glagol build` +- `glagol doc` +- artifact manifests +- JSON diagnostics +- the release-gate script + +## Experimental-Supported Surface + +The experimental language surface is the accumulated v1.1-v1.7 surface: +`i32`, `bool`, builtin internal `unit`, top-level tests, locals, `if`, +`while`, structs, fixed direct scalar arrays with checked indexing, string +value flow, option/result values and the exact v1.4 match slice, +standard-runtime alpha names, and the v1.6 lexical unsafe boundary with +reserved unsafe heads. + +Basic IO is exactly `std.io.print_i32`, `std.io.print_string`, +`std.io.print_bool`, and `std.string.len`. There is no file IO, environment +variable IO, process argument IO, process control, or time/clock API in this +experimental release. + +## Experimental Deferrals + +Vectors and growable collections are deferred until future beta work unless +separately implemented and gate-proven in Glagol. Fixed direct scalar arrays +with checked indexing satisfy the experimental gate because this is an +integration/readiness release for small flat local projects; growable +collections require allocation, ownership/lifetime, mutation, capacity, +diagnostics, lowering, runtime tests, and documentation that remain future beta +work. + +Slovo `v2.0.0-beta.1` also makes no stable ABI/layout, FFI, raw-memory +execution, LSP, debug metadata, source-map, file/env/process/time IO, or +package registry promise. + +## v1.7 + +Release tag: `v1.7` + +Release date: 2026-05-17 + +## Summary + +Slovo v1.7 is a conservative developer experience hardening release over the +v1.6 language contract. It adds tooling contracts only and does not promote new +source language syntax, type-system behavior, runtime semantics, standard +runtime names, ABI/layout guarantees, LSP support, stable debug metadata, or +stable source maps. + +v1.7 promotes contracts for: + +- `glagol new [--name ]` +- `glagol fmt --check ` +- `glagol fmt --write ` +- `glagol doc -o ` +- a local release-gate script + +## Project Scaffolding + +`glagol new` creates a valid v1.3-style project containing `slovo.toml` and +`src/main.slo`. The generated main module must be small, testable, and limited +to already-supported language forms. + +The command must fail without overwriting non-empty target directories. + +## Formatter Modes + +Plain `glagol fmt ` remains stdout formatting and does not write the +input file. + +`glagol fmt --check ` checks canonical formatting without +writing. `glagol fmt --write ` writes canonical formatting for +one file or for all immediate `.slo` project source modules in deterministic +module order. + +## Documentation Generator + +`glagol doc -o ` generates deterministic Markdown from +current source structure: modules, imports/exports, structs, functions, and +tests. It is a documentation generator, not a semantic reflection API. + +## Release Gate + +The repo must provide a script or documented command entry point that runs the +full v1 release gate locally without network, tag, push, or release-publication +side effects. + +## v1.7 Deferrals + +Slovo v1.7 does not support new language syntax or semantics, package +management, dependency management, stdin formatting, recursive directory +formatting, LSP, SARIF, watch mode, daemon protocols, debug adapters, stable +debug metadata, DWARF, stable standalone source-map files, semantic reflection, +runtime reflection, stable ABI, or stable layout. + +## v1.6 + +Release tag: `v1.6` + +Release date: 2026-05-17 + +## Summary + +Slovo v1.6 is a conservative memory and unsafe design slice over the v1.5 +contract. It does not promote raw memory, pointers, allocation, deallocation, +unchecked indexing, reinterpretation, or FFI execution. + +Lexical `(unsafe ...)` remains the only unsafe boundary. v1.6 reserves these +compiler-known unsafe operation heads: + +- `alloc` +- `dealloc` +- `load` +- `store` +- `ptr_add` +- `unchecked_index` +- `reinterpret` +- `ffi_call` + +Safe code using one of these heads must receive `UnsafeRequired` before normal +call lookup. Code inside `(unsafe ...)` using one of these heads must receive +`UnsupportedUnsafeOperation` until a future release defines syntax, type +rules, lowering, runtime behavior, diagnostics, and tests. + +User functions, imports, exports, and parameters cannot shadow the reserved +unsafe heads. Such declarations must be rejected with a structured +duplicate/reserved-name diagnostic. + +## Memory Direction + +v1.6 keeps safe values as the default Slovo memory model. Future memory work is +staged toward affine ownership plus explicit unsafe regions, but v1.6 does not +freeze ownership, pointer/reference types, allocator behavior, aliasing, +cleanup, or unsafe-region escape rules. + +No stable ABI, layout, FFI, allocator, ownership, or raw-memory execution +promise is made in v1.6. + +## Supported Fixture Changes + +v1.6 adds no new supported source fixture because no raw unsafe operation is +promoted. The supported unsafe fixture remains: + +```text +examples/supported/unsafe.slo +``` + +The speculative raw-memory example remains speculative: + +```text +examples/speculative/unsafe-memory.slo +``` + +## v1.6 Deferrals + +Slovo v1.6 does not support pointer types, raw allocation, raw deallocation, +pointer load/store, pointer arithmetic, unchecked indexing, raw +reinterpretation, FFI calls, foreign symbol execution, stable object/library or +header output, stable C ABI symbols, stable runtime layout, allocator APIs, +ownership transfer, or safe abstractions over unsafe operations. + +## v1.5 + +Release tag: `v1.5` + +Release date: 2026-05-17 + +## Summary + +Slovo v1.5 is a conservative standard library alpha over the v1.4 contract. It +promotes stable source-level standard-runtime names for existing runtime +behavior without adding new data types, IO breadth, imports, packages, +allocation, Unicode length semantics, or ABI promises. + +The matching Glagol v1.5 release implements this contract. v1.5 promotes: + +- `std.io.print_i32` +- `std.io.print_string` +- `std.io.print_bool` +- `std.string.len` + +These are compiler-known v1.5 standard-runtime names. They do not require +imports, do not name user modules, do not create package dependencies, and are +not stable C ABI symbols. + +Legacy `print_i32`, `print_string`, `print_bool`, and `string_len` remain +compatibility aliases. New examples prefer the promoted `std.*` names. + +## Standard Runtime Alpha + +The promoted forms are: + +```slo +(std.io.print_i32 value) +(std.io.print_string value) +(std.io.print_bool value) +(std.string.len value) +``` + +`std.io.print_i32` accepts exactly one `i32`, returns builtin `unit`, and keeps +the same stdout behavior as legacy `print_i32`. + +`std.io.print_string` accepts exactly one `string`, returns builtin `unit`, and +keeps the same stdout behavior and source string-literal constraints as legacy +`print_string`. + +`std.io.print_bool` accepts exactly one `bool`, returns builtin `unit`, and +keeps the same stdout behavior as legacy `print_bool`. + +`std.string.len` accepts exactly one `string`, returns `i32`, and keeps the +same decoded-byte count semantics as legacy `string_len`. + +## Diagnostics + +Unknown or unpromoted `std.*` calls must produce structured diagnostics. The +suggested diagnostic code is `UnsupportedStandardLibraryCall`. + +Arity and type mismatches for promoted `std.*` calls use the existing +arity/type diagnostics where applicable. Promoted `std.*` names are reserved +from user function/export shadowing. + +## Supported Fixture Additions + +v1.5 adds these Slovo fixtures: + +```text +examples/supported/standard-runtime.slo +examples/formatter/standard-runtime.slo +``` + +## v1.5 Deferrals + +Slovo v1.5 does not support `std.io.print_unit`, file IO, environment +variables, process arguments, time or clocks, vectors/maps/sets or other +collections, allocation, deallocation, ownership, user-defined standard +modules, imports or packages for standard-runtime names, overloading, generic +standard-library APIs, Unicode scalar/grapheme/display-width string length, +stable C ABI symbols, runtime layout promises, or FFI contracts. + +## v1.4 + +Release tag: `v1.4` + +Release date: 2026-05-17 + +## Summary + +Slovo v1.4 is a conservative core-language expansion over the v1.3 +project-mode contract. It promotes source-level `match` only for existing +`(option i32)` and `(result i32 i32)` values. It does not add user-defined +enums/ADTs, generic payloads, mutation, vectors, broader numeric types, +ownership, or layout/ABI promises. + +Assuming the matching Glagol v1.4 release implements this contract, v1.4 +promotes: + +- `match` expressions over `(option i32)` and `(result i32 i32)` +- exhaustive option arms: `some` and `none` +- exhaustive result arms: `ok` and `err` +- immutable arm payload binding for `some`, `ok`, and `err` +- one-or-more expression arm bodies whose final expression is the arm value +- common arm result typing for the whole match expression +- named diagnostics for match subject, payload, exhaustiveness, duplicate arm, + malformed pattern, arm type, binding collision, mutation, and container + boundaries + +Existing `is_some`, `is_none`, `is_ok`, `is_err`, `unwrap_some`, `unwrap_ok`, +and `unwrap_err` remain supported. New examples prefer `match` when control +flow depends on the payload. + +## Match Syntax + +Option matches use: + +```slo +(match value + ((some payload) + payload) + ((none) + 0)) +``` + +Result matches use: + +```slo +(match value + ((ok payload) + payload) + ((err code) + code)) +``` + +Payload bindings are immutable and scoped only to the selected arm. Arm bodies +may contain one or more expressions. The final expression is the arm value, and +all arms must produce the same result type. + +## Supported Fixture Additions + +v1.4 adds these Slovo fixtures: + +```text +examples/supported/option-result-match.slo +examples/formatter/option-result-match.slo +``` + +## v1.4 Deferrals + +Slovo v1.4 does not support user-defined enums or algebraic data types, +generic option/result payloads, non-`i32` payload matching, nested +option/result payloads beyond current v1 limits, options/results in arrays or +structs, option/result mutation, vectors, struct mutation, `i64`, general +`(block ...)` expressions outside match arms, pattern guards, wildcard/rest +patterns, destructuring structs/arrays/strings, standard-library error +conventions, user-catchable exceptions, ownership promises, stable layout, or +stable ABI. + +## v1.3 + +Release tag: `v1.3` + +Release date: 2026-05-17 + +## Summary + +Slovo v1.3 is a project-mode and local-modules release over the v1.2 language +and toolchain contract. It does not add packages, dependencies, remote +registries, version solving, workspaces, macros, public ABI, cross-package +visibility, incremental builds, watch/LSP, path escapes, or generated code. + +Assuming the matching Glagol v1.3 release implements this contract, v1.3 +promotes: + +- a project manifest named `slovo.toml` +- a flat local source-root convention, defaulting to `src` +- project mode for `glagol check`, `glagol test`, and `glagol build` +- explicit imports between local project modules +- explicit exports of top-level `fn` and `struct` names +- deterministic project/module ordering +- duplicate-name, missing-import, import-cycle, ambiguous-name, and visibility + diagnostics +- multi-file source spans and project artifact-manifest fields + +Single-file mode remains unchanged when a command receives one `.slo` file. + +## Project Manifest + +The v1.3 manifest is exactly `slovo.toml`: + +```toml +[project] +name = "example" +source_root = "src" +entry = "main" +``` + +`name` is required and must be a lowercase package-shaped identifier. +`source_root` defaults to `src` and must stay under the project root. `entry` +defaults to `main` and must be a flat module identifier. Unknown manifest +sections or keys are diagnostics in v1.3. + +## Project Layout And Modules + +Project module files are immediate `.slo` children of the source root: + +```text +. +├── slovo.toml +└── src + ├── main.slo + └── math.slo +``` + +Each file declares one flat module whose name matches the file stem. A module +with exports uses: + +```slo +(module math (export add_one)) +``` + +The only import form is: + +```slo +(import math (add_one)) +``` + +Only exported top-level `fn` and `struct` declarations are importable. Tests, +locals, parameters, fields, builtins, intrinsics, generated declarations, and +imported names are not exportable or importable. + +## Project Commands + +Project mode is selected only when the input is a directory containing +`slovo.toml` or the `slovo.toml` file itself: + +```text +glagol check +glagol test +glagol build -o +``` + +`check` validates every project module. `test` checks the project and runs +top-level tests in deterministic module and source order. `build` checks the +project, requires the entry module and supported `main` function, and produces +one hosted native executable through the existing LLVM IR plus Glagol runtime C +plus Clang path. + +## Supported Fixture Additions + +v1.3 adds this Slovo project fixture: + +```text +examples/projects/basic/slovo.toml +examples/projects/basic/src/main.slo +examples/projects/basic/src/math.slo +``` + +exp-17 later adds this project enum-import fixture: + +```text +examples/projects/enum-imports/slovo.toml +examples/projects/enum-imports/src/readings.slo +examples/projects/enum-imports/src/main.slo +``` + +## v1.3 Deferrals + +Slovo v1.3 does not support packages, dependencies, remote registries, version +solving, lockfiles, workspaces, hierarchical modules, import aliases, glob +imports, qualified names, re-exports, macros, public ABI, cross-package +visibility, incremental builds, watch mode, LSP, SARIF, path escapes, +generated code, project formatting, build profiles, cross-compilation, object +output, library output, header output, stable debug metadata, DWARF, or +standalone source maps. + +## v1.2 + +Release tag: `v1.2` + +Release date: 2026-05-17 + +## Summary + +Slovo v1.2 is a practical runtime values release over the v1.1 single-file +toolchain contract. It does not add project mode, package management, imports, +multi-file modules, stable ABI/layout promises, or a standard runtime. + +Assuming the matching Glagol v1.2 release implements this contract, v1.2 +promotes: + +- immutable string `let` locals +- string parameters and returns +- calls returning strings +- string equality with `=` +- string byte length with `string_len` +- top-level tests using string equality or string length comparisons +- temporary compiler/runtime intrinsic `print_bool` +- stable runtime trap messages and process exit behavior for array bounds and + option/result unwrap failures + +## Supported Fixture Additions + +v1.2 adds these Slovo fixtures: + +```text +examples/supported/string-value-flow.slo +examples/supported/print-bool.slo +examples/formatter/string-value-flow.slo +examples/formatter/print-bool.slo +``` + +No `print_unit` fixture is added because unit printing remains unsupported. + +## Runtime String Contract + +Strings remain immutable compiler/runtime values. Every promoted string value +originates from a source string literal or from propagating such a value through +an immutable local, parameter, return, or call result. + +`string_len` returns the decoded byte count before the trailing NUL byte. String +equality compares decoded bytes before the trailing NUL byte. v1.2 does not +promise Unicode normalization, grapheme counting, display width, arbitrary byte +escapes, embedded NUL bytes, allocation, ownership, deallocation, stable ABI, or +FFI behavior. + +String concatenation remains deferred in v1.2 because it requires allocation, +ownership, lifetime, and deallocation semantics. exp-1 later stages only the +narrow `std.string.concat` contract. + +## Printing And Unit + +`print_bool` has exactly one `bool` argument, returns builtin `unit`, and writes +`true\n` or `false\n`. + +`print_unit` remains unsupported. This keeps `unit` as an internal builtin +result type for supported unit-producing body forms rather than making it a +user-visible, storable, comparable, or test-final value. + +## Runtime Errors + +Runtime traps are process-terminating runtime errors, not user-catchable Slovo +exceptions. Required stderr messages are: + +```text +slovo runtime error: array index out of bounds +slovo runtime error: unwrap_some on none +slovo runtime error: unwrap_ok on err +slovo runtime error: unwrap_err on ok +``` + +Each message is written with a trailing newline. The process exits with code +`1`. `glagol test` reports a top-level test that triggers one of these runtime +errors as a trapped test and exits with code `1`. + +## v1.2 Deferrals + +Slovo v1.2 does not support mutable string locals, string assignment, string +concatenation, string indexing or slicing, strings inside arrays/structs/ +options/results, broader owned or heap strings, user-defined string runtime bindings, +stable string ABI/layout, `print_unit`, user-visible `unit` values, project +mode, imports, packages, or multi-file modules. exp-1 later stages only +`std.string.concat` as a runtime-owned string operation. + +## v1.1 + +Release tag: `v1.1` + +Release date: 2026-05-17 + +## Summary + +Slovo v1.1 is a tooling release over the v1 language contract. It does not add +new source language features, broaden the supported fixture set, or relax any +v1 unsupported boundary. + +Assuming the matching Glagol v1.1 release implements the Slovo v1.1 toolchain +contract, the stable user-facing tool surface is: + +```text +glagol check +glagol fmt +glagol test +glagol build -o +``` + +These commands are single-file commands in v1.1. Project mode, package +management, multi-file modules, stdin formatting, write-in-place formatting, +test filtering, cross-compilation, optimization profiles, stable ABI promises, +object/library output, C headers, FFI, LSP, SARIF, and daemon protocols remain +deferred. + +## Toolchain Contract + +v1.1 productizes the existing v1 language through a predictable CLI contract: + +- `check` parses, lowers, and type-checks one Slovo source file without + producing primary artifacts. +- `fmt` prints canonical formatted source for one file to stdout and does not + modify the input file. +- `test` runs top-level Slovo tests in one file and reports source failures, + failed tests, and reported runtime traps as ordinary command failures. +- `build` produces a hosted native executable for the current machine through + the Glagol LLVM IR plus runtime C plus Clang pipeline. +- `--json-diagnostics` requests newline-delimited JSON diagnostics on stderr. +- `--manifest ` requests a `slovo.artifact-manifest` version `1` + manifest describing the actual invocation outputs. +- `--no-color`, `--help`, and `--version` are common command flags. + +Machine diagnostics remain schema `slovo.diagnostic` version `1`. Artifact +manifests remain schema `slovo.artifact-manifest` version `1`. + +## Stable Exit Codes + +Glagol v1.1 commands use this stable table: + +```text +0 success +1 source parse, lowering, type, formatter, test failure, or unsupported form +2 command-line usage error +3 required toolchain component missing or unusable +4 internal compiler error +5 manifest or output artifact write failure +``` + +A failed Slovo test is exit code `1`. A missing hosted build dependency such as +Clang is exit code `3`. Panics or impossible compiler states on supported +inputs are release blockers and map to exit code `4` only as a containment +path. + +## Compatibility Aliases + +The canonical documented commands are `check`, `fmt`, `test`, and `build`. + +Glagol may retain pre-v1.1 developer commands or flags as compatibility aliases +only when they route through the same diagnostics, manifest, and exit-code +paths as the canonical commands and do not change semantics. Slovo v1.1 does +not treat those aliases as primary user documentation, and exact alias names +remain Glagol release evidence rather than new Slovo language surface. + +## Native Build Boundary + +`glagol build -o ` is a hosted native build path, not a +stable systems ABI promise. It proves that supported v1 programs can build and +run as direct executables on the current host using documented Glagol runtime C +inputs and the host Clang/linker environment. + +Required v1.1 native build smoke coverage includes `add`, `while`, array value +flow, option payload flow, result payload flow, struct value flow, and string +print. + +## Release Gates + +Slovo-side gate: + +```text +git -C slovo diff --check +``` + +Glagol-side gates for the matching v1.1 release include the baseline test and +format gates, CLI tests for all canonical commands, stable exit-code tests, +JSON diagnostic snapshots, artifact manifest snapshots, hosted native build +smokes, fixture-alignment checks, and release-note review for aliases and +deferrals. + +No `v1.1` tag should be cut while required gates are failing, skipped, or +blocked by review. + +## v1 + +Release tag: `v1` + +Release date: 2026-05-17 + +## Summary + +Slovo v1 extends the v0 kernel with practical value flow while preserving the +manifest rule: a feature is supported only when the language contract, fixtures, +formatter behavior, diagnostics, tests, and Glagol implementation agree. + +v1 keeps native executable output, stable ABI/layout promises, broad runtime +bindings, raw memory, and FFI out of scope. The supported compiler path remains +Slovo source through Glagol to textual LLVM IR, with native execution available +through the explicit LLVM-plus-runtime Clang workflow. + +## Supported Fixture Set + +These examples define the promoted Slovo v1 surface: + +```text +examples/supported/add.slo +examples/supported/top-level-test.slo +examples/supported/local-variables.slo +examples/supported/if.slo +examples/supported/while.slo +examples/supported/struct.slo +examples/supported/struct-value-flow.slo +examples/supported/array.slo +examples/supported/array-value-flow.slo +examples/supported/array-direct-scalars.slo +examples/supported/array-direct-scalars-value-flow.slo +examples/supported/array-string.slo +examples/supported/array-string-value-flow.slo +examples/supported/option-result.slo +examples/supported/option-result-flow.slo +examples/supported/option-result-payload.slo +examples/supported/string-print.slo +examples/supported/unsafe.slo +``` + +Formatter fixtures under `examples/formatter/` define canonical v1 layout, +including the promoted full-line comment stability fixture +`examples/formatter/comments.slo`. + +Frozen v0 compatibility fixtures remain under `examples/compat/v0/`. + +## Promoted v1 Language Surface + +- Struct value flow: immutable struct locals, struct parameters, struct + returns, calls returning structs, and field access through stored values. +- Fixed array value flow: immutable positive-length arrays over the current + direct scalar families `i32`, `i64`, `f64`, `bool`, plus `string`, with + immutable array locals, parameters, returns, calls returning arrays, and + runtime-checked dynamic indexing on `i32` expressions. +- Option/result value flow for `(option i32)` and `(result i32 i32)`: immutable + locals, parameters, calls returning option/result values, tag observation, and + explicit trap-based `i32` payload extraction with `unwrap_some`, `unwrap_ok`, + and `unwrap_err`. +- Narrow string runtime slice: direct source string literals passed to + temporary compiler/runtime intrinsic `print_string`. +- Lexical `unsafe` remains the accepted unsafe boundary, without raw memory or + FFI operations. + +### Runtime String Slice + +- Promotes the Slovo-side contract for immutable source string literals passed + directly to temporary compiler/runtime intrinsic `(print_string value)`. +- Defines the first runtime representation as a borrowed immutable + NUL-terminated byte pointer to compiler-emitted static storage, with no user + ownership, mutation, allocation, free, stable ABI, or broader runtime binding + promise. +- Adds `examples/supported/string-print.slo` and + `examples/formatter/string-print.slo`. + +Still deferred at that stage: string locals, string function parameters or +returns, string equality/comparison, strings inside arrays, structs, options, +or results, slices, concatenation, length, indexing, ownership, user-defined +string runtime bindings, and stable string ABI promises. Current exp-19 later +promotes only direct immutable `string` struct fields. + +## Tooling Contracts + +- Machine diagnostics use `slovo.diagnostic` version `1` with a root + `(diagnostic ...)` form. +- Artifact manifests use `slovo.artifact-manifest` version `1` for compiler + outputs and test-report side channels. +- Formatter output is stable for promoted formatter fixtures, preserves accepted + full-line comment positions, rejects unsupported comment/layout positions + with structured diagnostics, and does not promise width-based reflow. +- Glagol keeps golden diagnostic fixtures for current explicitly rejected v1 + boundaries and textual lowering-inspector golden fixtures for promoted v1 + feature families. +- Stable LLVM debug metadata, DWARF, standalone source-map files, JSON tooling + output, direct native output, package layout, and stable ABI/layout remain + deferred. + +## Explicit Deferrals + +Slovo v1 does not support: + +- struct field/value mutation +- full option/result matching, mapping, equality, printing, nested values, + non-`i32` payloads, containers, or user-catchable payload traps +- bool or unit printing, user-declared/stored `unit`, or broader standard + runtime printing +- full string value flow, ownership, slicing, indexing, equality/comparison, or + user-defined runtime bindings +- array mutation, arrays of unsupported element types, zero-length arrays, + array equality, printing arrays, nested arrays, unchecked indexing, or + stable array layout +- pointer types, allocation, deallocation, load/store, pointer arithmetic, + reinterpretation, raw memory operations, or FFI +- JSON diagnostics or lowering-inspector output +- macros, generics, package management, concurrency, direct machine-code + backend, stable ABI, or stable layout + +## v0 + +Release tag: `v0` + +Release date: 2026-05-16 + +Release commit: `d7a60ce` + +## Summary + +Slovo v0 is the first released language contract. It turns the initial +manifest, design laws, and compiler experiments into a strict supported subset +with matching Glagol implementation coverage. + +The purpose of v0 is not broad application development. The purpose is to +establish a clean, compiler-backed kernel that can be parsed, formatted, +checked, tested, inspected, lowered, and emitted as LLVM IR. + +## Supported Fixture Set + +These examples define the compiler-supported Slovo v0 surface: + +```text +examples/supported/add.slo +examples/supported/top-level-test.slo +examples/supported/local-variables.slo +examples/supported/if.slo +examples/supported/while.slo +examples/supported/struct.slo +examples/supported/array.slo +examples/supported/option-result.slo +examples/supported/unsafe.slo +``` + +Formatter fixtures live in `examples/formatter/` and define canonical layout +for the same strict supported syntax. + +Frozen copies of the v0 supported and formatter fixtures live under +`examples/compat/v0/` for post-v0 compatibility checks. + +Examples under `examples/speculative/` are design targets, not supported v0 +compiler fixtures. + +## Supported Language Surface + +Slovo v0 supports: + +- `(module name)` +- top-level `(fn ...)` +- top-level `(test "name" body... final-expression)` +- top-level `(struct Name (field i32)...)` +- explicit `i32` parameters and `i32` returns +- direct `(option i32)` and `(result i32 i32)` constructor returns +- integer literals and parameter references +- binary `+` +- equality comparison `=` +- ordering comparison `<` +- user function calls +- temporary compiler intrinsic `print_i32` +- local `i32` bindings with `let` and `var` +- mutable local assignment with `set` +- value-producing `if` +- first-pass `while` as a non-final sequential body form +- first-pass struct construction and immediate field access +- first-pass fixed `i32` array construction +- immutable fixed `i32` array locals +- literal checked indexing +- first-pass option/result constructors +- lexical `unsafe` expression blocks containing otherwise supported safe forms + +## Compiler Contract + +The matching compiler release is Glagol v0. Its implementation-facing surface is +the `glagol` binary CLI. + +Glagol can: + +- emit LLVM IR for supported programs +- format supported source +- print parsed trees +- inspect surface lowering +- inspect checked lowering +- check top-level tests +- run top-level tests +- emit structured diagnostics for unsupported or invalid source-reachable forms + +Native executable output is not part of the Slovo v0 language contract. v0 uses +the explicit Glagol LLVM IR plus Clang runtime-linking workflow. + +## Explicit Deferrals + +Slovo v0 does not support: + +- runtime string values +- dynamic array indices +- runtime array bounds traps +- array parameters, returns, or mutation +- slices +- option/result locals, calls as values, matching, unwrap, equality, or printing +- struct locals, parameters, returns, field mutation, nested fields, recursive + structs, or methods +- pointer types +- raw allocation and deallocation +- pointer load/store +- pointer arithmetic +- unchecked indexing behavior +- reinterpretation +- FFI +- unsafe abstractions +- stable ABI or layout promises +- macros +- generics +- package management +- ownership or affine resources +- concurrency +- direct machine-code backend + +## Post-v0 Direction + +Post-v0 work starts in `SPEC-v1.md` and `.llm/V1_ROADMAP.md`. + +The first recommended v1 slice is struct value flow: struct locals, parameters, +returns, and field access through stored struct values. This slice should be +specified before implementation and must preserve the v0 support rule. diff --git a/docs/language/ROADMAP.md b/docs/language/ROADMAP.md new file mode 100644 index 0000000..8d49845 --- /dev/null +++ b/docs/language/ROADMAP.md @@ -0,0 +1,3298 @@ +# Slovo Roadmap + +This roadmap tracks the language contract. Glagol, in `../glagol`, tracks the +compiler implementation. + +Guiding rule: the manifest wins. A feature is not accepted until it has surface syntax, typed-core meaning, lowering behavior, formatter behavior, diagnostics, and tests. + +Long-horizon planning lives in +`.llm/GENERAL_PURPOSE_LANGUAGE_ROADMAP.md`. It defines the experimental +release train from the historical `v2.0.0-beta.1` tag toward and beyond the +first real general-purpose beta Slovo contract. + +Current stage: `1.0.0-beta`, released on 2026-05-21 as the first real +general-purpose beta Slovo contract. The beta release integrates the completed +unsigned `u32`/`u64` numeric and staged-stdlib breadth scope from `exp-125` +with the already promoted project/package workflow, explicit std-source +imports, concrete vector families, fixed arrays, structs, enums, result/ +option, generated docs, formatter behavior, and structured diagnostics. + +The final experimental precursor scope is `exp-125`, defined in +`.llm/EXP_125_UNSIGNED_U32_U64_NUMERIC_AND_STDLIB_BREADTH_ALPHA.md`. Its +unsigned direct-value flow, parse/format runtime lanes, and matching staged +stdlib helper breadth are now absorbed into `1.0.0-beta`. + +The last tagged experimental alpha before beta is `exp-124`, defined in +`.llm/EXP_124_FIXED_ARRAY_ENUM_AND_STRUCT_ELEMENTS_ALPHA.md`. + +The previous released experimental alpha contract is `exp-123`, defined in +`.llm/EXP_123_OWNED_VECTOR_BENCHMARK_SUITE_EXTENSION_AND_WHITEPAPER_REFRESH_ALPHA.md`, +for one connected benchmark/publication refresh. It keeps the exp-121 +language surface unchanged while refreshing the current whitepaper baseline +and the documented nine-kernel benchmark suite around `math-loop`, +`branch-loop`, `parse-loop`, `array-index-loop`, `string-eq-loop`, +`array-struct-field-loop`, `enum-struct-payload-loop`, +`vec-i32-index-loop`, and `vec-string-eq-loop`, with local-machine evidence +only, no threshold claim, and no beta claim. + +The previous released experimental alpha contract is `exp-122`, defined in +`.llm/EXP_122_COMPOSITE_DATA_BENCHMARK_SUITE_EXTENSION_AND_WHITEPAPER_REFRESH_ALPHA.md`, +for one connected benchmark/publication refresh. It kept the exp-121 +language surface unchanged while refreshing the earlier whitepaper baseline +and the documented seven-kernel benchmark suite around `math-loop`, +`branch-loop`, `parse-loop`, `array-index-loop`, `string-eq-loop`, +`array-struct-field-loop`, and `enum-struct-payload-loop`, with +local-machine evidence only, no threshold claim, and no beta claim. + +The previous released experimental alpha contract is `exp-121`, defined in +`.llm/EXP_121_NON_RECURSIVE_STRUCT_ENUM_PAYLOADS_ALPHA.md`, for one connected +broadening of the enum payload surface. It allows unary enum payload variants +to use current known non-recursive struct types, while keeping exactly one +payload per payload variant, a conservative same-payload-type-per-enum rule +when payload variants are present, existing immutable enum flow through +locals/params/returns/calls/tests/main, existing `match` payload binding and +field access behavior, no enum equality requirement for struct-payload enums, +no direct array/vec/option/result payloads, no recursion, and no beta claim. + +The previous released experimental alpha contract is `exp-120`, defined in +`.llm/EXP_120_FIXED_ARRAY_STRUCT_FIELDS_ALPHA.md`, for one connected +broadening of the struct-field surface. It adds direct struct fields over the +current promoted fixed immutable array family `(array i32 N)`, `(array i64 N)`, +`(array f64 N)`, `(array bool N)`, and `(array string N)`, while keeping array +lengths positive only, arrays and structs immutable only, checked indexing on +`i32` expressions only, no field mutation, no array mutation, and no beta +claim. + +The earlier released experimental alpha contract is `exp-119`, defined in +`.llm/EXP_119_BENCHMARK_SUITE_EXTENSION_AND_WHITEPAPER_REFRESH_ALPHA.md`, for +one connected benchmark/publication refresh. It keeps the exp-118 language +surface unchanged while refreshing the earlier whitepaper baseline and the +documented five-kernel benchmark suite around `math-loop`, `branch-loop`, +`parse-loop`, `array-index-loop`, and `string-eq-loop`, with local-machine +evidence only, no threshold claim, and no beta claim. + +The previous released experimental alpha contract is `exp-118`, defined in +`.llm/EXP_118_STRING_FIXED_ARRAYS_ALPHA.md`, for one connected broadening of +the fixed-array surface. It adds `(array string N)` alongside the earlier +direct scalar array family, while keeping array lengths positive only, arrays +immutable only, checked indexing on `i32` expressions only, arrays in struct +fields out of scope at that earlier boundary, and no beta claim. + +The previous released experimental alpha contract is `exp-117`, defined in +`.llm/EXP_117_DIRECT_SCALAR_FIXED_ARRAYS_ALPHA.md`, for one connected +broadening of the fixed-array surface. It adds direct scalar element families +`i64`, `f64`, and `bool` alongside the existing `i32` lane, while keeping +array lengths positive only, arrays immutable only, checked indexing on `i32` +expressions only, string arrays out of scope at that earlier boundary, and no +beta claim. + +The previous released experimental alpha contract is `exp-116`, defined in +`.llm/EXP_116_DIRECT_SCALAR_AND_STRING_ENUM_PAYLOADS_ALPHA.md`, for one +connected broadening of the enum payload surface. It adds unary direct `i64`, +`f64`, `bool`, and `string` payload variants alongside payloadless variants +and the existing unary `i32` lane, while keeping exactly one payload per +payload variant, a conservative same-payload-kind-per-enum rule, containers +and composite payloads out of scope, and no beta claim. + +The previous released experimental alpha contract is `exp-115`, defined in +`.llm/EXP_115_COMPOSITE_STRUCT_FIELDS_AND_NESTED_STRUCTS_ALPHA.md`, for one +connected broadening of the struct-field surface. It adds current concrete +vec, option, and result families plus current known non-recursive struct +types as direct struct fields, alongside the already-supported direct scalar, +immutable string, and current enum field families, without broadening arrays +as fields, field mutation, recursive/cyclic layouts, adding new runtime +calls, or claiming beta maturity. + +The previous released experimental alpha contract is `exp-114`, defined in +`.llm/EXP_114_MUTABLE_COMPOSITE_LOCALS_ALPHA.md`, for one connected +broadening of the local-value surface. It adds same-type mutable whole-value +reassignment for `string`, the current concrete vec families, the current +concrete option/result families, current known struct values, and current enum +values, without broadening arrays, field/element/payload mutation, adding new +runtime calls, or claiming beta maturity. + +The previous released experimental alpha contract is `exp-113`, defined in +`.llm/EXP_113_MUTABLE_SCALAR_LOCALS_ALPHA.md`, for one connected broadening +of the local-value surface. It adds direct mutable `bool`, `i64`, and `f64` +locals declared with `var` and reassigned with `set` from already promoted +same-type scalar expressions, without broadening string/vector/option/result/ +struct/enum mutation, adding new runtime calls, or claiming beta maturity. + +The previous released experimental alpha contract is `exp-112`, defined in +`.llm/EXP_112_IMMUTABLE_BOOL_LOCALS_ALPHA.md`, for one connected broadening +of the local-value surface. It adds direct immutable `bool` locals declared +with `let`, initialized from already promoted bool expressions, and reusable +in existing predicate, return, call, test, and `main` positions, without +adding mutable bool locals, bool assignment, new runtime calls, or a beta +claim. + +The previous released experimental alpha contract is `exp-111`, defined in +`.llm/EXP_111_STANDARD_IO_STDIN_SOURCE_HELPERS_ALPHA.md`, for one connected +broadening of the concrete `std.io` facade. It adds +`read_stdin_result`, `read_stdin_option`, `read_stdin_or`, +`read_stdin_i32_result`, `read_stdin_i32_option`, `read_stdin_i32_or_zero`, +`read_stdin_i32_or`, `read_stdin_i64_result`, `read_stdin_i64_option`, +`read_stdin_i64_or_zero`, `read_stdin_i64_or`, `read_stdin_f64_result`, +`read_stdin_f64_option`, `read_stdin_f64_or_zero`, `read_stdin_f64_or`, +`read_stdin_bool_result`, `read_stdin_bool_option`, +`read_stdin_bool_or_false`, and `read_stdin_bool_or` while staying +source-authored over the promoted `std.io.read_stdin_result`, +`std.string.parse_*_result`, and exp-109 bridge helpers only, without +adding compiler-known std names, generics, new runtime calls, or a beta +claim. + +The previous released experimental alpha contract is `exp-109`, defined in +`.llm/EXP_109_STANDARD_OPTION_AND_RESULT_BRIDGE_HELPERS_ALPHA.md`, for one +connected broadening of the concrete `std.option` and `std.result` facades. +It adds `some_or_err_i32`, `some_or_err_i64`, `some_or_err_f64`, +`some_or_err_bool`, `some_or_err_string`, `ok_or_none_i32`, +`ok_or_none_i64`, `ok_or_none_string`, `ok_or_none_f64`, and +`ok_or_none_bool` while staying source-authored over the existing concrete +option/result families only, without adding compiler-known std names, +generics, or a beta claim. + +The previous released experimental alpha contract is `exp-108`, defined in +`.llm/EXP_108_STANDARD_VEC_STRING_F64_AND_BOOL_PREFIX_AND_SUFFIX_HELPERS_ALPHA.md`, +for one connected broadening of the concrete `std.vec_string`, +`std.vec_f64`, and `std.vec_bool` facades. It adds `starts_with`, +`without_prefix`, `ends_with`, and `without_suffix` to all three modules +while staying source-authored, recursive, and immutable over the existing +vec-string, vec-f64, and vec-bool runtime families only, without adding +compiler-known std names, generics, or a beta claim. + +The previous released experimental alpha contract is `exp-106`, defined in +`.llm/EXP_106_STANDARD_VEC_F64_AND_BOOL_OPTION_QUERY_HELPERS_ALPHA.md`, for +one connected broadening of the concrete `std.vec_f64` and `std.vec_bool` +facades. It adds `index_option`, `first_option`, `last_option`, +`index_of_option`, and `last_index_of_option` to both modules while staying +source-authored, recursive, and immutable over the existing vec-f64 and +vec-bool runtime families and the already promoted concrete option families +only, without adding compiler-known std names, generics, edit helpers, or a +beta claim. + +The previous released experimental alpha contract is `exp-104`, defined in +`.llm/EXP_104_STANDARD_VEC_BOOL_BASELINE_ALPHA.md`, for the first usable +`std.vec_bool` baseline facade over the sibling concrete `(vec bool)` runtime +family. It freezes the baseline direct wrappers, builder helpers, query +helpers, and the simple real-program helpers `contains` and `count_of` +without widening compiler semantics beyond concrete `(vec bool)` flow and +equality, and without adding generics, option/result helpers, transform/edit +helpers, or a beta claim. + +The previous released experimental alpha contract is `exp-102`, defined in +`.llm/EXP_102_STANDARD_OPTION_BOOL_AND_F64_BASELINE_ALPHA.md`, for one +connected broadening of the concrete `std.option` facade and the conservative +option core-language slice. It adds the concrete `(option f64)` and +`(option bool)` helper families to `std/option.slo`, broadens immutable value +flow, extraction, and source-level `match` to those same families, and keeps +the whole slice explicit and concrete without adding compiler-known std names, +generic option semantics, or a beta claim. + +The earlier released experimental alpha contract is `exp-101`, defined in +`.llm/EXP_101_STANDARD_VEC_STRING_OPTION_AND_TRANSFORM_HELPERS_ALPHA.md`, for +one connected broadening of the concrete `std.vec_string` facade. It adds the +option-query helpers `index_option`, `first_option`, `last_option`, +`index_of_option`, and `last_index_of_option`, plus the transform helpers +`concat`, `take`, `drop`, `reverse`, and `subvec`, while staying +source-authored, recursive, and immutable over the existing vec-string +runtime family without adding compiler-known std names, generics, edit +helpers, or a beta claim. + +The earlier released experimental alpha contract is `exp-100`, defined in +`.llm/EXP_100_STANDARD_OPTION_STRING_BASELINE_ALPHA.md`, for the first +concrete `(option string)` baseline on top of the existing option slice. It +broadens `std/option.slo` with concrete string constructors, observers, and +fallback helpers, and broadens immutable option value flow, payload +extraction, and source-level `match` to `(option string)` without adding +compiler-known std names, generic option semantics, or a beta claim. + +The earlier released experimental alpha contract is `exp-99`, defined in +`.llm/EXP_99_STANDARD_VEC_STRING_BASELINE_ALPHA.md`, for the first usable +`std.vec_string` baseline facade over the sibling concrete `(vec string)` +runtime family. It adds the required direct wrappers, builder helpers, query +helpers, and simple real-program helpers as one connected source-authored +slice without widening compiler semantics beyond concrete `(vec string)` flow +and equality, and without adding generics, helper families beyond the frozen +baseline, or a beta claim. + +The earlier released experimental alpha contract is `exp-98`, defined in +`.llm/EXP_98_STANDARD_VEC_I64_EDIT_HELPERS_ALPHA.md`, for connected edit +helpers on top of the existing `std.vec_i64` baseline facade. It adds +`insert_at`, `insert_range`, `replace_at`, `replace_range`, `remove_at`, and +`remove_range` as one connected source-authored slice, keeps the full +vec_i64 source family recursive and immutable, and does not add +compiler-known std names, generics, or a beta claim. + +The earlier released experimental alpha contract is `exp-97`, defined in +`.llm/EXP_97_STANDARD_VEC_I64_TRANSFORM_HELPERS_ALPHA.md`, for narrow +transform helpers on top of the existing `std.vec_i64` baseline facade. It +adds `concat`, `take`, `drop`, `reverse`, and `subvec` as one connected +source-authored slice, keeps the full vec_i64 source family recursive and +immutable, and does not add compiler-known std names, insert/replace/remove +helpers, generics, or a beta claim. + +The earlier released experimental alpha contract is `exp-96`, defined in +`.llm/EXP_96_STANDARD_VEC_I64_OPTION_QUERY_HELPERS_ALPHA.md`, for concrete +option-query helpers on top of the existing `std.vec_i64` baseline facade. +It adds `index_option`, `first_option`, `last_option`, `index_of_option`, +and `last_index_of_option` as one connected source-authored slice, keeps the +module self-contained through raw current `(option i64)` and `(option i32)` +forms instead of `std.option`, keeps the full vec_i64 source family recursive +and immutable, and does not add compiler-known std names, transform/range/edit +helpers, generics, or a beta claim. + +The earlier released experimental alpha contract is `exp-95`, defined in +`.llm/EXP_95_STANDARD_OPTION_I64_BASELINE_ALPHA.md`, for the first concrete +`(option i64)` baseline on top of the existing option slice. It broadens +`std/option.slo` with the `i64` constructor, observer, and fallback helper +surface, and broadens immutable option value flow, payload extraction, and +source-level `match` to `(option i64)` without adding compiler-known std +names, generic option semantics, or a beta claim. + +The earlier released `exp-94` is defined in +`.llm/EXP_94_STANDARD_VEC_I64_BASELINE_ALPHA.md`, for the first usable +`std.vec_i64` baseline facade over the sibling concrete `(vec i64)` runtime +family. It adds the required direct wrappers, builder helpers, query +helpers, and simple real-program helpers as one connected source-authored +slice without widening compiler semantics or adding new Slovo-side +compiler-known runtime names. It remains experimental maturity, not a beta +claim. + +The earlier released `exp-93` is defined in +`.llm/EXP_93_STANDARD_VEC_I32_COUNT_OF_HELPER_ALPHA.md`, for one new public +`std.vec_i32` helper, `count_of`, layered onto that same concrete runtime +family without widening compiler semantics or adding new runtime names. It +remains experimental maturity, not a beta claim. + +The earlier released `exp-92` is defined in +`.llm/EXP_92_STANDARD_VEC_I32_WITHOUT_PREFIX_HELPER_ALPHA.md`, for one new +public `std.vec_i32` helper, `without_prefix`, layered onto that same +concrete runtime family without widening compiler semantics or adding new +runtime names. It remains experimental maturity, not a beta claim. + +The earlier released `exp-91` is defined in +`.llm/EXP_91_STANDARD_VEC_I32_WITHOUT_SUFFIX_HELPER_ALPHA.md`, for one new +public `std.vec_i32` helper, `without_suffix`, layered onto that same +concrete runtime family without widening compiler semantics or adding new +runtime names. It remains experimental maturity, not a beta claim. + +The earlier released `exp-90` is defined in +`.llm/EXP_90_STANDARD_VEC_I32_ENDS_WITH_HELPER_ALPHA.md`, for one new +public `std.vec_i32` helper, `ends_with`, layered onto that same concrete +runtime family without widening compiler semantics or adding new runtime +names. It remains experimental maturity, not a beta claim. + +The earlier released `exp-89` is defined in +`.llm/EXP_89_STANDARD_VEC_I32_STARTS_WITH_HELPER_ALPHA.md`, for one new +public `std.vec_i32` helper, `starts_with`, layered onto that same concrete +runtime family without widening compiler semantics or adding new runtime +names. It remains experimental maturity, not a beta claim. + +The earlier released `exp-88` is defined in +`.llm/EXP_88_STANDARD_VEC_I32_REPLACE_RANGE_HELPER_ALPHA.md`, for one new +public `std.vec_i32` helper, `replace_range`, layered onto that same +concrete runtime family without widening compiler semantics or adding new +runtime names. It remains experimental maturity, not a beta claim. + +The earlier released `exp-87` is defined in +`.llm/EXP_87_STANDARD_VEC_I32_INSERT_RANGE_HELPER_ALPHA.md`, for one new +public `std.vec_i32` helper, `insert_range`, layered onto that same concrete +runtime family without widening compiler semantics or adding new runtime +names. It remains experimental maturity, not a beta claim. + +The earlier released `exp-86` is defined in +`.llm/EXP_86_STANDARD_VEC_I32_REMOVE_RANGE_HELPER_ALPHA.md`, for one new +public `std.vec_i32` helper, `remove_range`, layered onto that same concrete +runtime family without widening compiler semantics or adding new runtime +names. It remains experimental maturity, not a beta claim. + +The earlier released `exp-85` is defined in +`.llm/EXP_85_STANDARD_VEC_I32_SUBVEC_HELPER_ALPHA.md`, for one new public +`std.vec_i32` helper, `subvec`, layered onto that same concrete runtime +family without widening compiler semantics or adding new runtime names. It +remains experimental maturity, not a beta claim. + +The earlier released `exp-84` is defined in +`.llm/EXP_84_STANDARD_VEC_I32_INSERT_HELPER_ALPHA.md`, for one new public +`std.vec_i32` helper, `insert_at`, layered onto that same concrete runtime +family without widening compiler semantics or adding new runtime names. It +remains experimental maturity, not a beta claim. + +The earlier released `exp-83` is defined in +`.llm/EXP_83_STANDARD_VEC_I32_REMOVE_HELPER_ALPHA.md`, for one new public +`std.vec_i32` helper, `remove_at`, layered onto that same concrete runtime +family without widening compiler semantics or adding new runtime names. It +remains experimental maturity, not a beta claim. + +The earlier released `exp-82` is defined in +`.llm/EXP_82_STANDARD_VEC_I32_REPLACE_HELPER_ALPHA.md`, for one new public +`std.vec_i32` helper, `replace_at`, layered onto that same concrete runtime +family without widening compiler semantics or adding new runtime names. It +remains experimental maturity, not a beta claim. + +The earlier released `exp-81` is defined in +`.llm/EXP_81_STANDARD_VEC_I32_RANGE_HELPER_ALPHA.md`, for one new public +`std.vec_i32` helper, `range`, layered onto that same concrete runtime +family without widening compiler semantics or adding new runtime names. It +remains experimental maturity, not a beta claim. + +The earlier released `exp-80` is defined in +`.llm/EXP_80_STANDARD_VEC_I32_GENERATED_CONSTRUCTOR_HELPERS_ALPHA.md`, for +narrow generated constructor helpers layered onto the existing `std.vec_i32` +source facade over the promoted `(vec i32)` runtime family. + +The earlier released `exp-79` is defined in +`.llm/EXP_79_STANDARD_VEC_I32_TRANSFORM_HELPERS_ALPHA.md`, for narrow +recursive transform helpers layered onto the existing `std.vec_i32` source +facade over the promoted `(vec i32)` runtime family. It remains +experimental maturity, not a beta claim. + +The earlier released `exp-78` is defined in +`.llm/EXP_78_STANDARD_CLI_LOCAL_SOURCE_FACADE_ALPHA.md`, for the unchanged +`std.cli` helper surface recorded as the Slovo-side contract for the sibling +Glagol local-source gate over the existing `std.process` and `std.string` +source facades. It remains experimental maturity, not a beta claim. + +The earlier released `exp-77` is defined in +`.llm/EXP_77_STANDARD_VEC_I32_OPTION_QUERY_HELPERS_ALPHA.md`, for concrete +option-returning query helpers layered onto the current `std.vec_i32` source +facade over the promoted `(vec i32)` runtime family. It remains experimental +maturity, not a beta claim. + +The earlier released `exp-76` is defined in +`.llm/EXP_76_STANDARD_VEC_I32_SOURCE_HELPERS_ALPHA.md`, for a concrete +`std.vec_i32` source facade over the current promoted `(vec i32)` runtime +family. It remains experimental maturity, not a beta claim. + +The earlier released `exp-75` is defined in +`.llm/EXP_75_STANDARD_OPTION_CONSTRUCTORS_ALPHA.md`, for source-authored +concrete option constructors over the current promoted `(option i32)` family. +It remains experimental maturity, not a beta claim. +The earlier released `exp-74` is defined in +`.llm/EXP_74_STANDARD_RESULT_CONSTRUCTOR_HELPERS_ALPHA.md`, for +source-authored concrete result constructors over the current promoted result +families. It remains experimental maturity, not a beta claim. + +The earlier released `exp-73` is defined in +`.llm/EXP_73_STANDARD_IO_VALUE_HELPERS_ALPHA.md`, for source-authored +value-returning IO print helpers over the current promoted print runtime +calls. It remains experimental maturity, not a beta claim. + +The earlier released `exp-72` is defined in +`.llm/EXP_72_STANDARD_CLI_CUSTOM_FALLBACK_HELPERS_ALPHA.md`, for +source-authored typed CLI argument custom-fallback helpers over the current +argument lookup and concrete parse result families. It remains experimental +maturity, not a beta claim. + +The earlier released `exp-71` is defined in +`.llm/EXP_71_STANDARD_PROCESS_CUSTOM_FALLBACK_HELPERS_ALPHA.md`, for +source-authored typed process argument custom-fallback helpers over the +current argument lookup and concrete parse result families. It remains +experimental maturity, not a beta claim. + +The earlier released `exp-70` is defined in +`.llm/EXP_70_STANDARD_FS_CUSTOM_FALLBACK_HELPERS_ALPHA.md`, for +source-authored typed filesystem read custom-fallback helpers over the current +text-read and concrete parse result families. It remains experimental +maturity, not a beta claim. + +The earlier released `exp-69` is defined in +`.llm/EXP_69_STANDARD_ENV_CUSTOM_FALLBACK_HELPERS_ALPHA.md`, for +source-authored typed environment parse custom-fallback helpers over the +current environment lookup and concrete parse result families. It remains +experimental maturity, not a beta claim. + +The earlier released `exp-68` is defined in +`.llm/EXP_68_STANDARD_STRING_CUSTOM_FALLBACK_HELPERS_ALPHA.md`, for +source-authored typed string parse custom-fallback helpers over the current +concrete parse result families. It remains experimental maturity, not a beta +claim. + +The earlier released `exp-67` is defined in +`.llm/EXP_67_STANDARD_PROCESS_TYPED_HELPERS_ALPHA.md`, for source-authored +typed process argument result/fallback helpers over the current argument +lookup and concrete parse result families. It remains experimental maturity, +not a beta claim. + +The earlier released `exp-66` is defined in +`.llm/EXP_66_STANDARD_FS_TYPED_READ_HELPERS_ALPHA.md`, for source-authored +typed filesystem read result/fallback helpers over the current text-read and +concrete parse result families. It remains experimental maturity, not a beta +claim. + +The earlier released `exp-65` is defined in +`.llm/EXP_65_STANDARD_ENV_TYPED_HELPERS_ALPHA.md`, for source-authored typed +environment parse result/fallback helpers over the current environment lookup +and concrete parse result families. It remains experimental maturity, not a +beta claim. + +The earlier released `exp-64` is defined in +`.llm/EXP_64_STANDARD_NUM_FALLBACK_HELPERS_ALPHA.md`, for source-authored +checked conversion fallback helpers over the current numeric result +families. It remains experimental maturity, not a beta claim. + +The earlier released `exp-63` is defined in +`.llm/EXP_63_STANDARD_FS_FALLBACK_HELPERS_ALPHA.md`, for source-authored +filesystem fallback/status helpers over the current `(result string i32)` and +`(result i32 i32)` families. It remains experimental maturity, not a beta +claim. + +The earlier released `exp-62` is defined in +`.llm/EXP_62_STANDARD_ENV_FALLBACK_HELPERS_ALPHA.md`, for source-authored +environment presence/fallback helpers over the current `(result string i32)` +family. It remains experimental maturity, not a beta claim. + +The earlier released `exp-61` is defined in +`.llm/EXP_61_STANDARD_PROCESS_FALLBACK_HELPERS_ALPHA.md`, for source-authored +process argument fallback helpers over the current `(result string i32)` +family. It remains experimental maturity, not a beta claim. + +The earlier released `exp-60` is defined in +`.llm/EXP_60_STANDARD_STRING_FALLBACK_HELPERS_ALPHA.md`, for source-authored +string parse fallback helpers over the current concrete parse result +families. It remains experimental maturity, not a beta claim. + +The earlier released `exp-59` is defined in +`.llm/EXP_59_HOSTED_BUILD_OPTIMIZATION_AND_BENCHMARK_PUBLICATION_ALPHA.md`, +for the earlier benchmark/publication baseline paired with Glagol exp-59. It +keeps the exp-58 language surface unchanged and remains experimental +maturity, not a beta claim. + +The earlier released `exp-58` is defined in +`.llm/EXP_58_BOOLEAN_LOGIC_ALPHA.md`, for boolean `and`, `or`, and `not` +forms lowered through existing `if` semantics. + +The earlier released `exp-57` is defined in +`.llm/EXP_57_INTEGER_BITWISE_ALPHA.md`, for explicit integer bitwise heads +over same-width `i32` and `i64` operands plus source-authored `std.math` +bitwise wrappers. + +The earlier released `exp-56` is defined in +`.llm/EXP_56_INTEGER_REMAINDER_ALPHA.md`, for integer remainder `%` over +same-width `i32` and `i64` operands plus source-authored `std.math` +remainder/even/odd helpers. + +The earlier released `exp-55` is defined in +`.llm/EXP_55_RESULT_F64_BOOL_SOURCE_FLOW_ALPHA.md`, for concrete `f64` and +`bool` result constructors and source `match` payload bindings. + +The earlier released `exp-54` is defined in +`.llm/EXP_54_STANDARD_CLI_TYPED_ARGUMENTS_ALPHA.md`, for typed argument parse +helpers in the staged CLI facade module. + +The earlier released `exp-53` is defined in +`.llm/EXP_53_STANDARD_CLI_SOURCE_FACADE_ALPHA.md`, for explicit project-mode +source search of the staged CLI facade module. + +The earlier released `exp-52` is defined in +`.llm/EXP_52_STANDARD_PROCESS_FACADE_SOURCE_SEARCH_ALPHA.md`, for explicit +project-mode source search of the staged process facade module. + +The earlier released `exp-51` is defined in +`.llm/EXP_51_STANDARD_LIBRARY_PATH_LIST_ALPHA.md`, for ordered +`SLOVO_STD_PATH` discovery of explicit standard-library source imports. + +The earlier released `exp-50` is defined in +`.llm/EXP_50_INSTALLED_STANDARD_LIBRARY_DISCOVERY_ALPHA.md`, for installed +toolchain discovery of explicit standard-library source imports. + +The earlier released `exp-49` is defined in +`.llm/EXP_49_STANDARD_IO_FACADE_SOURCE_SEARCH_ALPHA.md`, for explicit +project-mode source search of the staged IO facade module. + +The earlier released `exp-48` is defined in +`.llm/EXP_48_STANDARD_CORE_FACADE_SOURCE_SEARCH_ALPHA.md`, for explicit +project-mode source search of staged core facade modules. + +The earlier released `exp-47` is defined in +`.llm/EXP_47_STANDARD_HOST_FACADE_SOURCE_SEARCH_ALPHA.md`, for explicit +project-mode source search of staged host facade modules. + +The earlier released `exp-46` is defined in +`.llm/EXP_46_WORKSPACE_STANDARD_SOURCE_SEARCH_ALPHA.md`, for explicit +workspace-package standard source search. + +The earlier released `exp-45` is defined in +`.llm/EXP_45_STANDARD_RESULT_OPTION_SOURCE_SEARCH_ALPHA.md`, for explicit +project-mode `std.result` and `std.option` source search. + +The earlier released `exp-44` is defined in +`.llm/EXP_44_STANDARD_LIBRARY_SOURCE_SEARCH_ALPHA.md`, for explicit +project-mode `std.math` source search. + +The earlier released `exp-43` is defined in +`.llm/EXP_43_TECHNICAL_WHITEPAPERS_AND_SKILLS_ALPHA.md`, for technical +whitepapers, PDF publication artifacts, and beta language-design skills. + +The earlier released `exp-42` is defined in +`.llm/EXP_42_HOT_LOOP_BENCHMARK_MODE_ALPHA.md`, for hot-loop benchmark mode +alignment. It remains experimental maturity, not a beta claim, and does not +change the exp-39 language surface. + +The earlier released `exp-41`, Lisp Benchmark Comparison Alpha, is defined in +`.llm/EXP_41_LISP_BENCHMARK_COMPARISON_ALPHA.md` for Lisp-family benchmark +comparison alignment. + +The earlier released `exp-40`, Benchmark Suite And License Alpha, is defined +in `.llm/EXP_40_BENCHMARK_SUITE_AND_LICENSE_ALPHA.md` for benchmark-suite +alignment, project licensing, and release naming criteria. + +The earlier released `exp-39`, Standard Math Extensions And Benchmark +Scaffold Alpha, is defined in +`.llm/EXP_39_STANDARD_MATH_EXTENSIONS_AND_BENCHMARK_SCAFFOLD_ALPHA.md` for +source-authored math helper extensions in `std/math.slo` and matching Glagol +benchmark scaffold gates. + +The earlier released `exp-38`, Standard Host Source Facades Alpha, is defined +in `.llm/EXP_38_STANDARD_HOST_SOURCE_FACADES_ALPHA.md` and remains part of the +experimental history. + +The earlier released `exp-37`, Standard Time Source Facade Alpha, is defined +in `.llm/EXP_37_STANDARD_TIME_SOURCE_FACADE_ALPHA.md` and remains part of the +experimental history. + +The earlier released `exp-36`, Standard Option Source Helpers Alpha, is +defined in `.llm/EXP_36_STANDARD_OPTION_SOURCE_HELPERS_ALPHA.md` and remains +part of the experimental history. +The earlier released `exp-35`, Standard Result Bool Source Helpers Alpha, is +defined in `.llm/EXP_35_STANDARD_RESULT_BOOL_SOURCE_HELPERS_ALPHA.md` and +remains part of the experimental history. + +The earlier released `exp-34`, String Parse Bool Result Alpha, is defined in +`.llm/EXP_34_STRING_PARSE_BOOL_RESULT_ALPHA.md` and remains part of the +experimental history. + +The earlier released `exp-33`, Standard Result Source Helpers Alpha, is +defined in `.llm/EXP_33_STANDARD_RESULT_SOURCE_HELPERS_ALPHA.md` and remains +part of the experimental history. + +The earlier released `exp-32`, Standard Math Source Helpers Alpha, is defined +in `.llm/EXP_32_STANDARD_MATH_SOURCE_HELPERS_ALPHA.md` and remains part of +the experimental history. + +The earlier released `exp-31`, Checked F64 To I64 Result Alpha, is defined in +`.llm/EXP_31_CHECKED_F64_TO_I64_RESULT_ALPHA.md` and remains part of the +experimental history. + +The earlier released `exp-30`, Standard Library Source Layout Alpha, is +defined in `.llm/EXP_30_STANDARD_LIBRARY_SOURCE_LAYOUT_ALPHA.md` and remains +part of the experimental history. + +The earlier released `exp-29`, Numeric Struct Fields Alpha, is defined in +`.llm/EXP_29_NUMERIC_STRUCT_FIELDS_ALPHA.md` and remains part of the +experimental history. + +The current released experimental alignment is `exp-14`, Standard Runtime +Conformance Alignment, defined in +`.llm/EXP_14_STANDARD_RUNTIME_CONFORMANCE_ALIGNMENT.md`. It adds no source +syntax, type forms, runtime APIs, compiler-known `std.*` names, standard +library functions, manifest schema version, ABI/layout promise, runtime +headers/libraries, or beta maturity. + +Release maturity policy lives in `.llm/RELEASE_MATURITY_POLICY.md`. All +current releases are experimental maturity; beta is reserved for the +general-purpose threshold. + +## Definition Of Supported + +An example is compiler-supported only when Glagol can parse it, lower it, type-check it, emit LLVM for it, and the behavior is covered by an automated test. + +Partial compiler recognition does not count as support. A checked form that can +still reach a backend panic, lacks diagnostics, or lacks tests remains a design +target. + +Current supported subset: + +- `examples/supported/add.slo` +- `examples/supported/top-level-test.slo` +- `examples/supported/local-variables.slo` +- `examples/supported/composite-locals.slo` +- `examples/supported/composite-struct-fields.slo` +- `examples/supported/if.slo` +- `examples/supported/while.slo` +- `examples/supported/struct.slo` +- `examples/supported/struct-value-flow.slo` +- `examples/supported/array.slo` +- `examples/supported/array-value-flow.slo` +- `examples/supported/array-direct-scalars.slo` +- `examples/supported/array-direct-scalars-value-flow.slo` +- `examples/supported/array-string.slo` +- `examples/supported/array-string-value-flow.slo` +- `examples/supported/array-struct-fields.slo` +- `examples/supported/enum-payload-structs.slo` +- `examples/supported/option-result.slo` +- `examples/supported/option-result-flow.slo` +- `examples/supported/option-result-payload.slo` +- `examples/supported/option-result-match.slo` +- `examples/supported/string-print.slo` +- `examples/supported/string-value-flow.slo` +- `examples/supported/print-bool.slo` +- `examples/supported/standard-runtime.slo` +- `examples/supported/unsafe.slo` +- `examples/supported/time-sleep.slo` +- `examples/supported/string-parse-i32-result.slo` +- `examples/supported/result-helpers.slo` +- `examples/supported/enum-payload-i32.slo` +- `examples/supported/enum-payload-direct-scalars.slo` +- `examples/supported/enum-struct-fields.slo` +- `examples/supported/primitive-struct-fields.slo` +- `examples/supported/f64-numeric-primitive.slo` +- `examples/supported/i64-numeric-primitive.slo` +- `examples/supported/numeric-widening-conversions.slo` +- `examples/supported/checked-i64-to-i32-conversion.slo` +- `examples/supported/integer-to-string.slo` +- `examples/supported/string-parse-i64-result.slo` +- `examples/supported/f64-to-string.slo` +- `examples/supported/f64-to-i32-result.slo` +- `examples/supported/string-parse-f64-result.slo` +- `examples/supported/numeric-struct-fields.slo` +- `examples/supported/f64-to-i64-result.slo` +- `examples/supported/string-parse-bool-result.slo` +- `(module name)` +- top-level `(struct Name (field i32)...)` +- top-level `(fn ...)` +- top-level `(test "name" body... final-expression)` +- explicit `i32` parameters and `i32` returns, plus direct `(option i32)` and + `(result i32 i32)` constructor returns +- integer literals, parameter references, binary `+`, user function calls, + legacy compiler/runtime compatibility alias `print_i32`, equality + comparison `=`, ordering comparison `<`, and final-expression returns +- local `i32` bindings with `let` and `var`, local references after + declaration, and mutable local assignment with `set` +- value-producing `if` +- first-pass `while` as a non-final sequential body form +- first-pass struct construction and immediate field access +- first v1 struct value flow through immutable locals, parameters, returns, + calls returning structs, and stored field access +- fixed direct scalar array constructors over `i32`, `i64`, `f64`, and `bool`, + immutable array locals, and literal checked indexing +- fixed direct scalar array value flow through immutable array locals + initialized from matching array-valued expressions, fixed array parameters, + returns, calls returning arrays, and runtime-checked dynamic indexing +- first-pass `i32` option/result constructors as direct function returns +- first v1 option/result value flow through immutable locals, parameters, + calls returning option/result values, and `is_some` / `is_none` / `is_ok` / + `is_err` tag observation +- first v1 trap-based `i32` option/result payload extraction with + `unwrap_some`, `unwrap_ok`, and `unwrap_err` +- v1.4 source-level `match` for existing `(option i32)` and + `(result i32 i32)` values, with exhaustive arms, immutable arm-scoped + payload bindings, one-or-more expression arm bodies, and common arm result + types +- exp-10 concrete `(result string i32)` with + the same narrow result flow, observers, unwraps, and `match` shape used by + existing `(result i32 i32)` support +- exp-15 preferred result helper standard names `std.result.is_ok`, + `std.result.is_err`, `std.result.unwrap_ok`, and `std.result.unwrap_err` + for only `(result i32 i32)`, `(result i64 i32)`, and + `(result string i32)`, plus `(result f64 i32)` where promoted by exp-28, + with legacy unqualified result helpers retained as compatibility syntax +- exp-16 user-defined enum variants carrying exactly one `i32` payload, + qualified unary constructors, immutable arm-local payload bindings in + exhaustive enum `match`, enum value flow through locals, parameters, + returns, calls, tests, and `main`, and same-enum tag-plus-payload equality +- exp-120 direct struct fields over fixed immutable arrays `(array i32 N)`, + `(array i64 N)`, `(array f64 N)`, `(array bool N)`, and `(array string N)`, + with immutable struct flow and checked indexing after field access +- exp-121 unary enum payload variants over current known non-recursive struct + types, with one payload per payload variant, one payload type per enum when + payload variants are present, immutable enum flow, and `match` payload + binding exposing existing field access behavior +- exp-118 fixed immutable arrays over `string`, with positive literal + lengths, direct construction, immutable local value flow, parameters, + returns, calls returning arrays, and checked indexing with `i32` + expressions only +- exp-117 fixed immutable arrays over direct scalar element families `i32`, + `i64`, `f64`, and `bool`, with positive literal lengths, direct + construction, immutable local value flow, parameters, returns, calls + returning arrays, and checked indexing with `i32` expressions only +- exp-116 user-defined enum variants carrying exactly one direct `i64`, + `f64`, `bool`, or `string` payload in addition to the existing unary `i32` + lane, with the same one-binding exhaustive `match` shape, immutable value + flow, and same-enum tag-plus-payload equality, while keeping one direct + payload kind per enum when payload variants are present +- exp-18 enum-typed struct fields using the current exp-4 payloadless and + exp-16 unary `i32` enum surfaces, with struct construction, immutable + struct flow, field access, same-enum equality on field access, and enum + match on field access +- exp-19 primitive struct fields using direct `bool` and immutable `string` + field types alongside direct `i32` and exp-18 enum fields, with struct + construction, immutable struct flow, field access, bool predicate use, + string equality, and `std.string.len` +- exp-112 immutable bool locals using direct immutable `let` locals declared + as `bool`, initialized from already promoted bool expressions, and flowing + into existing predicate, return, call, test, and `main` positions +- exp-113 mutable scalar locals using direct mutable `var` locals declared as + `bool`, `i64`, and `f64`, plus same-type `set` reassignment from already + promoted scalar expressions +- exp-114 mutable composite locals using same-type whole-value `var` / `set` + reassignment for `string`, current concrete vec families, current concrete + option/result families, current known struct values, and current enum values +- exp-115 composite struct fields and nested structs using direct struct field + declarations over current concrete vec families, current concrete + option/result families, and current known non-recursive struct types, + alongside the already supported direct scalar, immutable string, and current + enum field families +- exp-20 f64 numeric primitive using direct `f64` parameters, returns, + immutable locals, calls, decimal literals, same-type `f64` arithmetic and + comparison, and `std.io.print_f64` +- exp-21 i64 numeric primitive using direct `i64` parameters, returns, + immutable locals, calls, explicit signed decimal `i64` literal atoms, + same-type `i64` arithmetic and comparison, and `std.io.print_i64` +- exp-22 numeric widening conversions using explicit + `std.num.i32_to_i64 : (i32) -> i64`, + `std.num.i32_to_f64 : (i32) -> f64`, and + `std.num.i64_to_f64 : (i64) -> f64` calls over the existing numeric + primitives +- exp-23 checked i64-to-i32 conversion using explicit + `std.num.i64_to_i32_result : (i64) -> (result i32 i32)`, returning `ok` + for signed `i32` range values and `err 1` for out-of-range values +- exp-24 integer to string formatting using explicit + `std.num.i32_to_string : (i32) -> string` and + `std.num.i64_to_string : (i64) -> string`, returning decimal signed ASCII + text with `-` only for negative values and no leading `+` +- exp-25 string parse i64 result using explicit + `std.string.parse_i64_result : (string) -> (result i64 i32)`, returning + `ok value` for in-range ASCII signed decimal `i64` text and `err 1` for + parse failure or out-of-range input +- exp-26 f64 to string formatting using explicit + `std.num.f64_to_string : (f64) -> string`, returning finite decimal ASCII + text for promoted finite `f64` values +- exp-27 checked f64-to-i32 result conversion using explicit + `std.num.f64_to_i32_result : (f64) -> (result i32 i32)`, returning `ok` + value only for finite, exactly integral inputs inside the signed `i32` range + and `err 1` for non-finite, fractional, or out-of-range input +- exp-28 string parse f64 result using explicit + `std.string.parse_f64_result : (string) -> (result f64 i32)`, returning + `ok value` for complete finite ASCII decimal `f64` text and `err 1` for + ordinary parse failure, non-finite text, trailing or leading unsupported + characters, or out-of-domain input +- exp-29 numeric struct fields using direct immutable `i64` and finite `f64` + field types alongside already supported direct `i32`, `bool`, immutable + `string`, and current enum fields, with constructors, immutable struct flow, + field access, existing same-type numeric arithmetic/comparison, and existing + numeric print/format helpers +- exp-30 standard library source layout using `std/` as the staged source home + for standard library modules and examples, including narrow source-authored + `std/math.slo` helpers and source wrappers over already promoted + compiler-known calls, without automatic `std` imports or new + compiler-known `std.*` names +- exp-31 checked f64-to-i64 result conversion using explicit + `std.num.f64_to_i64_result : (f64) -> (result i64 i32)`, returning `ok` + value only for finite, exactly integral inputs inside the signed `i64` range + and `err 1` for non-finite, fractional, or out-of-range input +- exp-32 standard math source helpers in staged `std/math.slo`, adding + source-authored `i64` and finite `f64` `abs`, `min`, `max`, `clamp`, and + `square` coverage without new compiler-known `std.*` runtime names +- exp-39 standard math extension helpers in staged `std/math.slo`, adding + source-authored `neg`, `cube`, zero/positive/negative predicates, and + inclusive `in_range` coverage for `i32`, `i64`, and finite `f64` without + broad math or benchmark-performance claims +- exp-33 standard result source helpers in staged `std/result.slo`, adding + concrete `is_err_*` and `unwrap_err_*` wrappers for already promoted + concrete result families plus `unwrap_or_i32`, `unwrap_or_i64`, + `unwrap_or_string`, and `unwrap_or_f64` without new compiler-known `std.*` + runtime names +- exp-34 string parse bool result using explicit + `std.string.parse_bool_result : (string) -> (result bool i32)`, returning + `ok true` for exactly `true`, `ok false` for exactly `false`, and `err 1` + for empty input, uppercase/mixed-case text, leading/trailing whitespace, + numeric text, or any other text +- exp-35 standard result bool source helpers in staged `std/result.slo`, + adding `is_ok_bool`, `is_err_bool`, `unwrap_ok_bool`, `unwrap_err_bool`, + and `unwrap_or_bool` for `(result bool i32)` without new compiler-known + `std.*` runtime names, generic result helpers, or stable ABI/layout claims +- exp-36 standard option source helpers in staged `std/option.slo`, adding + `is_some_i32`, `is_none_i32`, `unwrap_some_i32`, and `unwrap_or_i32` for + `(option i32)` without new compiler-known `std.*` runtime names, generic + option helpers, automatic `std` imports/search, or stable ABI/layout claims +- exp-37 standard time source facade in staged `std/time.slo`, adding + `monotonic_ms` and `sleep_ms_zero` wrappers over the already released exp-8 + time calls without new compiler-known `std.*` runtime names, automatic + `std` imports/search, wall-clock/calendar/timezone APIs, high-resolution + timers, async timers, cancellation, scheduling guarantees, or stable + ABI/layout claims +- first runtime string slice for immutable source string literals passed + directly to legacy compiler/runtime compatibility alias `print_string`, + which prints the string plus a newline and returns builtin `unit` +- v1.2 immutable string value flow through `let` locals, parameters, returns, + calls returning strings, string equality, string byte length through + `string_len`, and top-level tests using string equality +- legacy compiler/runtime compatibility alias `print_bool`, which prints + `true` or `false` plus a newline and returns builtin `unit` +- v1.5 standard-runtime alpha names `std.io.print_i32`, + `std.io.print_string`, `std.io.print_bool`, and `std.string.len`, with + legacy `print_i32`, `print_string`, `print_bool`, and `string_len` retained + as compatibility aliases +- exp-8 host time/sleep alpha names `std.time.monotonic_ms: () -> i32` and + `std.time.sleep_ms: (i32) -> unit`, with deterministic `sleep_ms 0` and + only structural/non-negative `monotonic_ms` checks in conformance fixtures +- lexical `unsafe` expression blocks containing otherwise supported safe body + forms, plus `UnsafeRequired` / `UnsupportedUnsafeOperation` diagnostics for + raw unsafe operation heads reserved by v1.6: `alloc`, `dealloc`, `load`, + `store`, `ptr_add`, `unchecked_index`, `reinterpret`, and `ffi_call` + +Current exp-1 fixture support: + +- `examples/supported/owned-string-concat.slo` +- `examples/formatter/owned-string-concat.slo` +- compiler-known `std.string.concat` with signature + `(string, string) -> string`, producing immutable runtime-owned strings + +Current exp-2 fixture support: + +- `examples/supported/vec-i32.slo` +- `examples/formatter/vec-i32.slo` +- one concrete growable vector type `(vec i32)` +- compiler-known `std.vec.i32.empty`, `std.vec.i32.append`, + `std.vec.i32.len`, and `std.vec.i32.index` +- vector equality with `=` + +These are current compiler-supported exp-2 fixtures. + +Current exp-3 fixture support: + +- `examples/supported/host-io.slo` +- `examples/formatter/host-io.slo` +- compiler-known `std.io.eprint`, `std.process.argc`, `std.process.arg`, + `std.env.get`, `std.fs.read_text`, and `std.fs.write_text` +- process argument out-of-range and file read failure as exact runtime traps +- missing environment variables as empty strings +- text file write status `0` on success and `1` on host failure + +These are current compiler-supported exp-3 fixtures. + +Current exp-8 release fixtures: + +- `examples/supported/time-sleep.slo` +- `examples/formatter/time-sleep.slo` +- compiler-known `std.time.monotonic_ms: () -> i32` +- compiler-known `std.time.sleep_ms: (i32) -> unit` +- non-negative host monotonic elapsed milliseconds with implementation-owned + epoch +- deterministic `sleep_ms 0` +- no positive-duration timing assertion + +These are current compiler-supported exp-8 fixtures aligned by the released +exp-14 conformance release. + +Current exp-12 release fixtures: + +- `examples/supported/stdin-result.slo` +- `examples/formatter/stdin-result.slo` +- compiler-known `std.io.read_stdin_result: () -> (result string i32)` +- remaining stdin text as `ok` +- ordinary EOF with no bytes as `ok ""` +- ordinary host/input failure as `err 1` +- deterministic-enough test-runner behavior through an implementation-owned + fixed `ok` string + +These are current compiler-supported exp-12 fixtures. + +Current exp-13 release fixtures: + +- `examples/supported/string-parse-i32-result.slo` +- `examples/formatter/string-parse-i32-result.slo` +- compiler-known + `std.string.parse_i32_result: (string) -> (result i32 i32)` +- entire ASCII decimal signed `i32` parsing with optional leading `-` +- success as `(ok i32 i32 value)` +- ordinary parse failure as `(err i32 i32 1)` +- structural composition with `std.io.read_stdin_result` without assuming a + particular stdin payload + +These are current compiler-supported exp-13 fixtures. + +Current exp-15 released fixtures: + +- `examples/supported/result-helpers.slo` +- `examples/formatter/result-helpers.slo` +- compiler-known `std.result.is_ok` and `std.result.is_err` for + `(result i32 i32)` and `(result string i32)` +- compiler-known `std.result.unwrap_ok` returning `i32` for + `(result i32 i32)` and `string` for `(result string i32)` +- compiler-known `std.result.unwrap_err` returning `i32` for both supported + result families +- legacy unqualified `is_ok`, `is_err`, `unwrap_ok`, and `unwrap_err` remain + compatibility syntax where already supported + +These are released Slovo-side exp-15 fixtures with matching Glagol gates. + +Current exp-10 compiler-supported fixtures: + +- `examples/supported/host-io-result.slo` +- `examples/formatter/host-io-result.slo` +- concrete `(result string i32)` values +- compiler-known `std.process.arg_result`, `std.env.get_result`, + `std.fs.read_text_result`, and `std.fs.write_text_result` +- ordinary host failure as `err 1` +- exp-3 calls unchanged + +Current exp-11 release fixtures: + +- `examples/supported/random.slo` +- `examples/formatter/random.slo` +- compiler-known `std.random.i32: () -> i32` +- non-negative implementation-owned random `i32` values for basic CLI use +- exact unavailable trap: + `slovo runtime error: random i32 unavailable` + +Current exp-4 fixture support: + +- `examples/supported/enum-basic.slo` +- `examples/formatter/enum-basic.slo` +- payloadless `(enum Name VariantA VariantB ...)` declarations with at least + one variant +- zero-argument qualified constructors such as `(Name.VariantA)` +- enum values through immutable locals, parameters, returns, calls, equality, + and top-level tests +- exhaustive payloadless enum `match` arms with common final result types + +These are current compiler-supported exp-4 fixtures. + +Current exp-16 fixture support: + +- `examples/supported/enum-payload-i32.slo` +- `examples/formatter/enum-payload-i32.slo` +- enum declarations mixing payloadless variants and unary `i32` payload + variants +- zero-argument constructors for payloadless variants and qualified unary + constructors for payload variants +- enum values through immutable locals, parameters, returns, calls, equality, + top-level tests, and `main` +- exhaustive enum `match` arms with immutable arm-local payload bindings for + payload variants +- same-enum equality by tag for payloadless variants and by tag plus payload + for payload variants + +These are released experimental exp-16 fixtures. + +Current exp-121 fixture support: + +- `examples/supported/enum-payload-structs.slo` +- `examples/formatter/enum-payload-structs.slo` +- unary enum payload variants over current known non-recursive struct types +- one payload per payload variant and one payload type per enum when payload + variants are present +- qualified constructors, immutable enum locals, parameters, returns, calls, + tests, and `main` +- exhaustive `match` payload binding exposing the struct payload for existing + field access and checked indexing through array-bearing struct fields + +These are released experimental exp-121 fixtures. + +Current exp-120 fixture support: + +- `examples/supported/array-struct-fields.slo` +- `examples/formatter/array-struct-fields.slo` +- direct struct field declarations over fixed immutable arrays with element + families `i32`, `i64`, `f64`, `bool`, and `string` +- struct construction from matching fixed-array expressions +- immutable struct locals, parameters, returns, calls, tests, and `main` + for structs carrying those field types +- field access returning the declared fixed-array type for existing checked + indexing with `i32` expressions + +These are released experimental exp-120 fixtures. + +Current exp-118 fixture support: + +- `examples/supported/array-string.slo` +- `examples/formatter/array-string.slo` +- `examples/supported/array-string-value-flow.slo` +- `examples/formatter/array-string-value-flow.slo` +- fixed immutable arrays over `string` +- positive literal lengths only, immutable arrays only, and checked indexing + with `i32` expressions only +- direct constructors, immutable array locals, parameters, returns, calls + returning arrays, top-level tests, and `main` + +These are released experimental exp-118 fixtures. + +Current exp-117 fixture support: + +- `examples/supported/array-direct-scalars.slo` +- `examples/formatter/array-direct-scalars.slo` +- `examples/supported/array-direct-scalars-value-flow.slo` +- `examples/formatter/array-direct-scalars-value-flow.slo` +- fixed immutable arrays over direct scalar element families `i32`, `i64`, + `f64`, and `bool` +- positive literal lengths only, immutable arrays only, and checked indexing + with `i32` expressions only +- direct constructors, immutable array locals, parameters, returns, calls + returning arrays, top-level tests, and `main` + +These are released experimental exp-117 fixtures. + +Current exp-116 fixture support: + +- `examples/supported/enum-payload-direct-scalars.slo` +- `examples/formatter/enum-payload-direct-scalars.slo` +- enum declarations mixing payloadless variants and unary direct `i64`, + `f64`, `bool`, or `string` payload variants +- one payload per payload variant, with one direct payload kind per enum when + payload variants are present +- zero-argument constructors for payloadless variants and qualified unary + constructors for payload variants +- enum values through immutable locals, parameters, returns, calls, equality, + top-level tests, and `main` +- exhaustive enum `match` arms with one immutable arm-local payload binding + for payload variants + +These are released experimental exp-116 fixtures. + +Released exp-17 project contract fixture: + +- `examples/projects/enum-imports/` +- export lists may include top-level enum names in project/workspace modules +- import lists may import exported enum names +- imported enum types may be used in function signatures, immutable locals, + calls/returns, equality, exhaustive enum matches, and qualified + constructors +- the enum surface remains exactly exp-4 payloadless variants plus exp-16 + unary `i32` payload variants + +This is a released experimental exp-17 fixture. It does not claim beta +maturity or widen manifests, packages, enum payloads, generics, containers, +mutation, printing, ordering, hashing, reflection, or stable ABI/layout. + +Current exp-18 fixture support: + +- `examples/supported/enum-struct-fields.slo` +- `examples/formatter/enum-struct-fields.slo` +- direct struct fields whose types are current user-defined enum names +- struct construction with payloadless and unary `i32` enum field values +- immutable struct locals, parameters, returns, calls, tests, and `main` +- field access returning enum values +- same-enum equality and exhaustive enum `match` on field access + +This is a released experimental exp-18 fixture. It does not claim beta +maturity or widen enum payloads, containers, nested structs, mutation, +printing, ordering, hashing, reflection, import behavior, manifest schema, +package-manager behavior, or stable ABI/layout. + +Current exp-19 fixture support: + +- `examples/supported/primitive-struct-fields.slo` +- `examples/formatter/primitive-struct-fields.slo` +- direct struct fields whose types are `i32`, `bool`, or immutable `string` +- struct construction with `i32`, `bool`, and immutable `string` field values +- immutable struct locals, parameters, returns, calls, tests, and `main` +- field access returning primitive values +- bool field access in predicate/test positions +- string field equality and `std.string.len` on field access + +This is a released experimental exp-19 fixture. It does not claim beta +maturity or widen arrays, vectors, options, results, nested structs, mutation, +string ownership/cleanup guarantees, broader string operations, printing, +package/import behavior, manifest schema, generics, methods, traits, or stable +ABI/layout. + +Current exp-115 fixture support: + +- `examples/supported/composite-struct-fields.slo` +- `examples/formatter/composite-struct-fields.slo` +- direct struct field declarations using current concrete vec families +- direct struct field declarations using current concrete option/result + families +- direct struct field declarations using current known non-recursive struct + types +- coexistence with the already supported direct `i32`, `bool`, immutable + `string`, `i64`, finite `f64`, and current enum field families +- immutable struct locals, parameters, returns, calls, tests, and `main` + for structs carrying those fields +- field access returning those composite field values for use with existing + vec indexing, option/result `match`, string/number helpers, enum `match`, + and nested struct helper flow + +This is a released experimental exp-115 fixture alignment. It does not claim +beta maturity or add arrays as struct fields, field mutation, recursive/cyclic +layouts, imports, runtime names, or stable ABI/layout. + +Current exp-114 fixture support: + +- `examples/supported/composite-locals.slo` +- `examples/formatter/composite-locals.slo` +- same-type mutable whole-value `var` / `set` reassignment for `string`, + current concrete vec families, current concrete option/result families, + current known struct values, and current enum values +- fixed arrays remain immutable and field/element/payload mutation stays out + of scope + +This is a released experimental exp-114 fixture alignment. It does not claim +beta maturity or add mutable arrays, field mutation, vector element mutation, +option/result payload mutation, enum payload mutation, imports, runtime names, +or stable ABI/layout. + +Current exp-113 fixture support: + +- `examples/supported/local-variables.slo` +- `examples/formatter/local-variables.slo` +- direct mutable `var` locals declared as `bool`, `i64`, and `f64` +- same-type `set` reassignment from already promoted scalar expressions for + those declared local types +- the fixture pair retains the earlier direct `i32` local flow and exp-112 + immutable `bool` local coverage + +This is a released experimental exp-113 fixture alignment. It does not claim +beta maturity or add mutable `string` locals, vector mutation, option/result +mutation, struct or enum mutation, mixed-type assignment, imports, runtime +names, or stable ABI/layout. + +Current exp-20 fixture support: + +- `examples/supported/f64-numeric-primitive.slo` +- `examples/formatter/f64-numeric-primitive.slo` +- direct `f64` function parameters and returns +- immutable `let` locals declared as `f64` +- calls passing and returning `f64` +- decimal `f64` literals in `f64` contexts +- same-type `f64` `+`, `-`, `*`, `/`, `=`, `<`, `>`, `<=`, and `>=` +- `std.io.print_f64` +- top-level tests and `main` returning `i32` + +This is a released experimental exp-20 fixture. It does not claim beta +maturity or widen `f32`, wider/common integer types, char/bytes/decimal, +numeric casts, mixed numeric arithmetic, f64 collections, f64 enum payloads, +f64 struct fields, parsing, random floats, broader math APIs, manifest schema, +or stable ABI/layout. + +Current exp-21 fixture support: + +- `examples/supported/i64-numeric-primitive.slo` +- `examples/formatter/i64-numeric-primitive.slo` +- direct `i64` function parameters and returns +- immutable `let` locals declared as `i64` +- calls passing and returning `i64` +- explicit signed decimal `i64` literal atoms with the `i64` suffix +- same-type `i64` `+`, `-`, `*`, `/`, `=`, `<`, `>`, `<=`, and `>=` +- `std.io.print_i64` +- top-level tests and `main` returning `i32` + +This is a released experimental exp-21 fixture. It does not claim beta +maturity or widen `f32`, unsigned integers, narrower integer widths, +char/bytes/decimal, numeric casts, implicit promotion, mixed +`i32`/`i64`/`f64` arithmetic, i64 collections, i64 enum payloads, i64 struct +fields, parsing, random `i64`, broader math APIs, manifest schema, or stable +ABI/layout. + +Current exp-22 fixture support: + +- `examples/supported/numeric-widening-conversions.slo` +- `examples/formatter/numeric-widening-conversions.slo` +- `std.num.i32_to_i64 : (i32) -> i64` +- `std.num.i32_to_f64 : (i32) -> f64` +- `std.num.i64_to_f64 : (i64) -> f64` +- converted values feeding same-type arithmetic and comparison +- top-level tests and `main` returning `i32` + +This is a released experimental alpha exp-22 fixture. It does not claim beta +maturity or add implicit promotion, mixed numeric operators, narrowing or +checked conversions, cast syntax, `f32`, unsigned or narrower integer +families, numeric parse/format APIs, stable ABI/layout, manifest schema, or +standard-library breadth. + +Current exp-23 fixture support: + +- `examples/supported/checked-i64-to-i32-conversion.slo` +- `examples/formatter/checked-i64-to-i32-conversion.slo` +- `std.num.i64_to_i32_result : (i64) -> (result i32 i32)` +- `ok` results for signed `i32` range values +- `err 1` results for out-of-range values, without range-failure traps +- existing `std.result.*` observers and unwraps +- top-level tests and `main` returning `i32` + +This is a released experimental alpha exp-23 fixture. It does not claim beta +maturity or add `f64` narrowing, checked cast generics, mixed numeric +operators, `std.num.cast`, unsigned or narrower integer families, numeric +parse/format APIs, stable ABI/layout, manifest schema, or standard-library +breadth. + +Current exp-25 fixture support: + +- `examples/supported/string-parse-i64-result.slo` +- `examples/formatter/string-parse-i64-result.slo` +- `std.string.parse_i64_result : (string) -> (result i64 i32)` +- complete non-empty ASCII signed decimal `i64` parsing with optional leading + `-` +- success as `ok value` and parse/range failure as `err 1` +- existing result observation/extraction helpers over `(result i64 i32)` +- top-level tests and `main` returning `i32` + +This is a released experimental alpha exp-25 fixture. It does not claim beta +maturity or add `f64` parse, generic parse, leading `+`, whitespace trimming, +underscores, base/radix prefixes, locale-aware parsing, Unicode digit parsing, +rich parse errors, stable ABI/layout/ownership, manifest schema, or +standard-library breadth. + +Current exp-26 fixture support: + +- `examples/supported/f64-to-string.slo` +- `examples/formatter/f64-to-string.slo` +- `std.num.f64_to_string : (f64) -> string` +- finite decimal ASCII text for promoted finite `f64` values +- existing string equality, `std.string.len`, and `std.io.print_string` +- top-level tests and `main` returning `i32` + +This is a released experimental alpha exp-26 fixture. It does not claim beta +maturity or add `f32`, `f64` parse, generic format/display/interpolation, +locale/base/radix/grouping/padding/precision controls, stable NaN/infinity +text, implicit conversion, stable ABI/layout/ownership, manifest schema, or +standard-library breadth. + +Current exp-27 fixture support: + +- `examples/supported/f64-to-i32-result.slo` +- `examples/formatter/f64-to-i32-result.slo` +- `std.num.f64_to_i32_result : (f64) -> (result i32 i32)` +- `ok value` only for finite, exactly integral `f64` inputs inside the signed + `i32` range +- `err 1` for non-finite, fractional, or out-of-range input +- existing result observation/extraction helpers over `(result i32 i32)` +- top-level tests and `main` returning `i32` + +This is a released experimental alpha exp-27 fixture. It does not claim beta +maturity or add unchecked `f64` to `i32`, casts or cast syntax, generic +`cast_checked`, `f32`, unsigned or narrower integer families, `f64` parse, +mixed numeric arithmetic, numeric containers, stable ABI/layout/ownership, +manifest schema, or standard-library breadth. + +Current exp-28 fixture support: + +- `examples/supported/string-parse-f64-result.slo` +- `examples/formatter/string-parse-f64-result.slo` +- `std.string.parse_f64_result : (string) -> (result f64 i32)` +- complete finite ASCII decimal `f64` parsing +- `ok value` for `12.5` and `-0.25` +- `err 1` for ordinary parse failure, non-finite text, trailing or leading + unsupported characters, or out-of-domain input +- existing result observation/extraction helpers over `(result f64 i32)` +- top-level tests and `main` returning `i32` + +This is a released experimental alpha exp-28 fixture. It does not claim beta +maturity or add generic parse, bool/string/bytes parse, locale parsing, +Unicode digit parsing, underscores, rich parse errors, stable helper +ABI/layout/ownership, result genericity, f64 containers, mixed numeric +arithmetic, manifest schema, or standard-library breadth. + +Current exp-29 fixture support: + +- `examples/supported/numeric-struct-fields.slo` +- `examples/formatter/numeric-struct-fields.slo` +- direct immutable struct fields whose field types are exactly `i64` or `f64` +- constructors with `i64` and finite `f64` field initializers +- immutable struct local/parameter/return/call flow +- field access returning `i64` or `f64` +- existing same-type numeric arithmetic/comparison after field access +- existing numeric print/format helpers after field access +- top-level tests and `main` returning `i32` + +This is a released experimental alpha exp-29 fixture. It does not claim beta +maturity or add struct field mutation, nested structs, +arrays/vectors/options/results as fields, enum payload widening, numeric +containers, `f32`, unsigned or narrower integer families, mixed numeric +arithmetic, implicit conversions, new parse/format APIs, generic structs, +methods, traits, stable ABI/layout/ownership, manifest schema, FFI layout +claims, or standard-library breadth. + +Current exp-30 source-layout support: + +- `std/README.md` +- `std/cli.slo` +- `std/io.slo` +- `std/string.slo` +- `std/num.slo` +- `std/result.slo` +- `std/math.slo` +- `examples/formatter/std-source-layout-alpha.slo` +- flat current module names inside `std/*.slo` while repo-root `std` + import/search remains deferred +- source-authored `std/math.slo` helpers: `abs_i32`, `min_i32`, `max_i32`, + `clamp_i32`, `square_i32`, and `square_f64` + +This is a released experimental alpha exp-30 source-layout fixture. It does +not claim beta maturity or add automatic standard-library imports, replacement +of compiler-known `std.*` calls, new compiler-known `std.*` operation names, +stable standard-library APIs, manifest schema changes, broad math, generics, +traits, overloads, module/package registry behavior, or stable ABI/layout. + +Current exp-31 fixture support: + +- `examples/supported/f64-to-i64-result.slo` +- `examples/formatter/f64-to-i64-result.slo` +- `std.num.f64_to_i64_result : (f64) -> (result i64 i32)` +- `ok value` only for finite, exactly integral `f64` inputs inside the signed + `i64` range +- `err 1` for non-finite, fractional, or out-of-range input +- formatter-stable conservative out-of-range fixture value + `9223372036854776000.0` +- existing result observation/extraction helpers over `(result i64 i32)` +- `std.num.i64_to_string` over a successful `i64` payload +- top-level tests and `main` returning `i32` + +This is a released experimental alpha exp-31 fixture. It does not claim beta +maturity or add unchecked casts, unchecked `f64` to `i64`, cast syntax, +generic `cast_checked`, `f32`, unsigned or narrower integer families, mixed +numeric arithmetic, broad math, numeric containers, stable ABI/layout/ +ownership, manifest schema changes, or standard-library breadth. Detailed +edge behavior around the `f64`/`i64` limits is implementation-owned Glagol +test coverage. + +Current exp-32 source-helper support: + +- `std/math.slo` +- retained exp-30 helpers: `abs_i32`, `min_i32`, `max_i32`, `clamp_i32`, + `square_i32`, and `square_f64` +- added `i64` helpers: `abs_i64`, `min_i64`, `max_i64`, `clamp_i64`, and + `square_i64` +- added finite `f64` helpers: `abs_f64`, `min_f64`, `max_f64`, and + `clamp_f64` +- ordinary source functions using existing same-type arithmetic/comparison, + `if`, literals, calls, and explicit signatures + +This is a released experimental alpha exp-32 source fixture. It does not +claim beta maturity or add automatic standard-library imports, compiler-loaded +`std/` source, new compiler-known `std.*` operation names, broad math, +trigonometry, `sqrt`, `pow`, `f32`, unsigned or narrower integer families, +generic math, overloads, traits, mixed numeric arithmetic, numeric containers, +stable ABI/layout/ownership, manifest schema changes, or standard-library +breadth. + +Current exp-33 source-helper support: + +- `std/result.slo` +- retained i32 helpers: `is_ok_i32`, `is_err_i32`, `unwrap_ok_i32`, and + `unwrap_err_i32` +- completed concrete err wrappers: `is_err_i64`, `unwrap_err_i64`, + `is_err_string`, `unwrap_err_string`, `is_err_f64`, and `unwrap_err_f64` +- concrete fallback helpers: `unwrap_or_i32`, `unwrap_or_i64`, + `unwrap_or_string`, and `unwrap_or_f64` +- ordinary source functions over existing `std.result.is_ok`, + `std.result.is_err`, `std.result.unwrap_ok`, `std.result.unwrap_err`, `if`, + `match`, parameters, and explicit signatures + +This is a released experimental alpha exp-33 source fixture. It does not +claim beta maturity or add automatic standard-library imports, compiler-loaded +`std/` source, new compiler-known `std.*` operation names, generic +`std.result.map`, generic `std.result.unwrap_or`, `std.result.and_then`, +option helper names, new result payload families, stable ABI/layout/ +ownership, manifest schema changes, or standard-library breadth. + +Current exp-34 fixture support: + +- `examples/supported/string-parse-bool-result.slo` +- `examples/formatter/string-parse-bool-result.slo` +- `std.string.parse_bool_result : (string) -> (result bool i32)` +- exact complete ASCII lowercase `true` and `false` parsing +- `ok true` for `true` +- `ok false` for `false` +- `err 1` for empty input, uppercase or mixed-case text, leading or trailing + whitespace, numeric text, and any other text +- existing result observation/extraction helpers over the returned + `(result bool i32)` value +- top-level tests and `main` returning `i32` + +This is a released experimental alpha exp-34 fixture. It does not claim beta +maturity or add generic parse APIs, trap-based parse, bool parsing beyond +exact lowercase `true`/`false`, string/bytes parse, whitespace trimming, +case-insensitive parsing, locale/Unicode parsing, numeric boolean parsing, +rich parse errors, source-authored `std/result.slo` bool wrappers, stable +helper ABI/layout/ownership, manifest schema changes, or standard-library +breadth. + +Current exp-35 source-helper support: + +- `std/result.slo` +- retained exp-33 concrete result source helpers +- added `(result bool i32)` helpers: `is_ok_bool`, `is_err_bool`, + `unwrap_ok_bool`, `unwrap_err_bool`, and `unwrap_or_bool` +- ordinary source functions over existing compiler-supported + `std.result.is_ok`, `std.result.is_err`, `std.result.unwrap_ok`, + `std.result.unwrap_err`, `if`, parameters, and explicit signatures +- alignment with exp-34 compiler-supported bool parse result fixture flow + +This is a released experimental alpha exp-35 source fixture. It does not +claim beta maturity or add automatic standard-library imports, +compiler-loaded `std/` source, new compiler-known `std.*` operation names, +source-authored generic result helpers, generic `std.result.map`, generic +`std.result.unwrap_or`, `std.result.and_then`, option helper names, +source constructors for `(result bool i32)`, general source `match` over +`(result bool i32)` beyond compiler-supported fixture flow, stable +ABI/layout/ownership, manifest schema changes, or standard-library breadth. + +Current exp-74/exp-109 source-helper support: + +- `std/result.slo` +- retained exp-33 and exp-35 concrete result source helpers +- concrete source constructors: `ok_i32`, `err_i32`, `ok_i64`, `err_i64`, + `ok_string`, `err_string`, `ok_f64`, `err_f64`, `ok_bool`, and `err_bool` +- concrete bridge helpers: `ok_or_none_i32`, `ok_or_none_i64`, + `ok_or_none_string`, `ok_or_none_f64`, and `ok_or_none_bool` +- ordinary source functions over existing compiler-supported `ok`, `err`, + `std.result.is_ok`, `std.result.unwrap_ok`, `some`, `none`, `if`, + `match`, parameters, and explicit signatures + +This is the released experimental alpha result helper family through exp-109. +It does not claim beta maturity or add automatic standard-library imports, +compiler-loaded `std/` source, new compiler-known `std.*` operation names, +generic result helpers, broader `map`/`and_then`/`transpose`/`flatten` +packages, new payload families, stable ABI/layout/ownership, manifest schema +changes, or standard-library breadth. + +Current exp-36/exp-75/exp-95/exp-100/exp-102/exp-109 source-helper support: + +- `std/option.slo` +- concrete `(option i32)` helpers: `some_i32`, `none_i32`, `is_some_i32`, + `is_none_i32`, `unwrap_some_i32`, `unwrap_or_i32`, and `some_or_err_i32` +- concrete `(option i64)` helpers: `some_i64`, `none_i64`, `is_some_i64`, + `is_none_i64`, `unwrap_some_i64`, `unwrap_or_i64`, and `some_or_err_i64` +- concrete `(option f64)` helpers: `some_f64`, `none_f64`, `is_some_f64`, + `is_none_f64`, `unwrap_some_f64`, `unwrap_or_f64`, and `some_or_err_f64` +- concrete `(option bool)` helpers: `some_bool`, `none_bool`, `is_some_bool`, + `is_none_bool`, `unwrap_some_bool`, `unwrap_or_bool`, and + `some_or_err_bool` +- concrete `(option string)` helpers: `some_string`, `none_string`, + `is_some_string`, `is_none_string`, `unwrap_some_string`, + `unwrap_or_string`, and `some_or_err_string` +- ordinary source functions over existing compiler-supported `is_some`, + `is_none`, `unwrap_some`, `ok`, `err`, `if`, parameters, and explicit + signatures +- flat `std/*.slo` facade organization, with any future `std.slo` + aggregator/reexport layer deferred until import/search semantics exist + +This is the released experimental alpha option helper family through exp-109. +It does not +claim beta maturity or add automatic standard-library imports, repo-root +`std/` search, compiler-loaded `std/` source, new compiler-known `std.*` +operation names, generic option helpers, option payload families beyond +`(option i32)`, `(option i64)`, `(option f64)`, `(option bool)`, and +`(option string)`, stable ABI/layout/ownership, manifest schema changes, or +standard-library breadth. + +Current exp-111 stdlib/source support: + +- `std/io.slo` retains the zero-returning and value-returning print wrappers + and now also adds `read_stdin_result`, `read_stdin_option`, + `read_stdin_or`, `read_stdin_i32_result`, `read_stdin_i32_option`, + `read_stdin_i32_or_zero`, `read_stdin_i32_or`, `read_stdin_i64_result`, + `read_stdin_i64_option`, `read_stdin_i64_or_zero`, `read_stdin_i64_or`, + `read_stdin_f64_result`, `read_stdin_f64_option`, + `read_stdin_f64_or_zero`, `read_stdin_f64_or`, `read_stdin_bool_result`, + `read_stdin_bool_option`, `read_stdin_bool_or_false`, and + `read_stdin_bool_or` +- the matching explicit import fixture `examples/projects/std-import-io/` + now imports the expanded helper list and keeps stdin assertions structural + so the runner does not need to provide a specific stdin payload +- all new helpers are ordinary source wrappers over the promoted + `std.io.read_stdin_result`, `std.string.parse_*_result`, and exp-109 + `std.result.ok_or_none_*` + +Current exp-110 stdlib/source support: + +- `std/string.slo` retains `len`, `concat`, the concrete `parse_*_result` + wrappers, the zero/custom-fallback helpers, and now also adds + `parse_i32_option`, `parse_i64_option`, `parse_f64_option`, and + `parse_bool_option` +- `std/env.slo` retains `get`, `get_result`, `has`, `get_or`, the typed + `*_result` and fallback helpers, and now also adds `get_option`, + `get_i32_option`, `get_i64_option`, `get_f64_option`, and + `get_bool_option` +- `std/fs.slo` retains text read/write helpers, the typed `*_result` and + fallback helpers, and now also adds `read_text_option`, + `read_i32_option`, `read_i64_option`, `read_f64_option`, and + `read_bool_option` +- `std/process.slo` retains `argc`, `arg`, `arg_result`, `has_arg`, + `arg_or`, `arg_or_empty`, the typed `*_result` and fallback helpers, and + now also adds `arg_option`, `arg_i32_option`, `arg_i64_option`, + `arg_f64_option`, and `arg_bool_option` +- `std/cli.slo` retains `arg_text_result`, the typed `*_result` and fallback + helpers, and now also adds `arg_text_option`, `arg_i32_option`, + `arg_i64_option`, `arg_f64_option`, and `arg_bool_option` +- all new helpers are ordinary source wrappers over existing concrete result + helpers through exp-109 `std.result.ok_or_none_*` +- the matching explicit import fixtures + `examples/projects/std-import-string/`, + `examples/projects/std-import-env/`, + `examples/projects/std-import-fs/`, + `examples/projects/std-import-process/`, and + `examples/projects/std-import-cli/` + now import the expanded helper lists and keep deterministic option/result + and fallback coverage + +This is the current released exp-111 stdlib/source contract summary, with the +immediately previous exp-110 source package still listed above. It does not +claim beta maturity or add compiler-known std names, generics, new runtime +calls beyond the already promoted stdin-result lane, line/binary/async stdin +APIs, vec work, stable ABI/layout/ownership, or optimizer guarantees. + +Current exp-125 Slovo-side target: + +- `std/result.slo` stages concrete `(result u32 i32)` and `(result u64 i32)` + constructor, observer, unwrap, fallback, and option-bridge helpers +- `std/option.slo` stages concrete `(option u32)` and `(option u64)` + constructor, observer, unwrap, fallback, and result-bridge helpers +- `std/string.slo` stages `parse_u32_*` and `parse_u64_*` result/option/ + fallback helper lanes +- `std/num.slo` stages `u32_to_string` and `u64_to_string` +- `std/io.slo`, `std/env.slo`, `std/fs.slo`, `std/process.slo`, and + `std/cli.slo` stage matching `u32`/`u64` helper parity over the concrete + family pattern already used for `i32`, `i64`, `f64`, `bool`, and `string` +- the matching Slovo fixtures are `u32-numeric-primitive.slo`, + `u64-numeric-primitive.slo`, `unsigned-integer-to-string.slo`, + `string-parse-u32-result.slo`, `string-parse-u64-result.slo`, and the + widened `examples/projects/std-import-*` module fixtures + +This is a current Slovo-side contract target only. It does not claim beta +maturity, released compiler support, implicit promotion, mixed +signed/unsigned arithmetic, broader cast families, unsigned widths beyond +`u32`/`u64`, or stable ABI/layout/ownership. + +Current exp-37 source-facade support: + +- `std/time.slo` +- narrow time wrappers: `monotonic_ms` and `sleep_ms_zero` +- ordinary source functions over existing compiler-supported + `std.time.monotonic_ms`, `std.time.sleep_ms`, parameters, sequential body + forms, and explicit signatures +- flat `std/*.slo` facade organization, with any future `std.slo` + aggregator/reexport layer deferred until import/search semantics exist + +This is a released experimental alpha exp-37 source fixture. It does not +claim beta maturity or add automatic standard-library imports, repo-root +`std/` search, compiler-loaded `std/` source, new compiler-known `std.*` +operation names, wall-clock/calendar/timezone APIs, high-resolution timers, +async timers, cancellation, scheduling guarantees, stable ABI/layout/ownership, +manifest schema changes, or standard-library breadth. + +Current exp-38 source-facade support: + +- `std/random.slo` +- `std/env.slo` +- `std/fs.slo` +- narrow host/runtime wrappers for `std.random.i32`, `std.env.get`, + `std.env.get_result`, `std.fs.read_text`, `std.fs.read_text_result`, + `std.fs.write_text`, and `std.fs.write_text_result` +- flat `std/*.slo` facade organization, with any future `std.slo` + aggregator/reexport layer deferred until import/search semantics exist + +This is a released experimental alpha exp-38 source fixture. It does not +claim beta maturity or add automatic standard-library imports, repo-root +`std/` search, compiler-loaded `std/` source, new compiler-known `std.*` +operation names, random seed/range/bytes/float/UUID/crypto APIs, environment +mutation/enumeration, rich host error ADTs, binary/directory/streaming/async +filesystem APIs, stable ABI/layout/ownership, manifest schema changes, or +standard-library breadth. + +Current exp-39 source-helper support: + +- `std/math.slo` +- retained exp-32 helpers: `abs`, `min`, `max`, `clamp`, and `square` for + the currently staged `i32`, `i64`, and finite `f64` families +- added helpers for each family: `neg`, `cube`, `is_zero`, `is_positive`, + `is_negative`, and inclusive `in_range` +- ordinary source functions using existing same-type arithmetic/comparison, + `if`, literals, calls, and explicit signatures +- matching Glagol benchmark scaffold gates for local `math-loop` timing + comparisons only, with no performance threshold in the language contract + +This is a released experimental alpha exp-39 source fixture. It does not +claim beta maturity or add automatic standard-library imports, repo-root +`std/` search, compiler-loaded `std/` source, new compiler-known `std.*` +operation names, trigonometry, `sqrt`, `pow`, modulo, bit operations, +generic math, overloads, traits, mixed numeric arithmetic, optimizer +guarantees, benchmark thresholds, stable ABI/layout/ownership, manifest schema +changes, or standard-library breadth. + +Current exp-40 governance/tooling support: + +- project licensing recorded as `MIT OR Apache-2.0` +- release naming criteria documented in `.llm/ALPHA_BETA_RELEASE_CRITERIA.md` +- Glagol benchmark-suite gates aligned for `math-loop`, `branch-loop`, + `parse-loop`, `array-index-loop`, `string-eq-loop`, + `array-struct-field-loop`, and `enum-struct-payload-loop` +- Slovo, C, Rust, Python, and Clojure benchmark source slots, with local-only + timing comparisons and no release threshold; Common Lisp/SBCL is also part + of the current paired benchmark publication surface + +This is a released experimental alpha exp-40 governance/tooling fixture. It +does not claim beta maturity or add source syntax, type forms, +standard-library APIs, optimizer guarantees, benchmark thresholds, +cross-machine performance claims, stable ABI/layout/ownership, package +registry behavior, or language-surface changes. + +Current exp-41 governance/tooling support: + +- Glagol benchmark-suite gates include Common Lisp/SBCL beside Clojure as a + second Lisp-family comparison target +- benchmark timing is explicitly cold-process local-machine evidence only +- Clojure timings include JVM and Clojure startup; Common Lisp timings include + SBCL script startup +- the current published benchmark-suite kernel inventory is + `math-loop`, `branch-loop`, `parse-loop`, `array-index-loop`, + `string-eq-loop`, `array-struct-field-loop`, and + `enum-struct-payload-loop` + +This is a released experimental alpha exp-41 governance/tooling fixture. It +does not claim beta maturity or add source syntax, type forms, +standard-library APIs, optimizer guarantees, benchmark thresholds, +cross-machine performance claims, stable ABI/layout/ownership, package +registry behavior, or language-surface changes. + +Current exp-42 governance/tooling support: + +- Glagol benchmark-suite gates separate `cold-process` and `hot-loop` timing + modes +- hot-loop mode uses a larger runtime-supplied loop count and reports total + time plus normalized median time for the base loop count +- the paired benchmark-suite kernel inventory now covers arithmetic, branching, + parsing, checked fixed-array indexing, exact string equality, direct + fixed-array struct-field access, and unary enum payload matching over + non-recursive struct payloads +- Slovo language surface remains unchanged; no high-resolution timing API is + promoted + +This is a released experimental alpha exp-42 governance/tooling fixture. It +does not claim beta maturity or add source syntax, type forms, +standard-library APIs, high-resolution timing APIs, optimizer guarantees, +benchmark thresholds, cross-machine performance claims, stable +ABI/layout/ownership, package registry behavior, or language-surface changes. + +Current exp-43 documentation/tooling support: + +- `docs/SLOVO_WHITEPAPER.md` records the current language state, Lisp-family + comparison, local benchmark evidence, and path to `1.0.0-beta` +- generated PDF artifacts publish the whitepaper and language manifest +- `.llm/skills/slovo-beta-language-design/SKILL.md` records the + manifest-first beta language-design workflow + +This is a released experimental alpha exp-43 documentation/tooling fixture. +It does not claim beta maturity or add source syntax, type forms, +standard-library APIs, optimizer guarantees, benchmark thresholds, +cross-machine performance claims, stable ABI/layout/ownership, package +registry behavior, or language-surface changes. + +Current exp-44 language/tooling support: + +- `std/math.slo` carries its own export list for helper imports +- `examples/projects/std-import-math/` imports `(import std.math (...))` + without a local copied `math.slo` +- Glagol resolves explicit project-mode `std.math` imports to the repo-root + standard-library source file + +This is a released experimental alpha exp-44 source-search fixture. It does +not claim beta maturity or add automatic standard-library imports, broad +standard-library APIs, package registry behavior, workspace/package std +imports, optimizer guarantees, stable ABI/layout/ownership, or a `std.slo` +aggregator. + +Current exp-45 language/tooling support: + +- `std/result.slo` and `std/option.slo` carry their own export lists +- `examples/projects/std-import-result/` imports `(import std.result (...))` + without a local copied `result.slo` +- `examples/projects/std-import-option/` imports `(import std.option (...))` + without a local copied `option.slo` +- Glagol resolves these explicit project-mode imports to the repo-root + standard-library source files + +This is a released experimental alpha exp-45 source-search fixture. It does +not claim beta maturity or add automatic standard-library imports, generic +result or option helpers, broad standard-library APIs, package registry +behavior, workspace/package std imports, optimizer guarantees, stable +ABI/layout/ownership, or a `std.slo` aggregator. + +Current exp-46 language/tooling support: + +- `examples/workspaces/std-import-option/` imports `(import std.option (...))` + from a workspace package without a local copied `option.slo` +- Glagol resolves explicit workspace-package `std.` imports to the + repo-root standard-library source files + +This is a released experimental alpha exp-46 source-search fixture. It does +not claim beta maturity or add automatic standard-library imports, workspace +dependency syntax for std, package registry behavior, installed toolchain +stdlib paths, generic option helpers, broad standard-library APIs, optimizer +guarantees, stable ABI/layout/ownership, or a `std.slo` aggregator. + +Current exp-47 language/tooling support: + +- `std/time.slo`, `std/random.slo`, `std/env.slo`, and `std/fs.slo` carry + their own export lists for helper imports +- `examples/projects/std-import-time/` imports `(import std.time (...))` + without a local copied `time.slo` +- `examples/projects/std-import-random/` imports `(import std.random (...))` + without a local copied `random.slo` +- `examples/projects/std-import-env/` imports `(import std.env (...))` + without a local copied `env.slo` +- `examples/projects/std-import-fs/` imports `(import std.fs (...))` + without a local copied `fs.slo` + +This is a released experimental alpha exp-47 source-search fixture. It does +not claim beta maturity or add automatic standard-library imports, installed +toolchain stdlib paths, broad host APIs, optimizer guarantees, stable +ABI/layout/ownership, package registry behavior, or a `std.slo` aggregator. + +Current exp-48 language/tooling support: + +- `std/string.slo` and `std/num.slo` carry their own export lists for helper + imports +- `examples/projects/std-import-string/` imports `(import std.string (...))` + without a local copied `string.slo` +- `examples/projects/std-import-num/` imports `(import std.num (...))` + without a local copied `num.slo` + +This is a released experimental alpha exp-48 source-search fixture. It does +not claim beta maturity or add automatic standard-library imports, installed +toolchain stdlib paths, generic parse/format APIs, broad numeric casts, +optimizer guarantees, stable ABI/layout/ownership, package registry behavior, +or a `std.slo` aggregator. + +Current exp-49 language/tooling support: + +- `std/io.slo` carries its own export list for helper imports +- `examples/projects/std-import-io/` imports `(import std.io (...))` + without a local copied `io.slo` + +This is a released experimental alpha exp-49 source-search fixture. It does +not claim beta maturity or add automatic standard-library imports, installed +toolchain stdlib paths, broad IO APIs, formatted output APIs, optimizer +guarantees, stable ABI/layout/ownership, package registry behavior, or a +`std.slo` aggregator. + +Current exp-50 language/tooling support: + +- explicit standard-source imports may discover staged `std/*.slo` modules + from installed `share/slovo/std` toolchain layouts +- no new `std/*.slo` module, helper, or compiler-known runtime name is added + +This is a released experimental alpha exp-50 discovery fixture. It does not +claim beta maturity or add automatic standard-library imports, package +registry behavior, lockfiles, package std dependencies, stable install layout +guarantees, optimizer guarantees, stable ABI/layout/ownership, or a +`std.slo` aggregator. + +Current exp-51 language/tooling support: + +- `SLOVO_STD_PATH` may name an ordered OS path list of standard-library roots +- the first root containing the requested module file wins + +This is a released experimental alpha exp-51 discovery fixture. It does not +claim beta maturity or add automatic standard-library imports, package +registry behavior, lockfiles, semantic version solving, stable package +manager behavior, optimizer guarantees, stable ABI/layout/ownership, or a +`std.slo` aggregator. + +Current exp-52 language/tooling support: + +- `std/process.slo` carries its own export list for helper imports +- `examples/projects/std-import-process/` imports `(import std.process (...))` + without a local copied `process.slo` + +This is a released experimental alpha exp-52 source-search fixture. It does +not claim beta maturity or add automatic standard-library imports, package +registry behavior, process spawning, exit/status control, current-directory +APIs, signal handling, optimizer guarantees, stable ABI/layout/ownership, or a +`std.slo` aggregator. + +Current exp-53 language/tooling support: + +- `std/cli.slo` carries its own export list for helper imports +- `std/cli.slo` imports `std.process` and `std.string` as ordinary standard + source modules +- `examples/projects/std-import-cli/` imports `(import std.cli (...))` + without a local copied `cli.slo` + +This is a released experimental alpha exp-53 source-search fixture. It does +not claim beta maturity or add automatic standard-library imports, package +registry behavior, shell parsing, option/flag parsing, subcommands, +environment-backed configuration, optimizer guarantees, stable +ABI/layout/ownership, or a `std.slo` aggregator. + +Current exp-54 language/tooling support: + +- `std/cli.slo` adds `arg_i64_result` and `arg_i64_or_zero` +- `std/cli.slo` adds `arg_f64_result`, `arg_f64_or_zero`, `arg_bool_result`, + and `arg_bool_or_false` +- `examples/projects/std-import-cli/` imports the expanded helper list and + keeps deterministic tests on the currently source-supported result families + +This is a released experimental alpha exp-54 source-facade fixture. It does +not claim beta maturity or add automatic standard-library imports, package +registry behavior, shell parsing, option/flag parsing, subcommands, +environment-backed configuration, optimizer guarantees, stable +ABI/layout/ownership, or a `std.slo` aggregator. + +Current exp-101 stdlib/source support: + +- `std/vec_string.slo` now stages the concrete `(vec string)` facade through + the exp-99 baseline plus the exp-101 option-query and transform package +- the baseline direct wrappers remain `empty`, `append`, `len`, and `at` +- the baseline builder helpers remain `singleton`, `append2`, `append3`, + `pair`, and `triple` +- the baseline query helpers remain `is_empty`, `index_or`, `first_or`, and + `last_or` +- the new option-query helpers are `index_option`, `first_option`, + `last_option`, `index_of_option`, and `last_index_of_option` +- the new transform helpers are `concat`, `take`, `drop`, `reverse`, and + `subvec` +- the simple real-program helpers remain `contains` and `count_of` +- the helper lane stays source-authored, recursive, and immutable over only + `std.vec.string.empty`, `std.vec.string.append`, `std.vec.string.len`, and + `std.vec.string.index` +- the explicit project fixture `examples/projects/std-import-vec_string/` now + exercises deterministic baseline, option-query, transform, subvec, and + real-program coverage + +This is the current released exp-101 stdlib/source contract. It does not +claim beta maturity or add compiler-known std names, generics, vec edit +helpers, prefix/suffix helpers, nested vecs, mutating/capacity APIs, stable +ABI/layout/ownership, or optimizer guarantees. + +Current exp-108 stdlib/source support: + +- `std/vec_string.slo` retains the exp-99 baseline and exp-101 option-query + plus transform surfaces, exp-107 edit helpers, and now also ships + `starts_with`, `without_prefix`, `ends_with`, and `without_suffix` +- `std/vec_f64.slo` retains the exp-103 baseline, exp-105 transform, and + exp-106 option-query surfaces, exp-107 edit helpers, and now also ships + the same four prefix/suffix helpers +- `std/vec_bool.slo` retains the exp-104 baseline, exp-105 transform, and + exp-106 option-query surfaces, exp-107 edit helpers, and now also ships + the same four prefix/suffix helpers +- all three helper lanes stay source-authored, recursive, and immutable over + only their existing four runtime calls per family plus already promoted + concrete option families where earlier helpers need them +- the explicit project fixtures + `examples/projects/std-import-vec_string/`, + `examples/projects/std-import-vec_f64/`, and + `examples/projects/std-import-vec_bool/` now freeze deterministic direct, + builder, query, option-query, prefix/suffix, transform, subvec, edit, and + real-program coverage + +This is the current released exp-108 stdlib/source contract. It does not +claim beta maturity or add compiler-known std names, generics, sorting, +mapping, filtering, nested vecs, mutating/capacity APIs, stable +ABI/layout/ownership, or optimizer guarantees. + +Current exp-93 stdlib/source support: + +- `std/vec_i32.slo` retains the exp-76 direct, builder, query, option-query, + simple real-program, transform, generated constructor, range, replace, + remove, insert, subvec, remove-range, replace-range, insert-range, + starts-with, ends-with, without-suffix, and without-prefix helper surface +- `std/vec_i32.slo` adds exactly one new public helper, `count_of` +- `count_of ((values (vec i32)) (target i32)) -> i32` returns the number of + elements in `values` equal to `target` +- `count_of (empty) target` returns `0` +- if no element equals `target`, `count_of` returns `0` +- repeated matches are counted exactly +- the helper leaves both source vectors unchanged +- the implementation is an ordinary source helper over existing `len`, `at`, + equality, and `while` +- `examples/projects/std-import-vec_i32/` imports the expanded helper list + and keeps deterministic empty, repeated-match, single-match, no-match, and + unchanged-source tests + +This is the current released exp-93 stdlib/source contract. It does not +claim beta maturity or add automatic standard-library imports, +compiler-loaded standard-library source, new compiler-known `std.*` runtime +names, mutable vec locals, generic collections, slice/view types, vector +payload widening, broader copied subvector/range-edit helper families beyond +`insert_range`, `replace_range`, `remove_range`, and `subvec`, public +insert/remove/edit families beyond `insert_range`, `replace_range`, +`remove_range`, `subvec`, `insert_at`, `replace_at`, and `remove_at`, +mutating vec APIs, capacity/reserve/shrink, sorting, mapping, filtering, +iterators, optimizer guarantees, or stable ABI/layout/ownership. + +Current exp-87 stdlib/source support: + +- `std/vec_i32.slo` retains the exp-76 direct, builder, query, option-query, + simple real-program, transform, generated constructor, range, replace, + remove, insert, subvec, and remove-range helper surface +- `std/vec_i32.slo` adds exactly one new public helper, `insert_range` +- `insert_range ((values (vec i32)) (position i32) (inserted (vec i32))) -> + (vec i32)` inserts all of `inserted` into `values` at a valid `position` +- if `position < 0` or `position > len(values)`, `insert_range` returns + `values` unchanged +- if `0 <= position < len(values)`, `insert_range` inserts all of `inserted` + before the current element at `position` +- if `position == len(values)`, `insert_range` appends `inserted` at the end +- the helper preserves the order of both vectors and leaves both source + vectors unchanged +- the result length is `len(values) + len(inserted)` for valid positions +- the implementation is an ordinary compositional source helper over existing + `take`, `drop`, and `concat` +- `examples/projects/std-import-vec_i32/` imports the expanded helper list + and keeps deterministic middle insertion, append-at-end insertion, + negative-position, out-of-range-position, and unchanged-source tests + +This is the current released exp-87 stdlib/source contract. It does not claim +beta maturity or add automatic standard-library imports, +compiler-loaded standard-library source, new compiler-known `std.*` runtime +names, mutable vec locals, generic collections, slice/view types, vector +payload widening, broader copied subvector/range-edit helper families beyond +`insert_range`, `remove_range`, and `subvec`, public insert/remove/edit +families beyond `insert_range`, `remove_range`, `subvec`, `insert_at`, +`replace_at`, and `remove_at`, mutating vec APIs, capacity/reserve/shrink, +sorting, mapping, filtering, iterators, optimizer guarantees, or stable +ABI/layout/ownership. + +Current exp-86 stdlib/source support: + +- `std/vec_i32.slo` retains the exp-76 direct, builder, query, option-query, + simple real-program, transform, generated constructor, range, replace, + remove, insert, and subvec helper surface +- `std/vec_i32.slo` adds exactly one new public helper, `remove_range` +- `remove_range ((values (vec i32)) (start i32) (end_exclusive i32)) -> + (vec i32)` removes the half-open range `[start, end_exclusive)` from + `values` +- if `start < 0`, `end_exclusive <= start`, or `start >= len(values)`, + `remove_range` returns `values` unchanged +- if `end_exclusive >= len(values)`, `remove_range` removes the tail from + `start` +- the helper preserves the order of the remaining elements and leaves the + source vector unchanged +- the implementation is an ordinary compositional source helper over existing + `take`, `drop`, and `concat` +- `examples/projects/std-import-vec_i32/` imports the expanded helper list + and keeps deterministic middle-range removal, tail removal, + `end_exclusive <= start`, out-of-range-start, negative-start, and + unchanged-source tests + +This is the current released exp-86 stdlib/source contract. It does not claim +beta maturity or add automatic standard-library imports, +compiler-loaded standard-library source, new compiler-known `std.*` runtime +names, mutable vec locals, generic collections, slice/view types, vector +payload widening, broader subvector/range-removal helper families beyond +`remove_range` and `subvec`, public insert/remove/edit families beyond +`remove_range`, `subvec`, `insert_at`, `replace_at`, and `remove_at`, +mutating vec APIs, capacity/reserve/shrink, sorting, mapping, filtering, +iterators, optimizer guarantees, or stable ABI/layout/ownership. + +Current exp-85 stdlib/source support: + +- `std/vec_i32.slo` retains the exp-76 direct, builder, query, option-query, + simple real-program, transform, generated constructor, range, replace, + remove, and insert helper surface +- `std/vec_i32.slo` adds exactly one new public helper, `subvec` +- `subvec ((values (vec i32)) (start i32) (end_exclusive i32)) -> (vec i32)` + returns a copied contiguous subvector `[start, end_exclusive)` +- if `start < 0`, `end_exclusive <= start`, or `start >= len(values)`, + `subvec` returns `(empty)` +- if `end_exclusive > len(values)`, `subvec` returns the remaining tail from + `start` +- the helper preserves source order and leaves the source vector unchanged +- the helper is an ordinary copied vec helper, not a slice/view type +- the implementation is an ordinary compositional source helper over existing + `take` and `drop` +- `examples/projects/std-import-vec_i32/` imports the expanded helper list + and keeps deterministic middle-range, tail-truncation, empty-range, + out-of-range, negative-start, and unchanged-source tests + +This is the current released exp-85 stdlib/source contract. It does not claim +beta maturity or add automatic standard-library imports, +compiler-loaded standard-library source, new compiler-known `std.*` runtime +names, mutable vec locals, generic collections, slice/view types, vector +payload widening, broader copied subvector/window helper families beyond +`subvec`, public insert/remove/edit families beyond `subvec`, `insert_at`, +`replace_at`, and `remove_at`, mutating vec APIs, capacity/reserve/shrink, +sorting, mapping, filtering, iterators, optimizer guarantees, or stable +ABI/layout/ownership. + +Current exp-84 stdlib/source support: + +- `std/vec_i32.slo` retains the exp-76 direct, builder, query, option-query, + simple real-program, transform, generated constructor, range, replace, and + remove helper surface +- `std/vec_i32.slo` adds exactly one new public helper, `insert_at` +- `insert_at ((values (vec i32)) (position i32) (value i32)) -> (vec i32)` + returns a new vector for valid insertions while preserving source order +- if `0 <= position < len(values)`, `insert_at` inserts `value` before the + current element at `position` +- if `position == len(values)`, `insert_at` appends `value` at the end +- if the insertion is valid, the result length is `len(values) + 1` +- if `position < 0` or `position > len(values)`, `insert_at` returns + `values` unchanged +- the helper does not mutate the source vector +- the implementation is an ordinary compositional source helper over existing + `append`, `take`, `drop`, and `concat` +- `examples/projects/std-import-vec_i32/` imports the expanded helper list + and keeps deterministic middle-insert, append-at-end, negative, and + out-of-range insertion tests + +This is the current released exp-84 stdlib/source contract. It does not claim +beta maturity or add automatic standard-library imports, +compiler-loaded standard-library source, new compiler-known `std.*` runtime +names, mutable vec locals, generic collections, slice types, vector payload +widening, public insert/remove/edit families beyond `insert_at`, +`replace_at`, and `remove_at`, mutating vec APIs, capacity/reserve/shrink, +sorting, mapping, filtering, iterators, optimizer guarantees, or stable +ABI/layout/ownership. + +Current exp-82 stdlib/source support: + +- `std/vec_i32.slo` retains the exp-76 direct, builder, query, option-query, + simple real-program, transform, generated constructor, and range helper + surface +- `std/vec_i32.slo` adds exactly one new public helper, `replace_at` +- `replace_at ((values (vec i32)) (position i32) (replacement i32)) -> + (vec i32)` returns a new vector with the same length and order as `values` + except that the in-range `position` slot becomes `replacement` +- if `position < 0` or `position >= len(values)`, `replace_at` returns + `values` unchanged +- the helper does not mutate the source vector +- the implementation is an ordinary compositional source helper over existing + `append`, `take`, `drop`, and `concat` +- `examples/projects/std-import-vec_i32/` imports the expanded helper list + and keeps deterministic in-range, negative, and out-of-range replacement + tests + +This is the current released exp-82 stdlib/source contract. It does not claim +beta maturity or add automatic standard-library imports, +compiler-loaded standard-library source, new compiler-known `std.*` runtime +names, mutable vec locals, generic collections, slice types, vector payload +widening, public insert/remove/edit families beyond `replace_at`, mutating +vec APIs, capacity/reserve/shrink, sorting, mapping, filtering, iterators, +optimizer guarantees, or stable ABI/layout/ownership. + +Current exp-81 stdlib/source support: + +- `std/vec_i32.slo` retains the exp-76 direct, builder, query, option-query, + simple real-program, transform, and generated constructor helper surface +- `std/vec_i32.slo` adds exactly one new public helper, `range` +- `range ((start i32) (end_exclusive i32)) -> (vec i32)` produces an + ascending half-open sequence from `start` up to but excluding + `end_exclusive` +- negative bounds are allowed, and `range` returns an empty vector when + `end_exclusive <= start` +- generation stays private through an ordinary recursive source helper over + existing `append` and `empty` vec facade operations +- `range_from_zero` remains public and may delegate to `range 0 count` +- `examples/projects/std-import-vec_i32/` imports the expanded helper list + and keeps deterministic negative, empty, and positive ascending tests + +This is the current released exp-81 stdlib/source contract. It does not claim +beta maturity or add automatic standard-library imports, +compiler-loaded standard-library source, new compiler-known `std.*` runtime +names, mutable vec locals, generic collections, slice types, vector payload +widening, descending or stepped range variants, inclusive range variants, +edit helpers, mutating vec APIs, capacity/reserve/shrink, sorting, mapping, +filtering, iterators, optimizer guarantees, or stable ABI/layout/ownership. + +Current exp-80 stdlib/source support: + +- `std/vec_i32.slo` retains the exp-76 direct, builder, query, option-query, + simple real-program, and transform helper surface +- `std/vec_i32.slo` adds generated constructor helpers `repeat` and + `range_from_zero` +- the new helpers are ordinary recursive source helpers over existing + `append` and `empty` vec facade operations +- `repeat` returns an empty vector when `count <= 0` +- `range_from_zero` returns `0..count` with an exclusive upper bound and + returns an empty vector when `count <= 0` +- `examples/projects/std-import-vec_i32/` imports the expanded helper list + and keeps deterministic explicit standard-source tests + +This is a released experimental alpha exp-80 stdlib/source fixture. It does +not claim beta maturity or add automatic standard-library imports, +compiler-loaded standard-library source, new compiler-known `std.*` runtime +names, mutable vec locals, generic collections, slice types, vector payload +widening, mutating vec APIs, capacity/reserve/shrink, sorting, mapping, +filtering, iterators, optimizer guarantees, or stable ABI/layout/ownership. + +Current exp-78 stdlib/source support: + +- `std/cli.slo` keeps the exp-72 helper list unchanged +- `std/cli.slo` keeps `std.process` and `std.string` as its only direct + standard-source dependencies +- the Slovo-side contract now explicitly freezes this facade for the sibling + Glagol local-source gate +- no source edit to `std/cli.slo` is required for this release + +This is a released experimental alpha exp-78 contract-alignment fixture. It +does not claim beta maturity or add automatic standard-library imports, +compiler-loaded standard-library source, package registry behavior, shell +parsing, option/flag parsing, subcommands, environment-backed configuration, +optimizer guarantees, stable ABI/layout/ownership, or a `std.slo` +aggregator. + +Current exp-55 language/tooling support: + +- `examples/supported/result-f64-bool-match.slo` +- source constructors for `(result f64 i32)` and `(result bool i32)` +- source `match` with `f64` and `bool` ok payload bindings and `i32` err + payload bindings +- `std.cli` uses this support to propagate missing argument indexes for all + typed helper result families + +This is a released experimental alpha exp-55 source-flow fixture. It does not +claim beta maturity or add generic result types, result payload families +beyond the already promoted concrete families, generic result combinators, +optimizer guarantees, or stable ABI/layout/ownership. + +Current exp-60 stdlib/source support: + +- `std/string.slo` +- retained `len`, `concat`, and concrete `parse_*_result` wrappers +- added `parse_i32_or_zero`, `parse_i64_or_zero`, `parse_f64_or_zero`, and + `parse_bool_or_false` +- ordinary source `match` over existing concrete `i32`, `i64`, `f64`, and + `bool` parse result families +- `examples/projects/std-import-string/` imports the expanded helper list and + keeps deterministic tests on both direct parse results and fallback helpers + +This is a released experimental alpha exp-60 stdlib/source fixture. It does +not claim beta maturity or add automatic standard-library imports, +compiler-loaded `std/` source, generic parse helpers, whitespace trimming, +case-insensitive bool parsing, rich parse errors, optimizer guarantees, or +stable ABI/layout/ownership. + +Current exp-61 stdlib/source support: + +- `std/process.slo` +- retained `argc`, `arg`, `arg_result`, and `has_arg` +- added `arg_or` and `arg_or_empty` +- ordinary source `match` over the existing `(result string i32)` process + argument family +- `examples/projects/std-import-process/` imports the expanded helper list and + keeps deterministic tests on both missing-index and present-index flows + +This is a released experimental alpha exp-61 stdlib/source fixture. It does +not claim beta maturity or add automatic standard-library imports, +compiler-loaded `std/` source, shell parsing, option/flag parsing, +subcommands, process spawning, exit/status control, rich host errors, +optimizer guarantees, or stable ABI/layout/ownership. + +Current exp-56 language/stdlib support: + +- `examples/supported/integer-remainder.slo` +- `examples/formatter/integer-remainder.slo` +- `%` binary operator over same-width `i32` and `i64` operands +- formatter/checker/test-runner/LLVM support for integer signed remainder +- `std/math.slo` helpers: `rem_i32`, `is_even_i32`, `is_odd_i32`, + `rem_i64`, `is_even_i64`, and `is_odd_i64` +- explicit `std.math` import fixture coverage for the expanded helper list + +This is a released experimental alpha exp-56 numeric/stdlib fixture. It does +not claim beta maturity or add floating-point remainder, Euclidean modulo, +unsigned arithmetic, bit operations, generic math, mixed numeric arithmetic, +optimizer guarantees, or stable ABI/layout/ownership. + +Current exp-57 language/stdlib support: + +- `examples/supported/integer-bitwise.slo` +- `examples/formatter/integer-bitwise.slo` +- `bit_and`, `bit_or`, and `bit_xor` binary heads over same-width `i32` and + `i64` operands +- formatter/checker/test-runner/LLVM support for integer bitwise operations +- `std/math.slo` helpers: `bit_and_i32`, `bit_or_i32`, `bit_xor_i32`, + `bit_and_i64`, `bit_or_i64`, and `bit_xor_i64` + +This is a released experimental alpha exp-57 numeric/stdlib fixture. It does +not claim beta maturity or add shifts, bit-not, unsigned arithmetic, +bit-width-specific integer families, floating-point bitwise operations, +generic math, mixed numeric arithmetic, optimizer guarantees, or stable +ABI/layout/ownership. + +Current exp-123 documentation/tooling support: + +- `docs/SLOVO_WHITEPAPER.md` refreshed to the current exp-121 language surface + and exp-123 publication baseline +- benchmark methodology widened from seven kernels to nine: + `math-loop`, `branch-loop`, `parse-loop`, `array-index-loop`, + `string-eq-loop`, `array-struct-field-loop`, `enum-struct-payload-loop`, + `vec-i32-index-loop`, and `vec-string-eq-loop` +- paired same-machine publication tables are widened from seven rows to nine + in the controller-owned benchmark refresh pass +- generated PDF artifacts remain published through + `scripts/render-doc-pdfs.sh` +- compatibility `WHITEPAPER.pdf` remains mirrored from the canonical docs PDF +- explicit release metadata keeps exp-121 as the latest language-surface slice + while exp-123 is the current publication baseline + +This is a released experimental alpha exp-123 documentation/tooling fixture. +It does not claim beta maturity or add source syntax, type forms, +standard-library APIs, optimizer guarantees, benchmark thresholds, +cross-machine performance claims, stable ABI/layout/ownership, package +registry behavior, or language-surface changes. + +Current exp-58 language support: + +- `examples/supported/boolean-logic.slo` +- `examples/formatter/boolean-logic.slo` +- `(and left right)` and `(or left right)` as short-circuiting boolean forms +- `(not value)` as a boolean negation form +- lowering through existing `if`, so no new LLVM primitive is required + +This is a released experimental alpha exp-58 boolean logic fixture. It does +not claim beta maturity or add truthiness, variadic boolean operators, +pattern guards, macro expansion, optimizer guarantees, or stable +ABI/layout/ownership. + +Current exp-24 fixture support: + +- `examples/supported/integer-to-string.slo` +- `examples/formatter/integer-to-string.slo` +- `std.num.i32_to_string : (i32) -> string` +- `std.num.i64_to_string : (i64) -> string` +- decimal signed ASCII strings with `-` only for negative values and no + leading `+` +- existing string equality, `std.string.len`, and `std.io.print_string` +- top-level tests and `main` returning `i32` + +This is a released experimental alpha exp-24 fixture. It does not claim beta +maturity or add `f64` formatting, parse APIs, locale/base/radix/grouping or +padding controls, generic format/display traits, implicit conversions, stable +ABI/layout and ownership, manifest schema, or standard-library breadth. + +Current exp-5 fixture support: + +- `examples/workspaces/exp-5-local/` +- one workspace manifest with explicit local members +- two package manifests with package name/version metadata +- one local path dependency +- one package-qualified import +- deterministic local package graph recording in artifact manifests + +These are current compiler-supported exp-5 fixtures. + +Current compiler-supported exp-6 fixture: + +- `examples/ffi/exp-6-c-add/` +- top-level imported C function declaration for `c_add` +- `i32`-only scalar parameters and `i32` return +- lexical `(unsafe ...)` required at every imported C call site +- safe Slovo wrapper around `(unsafe (c_add 40 2))` +- fixture-scoped native C companion source, with no stable ABI/layout promise + +These are current compiler-supported exp-6 fixtures after matching Slovo and +Glagol exp-6 gates. + +v1 scope-freeze decisions: + +- struct field/value mutation remains deferred beyond v1 unless explicitly + promoted later +- option/result mapping, equality, printing, payloads and extraction beyond + the explicitly promoted `(result i32 i32)`, `(result string i32)`, and + `(result i64 i32)` concrete slices, nesting, containers, generic payloads, + payload ADTs, and user-catchable exceptions remain deferred unless + explicitly promoted later +- unit printing is unsupported in v1.2; `print_unit` remains deferred +- `unit` remains an internal builtin result type for supported unit-producing + body forms; user-declared or stored `unit` values remain unsupported +- standard-runtime printing beyond v1.5 `std.io.print_i32`, + `std.io.print_string`, and `std.io.print_bool`, exp-20 + `std.io.print_f64`, and exp-21 `std.io.print_i64` remains deferred +- `std.io.print_unit`, host IO beyond the exp-3 slice, exp-10 `*_result` + slice, and exp-12 stdin-result slice, parsing beyond exp-13 + `std.string.parse_i32_result`, exp-25 `std.string.parse_i64_result`, and + exp-28 `std.string.parse_f64_result`, plus exp-34 exact lowercase + `std.string.parse_bool_result`, + time, randomness beyond exp-11, user-visible allocation, integer formatting + beyond exp-24, f64 formatting beyond exp-26 finite text, + locale/base/radix/grouping/padding formatting controls, user-defined + standard modules, overloading, generic APIs, + Unicode length or digit semantics, and ABI/layout promises remain deferred +- lexical `unsafe` remains the only accepted unsafe boundary; v1.6 reserves + raw unsafe operation heads for gating diagnostics, but pointer types, + allocation, deallocation, load, store, pointer arithmetic, reinterpretation, + unchecked indexing, raw memory execution, and FFI remain deferred +- stable ABI and stable layout promises remain deferred; v1.6 keeps memory + direction staged toward safe values by default, future affine ownership, and + explicit unsafe regions + +v1.1 tooling release decisions: + +- v1.1 is a tooling release over v1 semantics, not a source language release. +- The canonical v1.1 commands are `glagol check `, + `glagol fmt `, `glagol test `, and + `glagol build -o `. +- v1.1 assumes `slovo.diagnostic` version `1`, + `slovo.artifact-manifest` version `1`, JSON diagnostics on request, artifact + manifests on request, a hosted native build path through LLVM IR, Glagol + runtime C, and Clang, and the stable exit-code table from + `.llm/V1_1_TOOLCHAIN_PRODUCTIZATION.md`. +- Compatibility aliases may remain only as Glagol-side aliases that route + through the canonical command behavior; Slovo docs teach the canonical + commands. +- Project mode, package management, multi-file modules, write-in-place + formatting, test filtering, build profiles, cross-compilation, stable ABI, + object/library/header output, FFI, LSP, SARIF, and daemon protocols stay + deferred. + +v1.2 practical runtime values release decisions: + +- v1.2 is a conservative source-language release over the v1.1 toolchain + contract. +- The precise Slovo-side contract is + `.llm/V1_2_PRACTICAL_RUNTIME_VALUES.md`. +- Runtime strings still originate only from immutable compiler-emitted source + literals and propagation of those values; v1.2 adds no allocation, ownership, + deallocation, stable ABI, or FFI promise. +- String concatenation stays deferred in v1.2 because it requires allocation, + ownership, lifetime, and deallocation semantics; exp-1 later stages only the + narrow `std.string.concat` contract. +- Runtime trap messages are stable for array bounds and option/result unwrap + failures, are written to stderr with a trailing newline, and exit the process + with code `1`. +- v1.2 preserves v1.1 single-file CLI/toolchain assumptions and does not add + project mode, imports, packages, or multi-file modules. + +v1.3 project mode and modules release decisions: + +- v1.3 is a conservative project-mode release over the v1.2 language and + toolchain contract. +- The precise Slovo-side contract is + `.llm/V1_3_PROJECT_MODE_AND_MODULES.md`. +- Project mode is selected only when `glagol check`, `glagol test`, or + `glagol build` receives a project root containing `slovo.toml` or the + `slovo.toml` file itself. +- Passing one `.slo` file keeps the v1.2 single-file behavior unchanged. +- `slovo.toml` uses a minimal `[project]` schema with required `name`, + optional `source_root` defaulting to `src`, and optional `entry` defaulting + to `main`; the detailed `.llm` contract is normative for identifier, + unknown-key, and path-boundary validation. +- v1.3 project modules are flat local `.slo` files directly under the source + root; each file stem must match its `(module name ...)` declaration. +- Imports are explicit local imports of exported names only; there are no + packages, dependencies, registries, version solving, workspaces, aliases, + glob imports, qualified names, or re-exports. +- Only top-level `fn` and `struct` declarations listed in a module export list + are importable. +- Duplicate-name, missing-import, import-cycle, ambiguous-name, and visibility + failures are required structured diagnostics with multi-file source spans. +- `AmbiguousName` means the same unqualified name was imported from different + modules; duplicate names within one import list remain `DuplicateName`. +- Module discovery, graph validation, test execution, diagnostics, and artifact + manifests use deterministic ordering. + +v1.4 core language expansion release decisions: + +- v1.4 is a conservative source-language release over the v1.3 project-mode + and v1.2 runtime-values contracts. +- The precise Slovo-side contract is + `.llm/V1_4_CORE_LANGUAGE_EXPANSION.md`. +- v1.4 promotes source-level `match` only for existing `(option i32)`, + `(option i64)`, and `(result i32 i32)` values. +- Option matches require exactly `some` and `none`; result matches require + exactly `ok` and `err`. +- Payload bindings for `some`, `ok`, and `err` are immutable and scoped only + to the selected arm. +- Match arm bodies may contain one or more expressions; the final expression + is the arm value. +- The match expression type is the common arm result type. Mismatches are + structured diagnostics. +- Existing tag observers and `unwrap_*` forms remain supported, but new + option/result payload examples prefer `match`. +- v1.4 does not promote user-defined enums/ADTs, generic payloads, mutation, + vectors, `i64`, general block expressions, pattern guards, wildcard/rest + patterns, destructuring, standard-library error conventions, ownership, or + layout/ABI promises. + +v1.5 standard library alpha release decisions: + +- v1.5 is a conservative standard-runtime naming release over the v1.4 + contract. +- The precise Slovo-side contract is + `.llm/V1_5_STANDARD_LIBRARY_ALPHA.md`. +- v1.5 promotes only `std.io.print_i32`, `std.io.print_string`, + `std.io.print_bool`, and `std.string.len`. +- These names are compiler-known source-level standard-runtime names, not user + modules, imports, package dependencies, foreign functions, or stable C ABI + symbols. +- `std.io.print_i32` accepts exactly one `i32`, returns builtin `unit`, and + keeps legacy `print_i32` stdout behavior. +- `std.io.print_string` accepts exactly one `string`, returns builtin `unit`, + and keeps legacy `print_string` stdout behavior and string-literal + constraints. +- `std.io.print_bool` accepts exactly one `bool`, returns builtin `unit`, and + keeps legacy `print_bool` stdout behavior. +- `std.string.len` accepts exactly one `string`, returns `i32`, and keeps + legacy `string_len` decoded-byte count semantics. +- Legacy `print_i32`, `print_string`, `print_bool`, and `string_len` remain + compatibility aliases, but new examples prefer `std.*` names. +- Promoted `std.*` names are reserved from user function/export shadowing. +- Unknown or unpromoted `std.*` calls use structured diagnostics; the + suggested diagnostic code is `UnsupportedStandardLibraryCall`. +- Arity and type mismatches for promoted `std.*` calls use existing + arity/type diagnostics where applicable. +- v1.5 does not promote `std.io.print_unit`, file IO, environment variables, + process arguments, time, vectors/collections, allocation, user-defined + standard modules, imports/packages, overloading, generic APIs, Unicode + length semantics, or ABI/layout promises. + +v1.6 memory and unsafe design slice release decisions: + +- v1.6 was released 2026-05-17. +- The precise Slovo-side contract is + `.llm/V1_6_MEMORY_UNSAFE_SLICE.md`. +- v1.6 promotes a reservation and gating contract, not raw-memory execution. +- Lexical `(unsafe ...)` remains the only unsafe boundary. +- `alloc`, `dealloc`, `load`, `store`, `ptr_add`, `unchecked_index`, + `reinterpret`, and `ffi_call` are compiler-known reserved unsafe heads. +- Safe code using a reserved unsafe head must receive `UnsafeRequired` before + ordinary call lookup. +- Code inside `(unsafe ...)` using a reserved unsafe head must receive + `UnsupportedUnsafeOperation` until a future release defines semantics. +- User functions, imports, exports, and parameters cannot shadow reserved + unsafe heads. +- Memory direction remains staged: safe values by default, future affine + ownership, and explicit unsafe regions. +- v1.6 does not promote pointer types, allocation/free, load/store, pointer + arithmetic, unchecked indexing, raw reinterpretation, FFI, stable ABI, or + stable layout. + +v1.7 developer experience hardening release decisions: + +- v1.7 was released 2026-05-17. +- The precise Slovo-side contract is + `.llm/V1_7_DEVELOPER_EXPERIENCE_HARDENING.md`. +- v1.7 is a tooling-only release over the v1.6 language contract. +- `glagol new [--name ]` scaffolds a valid v1.3-style + project with `slovo.toml`, `src/main.slo`, and a small testable main module. +- `glagol new` must fail without overwriting non-empty directories. +- Plain `glagol fmt ` remains stdout formatting. +- `glagol fmt --check ` checks canonical formatting without + writing files. +- `glagol fmt --write ` writes canonical formatting for one + file or all immediate project source modules. +- `glagol doc -o ` generates deterministic Markdown + from source structure: modules, imports/exports, structs, functions, and + tests. +- Documentation generation is not a semantic reflection API and does not claim + typed-core, debug metadata, source-map, ABI, layout, or runtime reflection + stability. +- A repo script or documented command entry point must run the full local v1 + release gate. +- LSP, watch mode, daemon protocols, SARIF, debug adapters, stable debug + metadata, DWARF, and stable source-map files remain deferred. + +v2.0.0-beta.1 experimental integration/readiness release decisions: + +- `v2.0.0-beta.1` is a historical experimental release contract, released + 2026-05-17. The pushed tag name is historical and is not a beta maturity + claim. +- The precise Slovo-side contract is + `.llm/V2_0_0_BETA_1_RELEASE_CONTRACT.md`. +- Experimental readiness is based on v1.7 and adds no new source language + syntax or semantics unless already implemented and gate-proven in Glagol. +- Experimental readiness means small real flat local projects can use + `glagol new`, `check`, `fmt --check`, `fmt --write`, `test`, `build`, + `doc`, artifact manifests, JSON diagnostics, and the release-gate script as + one integrated workflow. +- The experimental language surface is the accumulated v1.1-v1.7 surface: `i32`, + `bool`, builtin internal `unit`, tests, locals, `if`, `while`, structs, + fixed direct scalar arrays with checked indexing, string value flow, + option/result values and exact match slice, standard-runtime alpha, and + lexical unsafe with reserved unsafe heads. +- Basic IO is only `std.io.print_i32`, `std.io.print_string`, + `std.io.print_bool`, and `std.string.len`; file/env/process/time IO remains + deferred. +- Vectors and growable collections are not experimental readiness + requirements. Fixed arrays satisfy the experimental gate because this is an + integration milestone; growable collections require allocation, + ownership/lifetime, capacity, mutation, diagnostics, lowering, runtime + tests, and docs that remain future beta work. +- Stable ABI/layout, FFI, raw-memory execution, LSP, stable debug metadata, + stable source-map files, and package registries remain future beta work. + +exp-1 owned runtime strings release decisions: + +- exp-1 is the first experimental step toward `1.0.0-beta`. +- The precise Slovo-side contract is + `.llm/EXP_1_OWNED_RUNTIME_STRINGS.md`. +- exp-1 promotes exactly one new source-level operation: + `std.string.concat`. +- `std.string.concat` accepts two `string` values and returns an immutable + runtime-owned `string`. +- Existing string equality, `std.string.len`, `std.io.print_string`, string + locals, parameters, returns, and calls returning `string` apply to + literal-backed and runtime-owned strings. +- No legacy `string_concat` alias is introduced. +- Allocation failure traps with + `slovo runtime error: string allocation failed` and exits with code `1`. +- Runtime-owned string cleanup is compiler/runtime-owned and not + source-visible. +- exp-1 keeps mutable strings, string containers, string indexing/slicing, + user-visible allocation/deallocation, file IO, packages, generics, growable + collections, raw-memory execution, FFI, and stable ABI/layout deferred. + +The matching Glagol exp-4 gate has passed. The next planned implementation +stage is the next beta-roadmap slice after payloadless user-defined enums. + +exp-2 collections alpha release decisions: + +- exp-2 is a current experimental compiler-supported contract after matching + Glagol exp-2 gates. +- The precise Slovo-side contract is + `.llm/EXP_2_COLLECTIONS_ALPHA.md`. +- exp-2 promotes exactly one concrete growable vector type: `(vec i32)`. +- exp-2 promotes exactly four compiler-known standard-runtime operations: + `std.vec.i32.empty`, `std.vec.i32.append`, `std.vec.i32.len`, and + `std.vec.i32.index`. +- `std.vec.i32.append` accepts `(vec i32)` and `i32` and returns a new + immutable runtime-owned `(vec i32)` without mutating the input vector. +- `std.vec.i32.len` returns `i32`; `std.vec.i32.index` returns `i32` and traps + on invalid indices. +- Vector equality with `=` compares lengths and `i32` element values in order. +- Runtime trap texts are `slovo runtime error: vector allocation failed` and + `slovo runtime error: vector index out of bounds`. +- exp-2 keeps generic vectors, non-`i32` element types, vector mutation, + vector `var`/`set`, vector literals beyond empty/append construction, + `push`, nested vectors, vectors in arrays/structs/options/results, + iterators, slices, maps, sets, user-visible deallocation, stable + ABI/layout/helper symbols, packages, and IO expansion deferred. + +exp-3 standard IO and host environment release decisions: + +- exp-3 is a current experimental compiler-supported contract after matching + Glagol exp-3 gates. +- The precise Slovo-side contract is + `.llm/EXP_3_STANDARD_IO_HOST_ENV.md`. +- exp-3 promotes exactly six compiler-known standard-runtime operations: + `std.io.eprint`, `std.process.argc`, `std.process.arg`, `std.env.get`, + `std.fs.read_text`, and `std.fs.write_text`. +- `std.io.eprint` accepts `string`, writes to stderr without adding a newline, + and returns builtin `unit`. +- `std.process.argc` returns `i32`; `std.process.arg` accepts `i32` and + returns `string`, trapping on out-of-range access with + `slovo runtime error: process argument index out of bounds`. +- `std.env.get` accepts `string` and returns `string`; missing variables + return the empty string for this stage. +- `std.fs.read_text` accepts `string` and returns `string`, trapping on host + read failure with `slovo runtime error: file read failed`. +- `std.fs.write_text` accepts two `string` values and returns `0` on success + or `1` on host failure. +- exp-3 keeps networking, async IO, binary file APIs, directory traversal, + terminal control, platform abstraction, general host error ADTs, `result + string Error`, package interaction, stdin full read/line iteration, + randomness, time, stable ABI/layout, and stable helper symbols deferred. + +exp-4 user data types and polymorphism release decisions: + +- exp-4 is a current experimental compiler-supported contract after matching + Glagol exp-4 gates. +- The precise Slovo-side contract is + `.llm/EXP_4_USER_ADTS_ALPHA.md`. +- exp-4 promotes only payloadless user-defined enums: + `(enum Name VariantA VariantB ...)`, with at least one variant. +- Variant values are constructed only through zero-argument qualified calls + such as `(Name.VariantA)`. +- Enum values may flow through immutable locals, parameters, returns, calls, + equality with `=`, and top-level tests. +- Enum `match` supports exhaustive payloadless variant arms with pattern + syntax `((Name.VariantA) body...)`, one-or-more expression bodies, and common + final result types. +- Runtime/backend representation is compiler-owned; exp-4 promises no stable + layout, discriminant value, ABI, helper symbols, reflection, or conversion + to `i32`. +- exp-4 keeps payload variants, tuple/record variants, generic enums, generic + functions, type aliases, traits/interfaces/protocols, methods, derives, + variant payload binding, wildcard/rest patterns, guards, nested patterns, + enum values in arrays/options/results/vectors or nested structs beyond + exp-18 direct fields, enum mutation, enum printing/ordering/hash, + reflection, explicit discriminants, constructors with + arguments, unqualified variant constructors, stable ABI/layout, and moving + option/result to ordinary standard-library types deferred. + +exp-6 C FFI scalar imports alpha release decisions: + +- exp-6 is a released experimental contract, not beta maturity. +- The precise Slovo-side contract is + `.llm/EXP_6_C_FFI_SCALAR_IMPORTS_ALPHA.md`. +- exp-6 promotes only top-level imported C function declarations with + `i32`-only scalar parameters and `i32` or internal `unit` returns. +- Calls to imported C functions must occur inside lexical `(unsafe ...)`. +- `examples/ffi/exp-6-c-add/` is the minimal fixture target, with a `c_add` + C companion and a safe Slovo wrapper. +- Artifact manifests must record foreign import metadata while marking the + ABI/layout as experimental and fixture-scoped. +- exp-6 keeps pointer types, allocation/deallocation, raw unsafe heads + execution, ownership/lifetime rules, C exports, headers, libraries, broad + linker configuration, and stable ABI/layout deferred. + +exp-7 test selection and test-run metadata alpha target decisions: + +- exp-7 is a released experimental contract, not beta maturity. +- The precise Slovo-side release contract is + `.llm/EXP_7_TEST_SELECTION_ALPHA.md`. +- exp-7 is narrowed to `glagol test --filter + ` plus legacy `glagol --run-tests --filter + ` while Glagol keeps the legacy route. +- Filtering is deterministic, case-sensitive substring matching against test + display names. +- Selected tests execute in existing order; non-selected tests are skipped and + counted. +- Zero matches is a successful run with explicit discovered, selected, passed, + failed, and skipped counts. +- Test-run output and artifact manifests gain additive experimental metadata + for total discovered, selected, passed, failed, skipped, and optional filter + string. +- exp-7 keeps LSP, debug metadata, source maps, SARIF, watch/daemon + protocols, documentation comments, lint categories, benchmarks, and new + source language syntax deferred. + +exp-8 host time and sleep alpha release decisions: + +- exp-8 is a released experimental compiler-supported contract, not beta + maturity. +- The precise Slovo-side release contract is + `.llm/EXP_8_HOST_TIME_SLEEP_ALPHA.md`. +- exp-8 narrows the broad concurrency and long-running programs roadmap + category to exactly `std.time.monotonic_ms: () -> i32` and + `std.time.sleep_ms: (i32) -> unit`. +- `std.time.monotonic_ms` returns non-negative host monotonic elapsed + milliseconds with an implementation-owned epoch. +- `std.time.sleep_ms` accepts non-negative `i32` millisecond durations; `0` is + valid, and negative values reject or trap with + `slovo runtime error: sleep_ms negative duration`. +- Test-runner support is deterministic for `sleep_ms 0`; `monotonic_ms` + support is limited to structural tests or a non-negative host value. +- The exp-14 conformance-alignment release adds byte-identical Slovo fixtures + `examples/supported/time-sleep.slo` and + `examples/formatter/time-sleep.slo` for this already released exp-8 + behavior. +- Artifact manifests record standard-runtime time usage only if existing + manifest patterns support standard-runtime usage metadata; otherwise extra + time-specific metadata is deferred. +- exp-8 keeps threads, tasks, channels, async, cancellation, actors, shared + memory, data-race freedom, scheduling guarantees, timers, wall-clock/ + calendar/timezone APIs, high-resolution timers, signal handling, source + syntax, stable ABI/layout, and stable runtime helper symbols deferred. + +exp-9 reliability, performance, and ecosystem hardening release decisions: + +- exp-9 is a released experimental compiler-supported contract, not beta + maturity. +- The precise Slovo-side release contract is + `.llm/EXP_9_RELIABILITY_PERFORMANCE_ECOSYSTEM_HARDENING.md`. +- exp-9 is hardening-only over the released exp-8 baseline. It adds no source + language syntax, type-system behavior, standard-runtime names, package + features, manifest schema versions, runtime capabilities, stable + ABI/layout promises, public performance guarantees, or beta claim. +- Matching Glagol gates must include property/fuzz-style parser, formatter, + diagnostic, project graph, and promoted runtime API tests where feasible. +- Matching Glagol gates must include a source-reachable panic audit covering + compiler, formatter, diagnostic, project graph, test-runner, backend, + artifact-manifest, and runtime API paths. +- Matching Glagol gates must include benchmark fixtures or documented + benchmark smoke, compatibility inventory for promoted experimental + features, and a migration guide from `v2.0.0-beta.1` and exp releases. +- exp-9 keeps all language, standard-library, package-management, editor + protocol, debug/source-map, broad FFI, raw-memory execution, stable + benchmark-suite, stable ABI/layout, and beta-maturity expansion deferred. + +exp-10 result-based host errors alpha release decisions: + +- exp-10 is a released experimental compiler-supported contract, not beta + maturity. +- The precise Slovo-side release contract is + `.llm/EXP_10_RESULT_BASED_HOST_ERRORS_ALPHA.md`. +- exp-10 promotes exactly one new concrete result family: + `(result string i32)`. +- exp-10 promotes exactly four additive compiler-known standard-runtime + operations: `std.process.arg_result`, `std.env.get_result`, + `std.fs.read_text_result`, and `std.fs.write_text_result`. +- The only promised ordinary host failure code is `err 1`. +- `std.fs.write_text_result` returns `ok 0` on success and `err 1` on + ordinary host write failure. +- The existing exp-3 calls remain unchanged. +- Artifact manifests record exp-10 standard-runtime usage only if existing + manifest patterns already record standard-runtime usage metadata; otherwise + exp-10 adds no manifest schema fields. +- exp-10 keeps general host error ADTs, `result string Error`, + platform-specific codes, result equality/printing/mapping, broader host API + families, stable ABI/layout/helper symbols, and beta maturity deferred. + +exp-11 basic randomness alpha release decisions: + +- exp-11 is released experimental compiler support and not beta maturity. +- The precise Slovo-side release contract is + `.llm/EXP_11_BASIC_RANDOMNESS_ALPHA.md`. +- exp-11 promotes exactly one compiler-known standard-runtime operation: + `std.random.i32: () -> i32`. +- `std.random.i32` returns a non-negative implementation-owned pseudo-random + or host-random `i32` suitable only for basic CLI use. +- Runtime/host inability to produce a value traps with exactly + `slovo runtime error: random i32 unavailable`. +- Glagol test-runner behavior may use an implementation-owned deterministic + non-negative sample or sequence. +- Artifact manifests record exp-11 standard-runtime usage only if existing + manifest patterns already record standard-runtime usage metadata; otherwise + exp-11 adds no manifest schema fields. +- exp-11 keeps seed APIs, crypto/security promises, bytes APIs, ranges, + bounds, floats, random strings, UUIDs, broad `std.random.*`, stable + ABI/layout/helper symbols, and beta maturity deferred. + +exp-12 standard input result alpha release decisions: + +- exp-12 is released experimental compiler support and not beta maturity. +- Release date: 2026-05-18. +- The precise Slovo-side release contract is + `.llm/EXP_12_STDIN_RESULT_ALPHA.md`. +- exp-12 promotes exactly one compiler-known standard-runtime operation: + `std.io.read_stdin_result: () -> (result string i32)`. +- `std.io.read_stdin_result` reads remaining stdin as text, returns `ok` text + on success, returns `ok ""` for ordinary EOF with no bytes, and returns + `(err string i32 1)` for ordinary host/input failure. +- Glagol test-runner behavior may use an implementation-owned fixed `ok` + string. +- Artifact manifests record exp-12 standard-runtime usage only if existing + manifest patterns already record standard-runtime usage metadata; otherwise + exp-12 adds no manifest schema fields. +- exp-12 keeps trap stdin, line/prompt/terminal/binary/streaming/async stdin, + encoding or Unicode promises beyond existing string bytes, stable + ABI/layout/helper symbols, manifest schema changes, and beta maturity + deferred. + +exp-13 string parse i32 result alpha release decisions: + +- exp-13 is released experimental compiler support and not beta maturity. +- Release date: 2026-05-18. +- The precise Slovo-side release contract is + `.llm/EXP_13_STRING_PARSE_I32_RESULT_ALPHA.md`. +- exp-13 promotes exactly one compiler-known standard-runtime operation: + `std.string.parse_i32_result: (string) -> (result i32 i32)`. +- `std.string.parse_i32_result` parses the entire string as ASCII decimal + signed `i32`, with optional leading `-` and at least one digit. +- Success returns `(ok i32 i32 value)`. +- Empty input, non-digits, plus signs, whitespace, trailing bytes, non-ASCII + digits, and out-of-range values return `(err i32 i32 1)`. +- The only ordinary parse error code promised by exp-13 is `err 1`. +- Release fixtures are `examples/supported/string-parse-i32-result.slo` and + `examples/formatter/string-parse-i32-result.slo`. +- exp-13 keeps trap-based parse, parsing floats/bools/strings/bytes, + whitespace/locale/base-prefix/underscore/plus-sign parsing, generic parse, + parse error messages/codes, Unicode digit parsing, string indexing/slicing, + tokenizer/scanner APIs, stdin line APIs, stable ABI/layout/helper symbols, + manifest schema fields, and beta maturity deferred. + +exp-14 standard runtime conformance alignment release decisions: + +- exp-14 is released experimental conformance alignment and not beta maturity. +- The precise Slovo-side target contract is + `.llm/EXP_14_STANDARD_RUNTIME_CONFORMANCE_ALIGNMENT.md`. +- exp-14 adds `STANDARD_RUNTIME.md` as the promoted compiler-known `std.*` + operation catalog through exp-13. +- exp-14 adds byte-identical canonical fixtures + `examples/supported/time-sleep.slo` and + `examples/formatter/time-sleep.slo` for already released exp-8 + `std.time.monotonic_ms` and `std.time.sleep_ms 0`. +- The time/sleep fixtures require only structural/non-negative monotonic-time + behavior and deterministic `sleep_ms 0`; they do not assert + positive-duration timing. +- exp-14 requires the current supported fixture inventory to stay explicit and + requires Slovo/Glagol supported and formatter fixtures to stay byte-aligned + where matching files exist. +- exp-14 requires a fresh-project workflow covering `new`, `check`, + `fmt --check`, `test`, `doc`, and `build` where the hosted toolchain + exists. +- The workflow must exercise existing features together: modules/imports, + tests, strings, `std.string.concat`, `std.string.parse_i32_result`, result + `match`, `(vec i32)`, enum `match`, and `std.io.print_i32`. +- exp-14 release gates still include Slovo and Glagol diff checks, + `cargo fmt --check`, `cargo test`, ignored promotion gate, binary smoke, + LLVM smoke, and review `PASS`. +- exp-14 release gates passed with Slovo and Glagol diff checks, + `cargo fmt --check`, `cargo test`, ignored promotion gate, binary smoke, + LLVM smoke, focused conformance gate, and review `PASS`. +- exp-14 adds no source syntax, type forms, runtime APIs, compiler-known + `std.*` names, standard library functions, manifest schema version, + ABI/layout promise, runtime headers/libraries, or beta maturity. + +exp-15 result helper standard names alpha decisions: + +- The precise Slovo-side target contract is + `.llm/EXP_15_RESULT_HELPER_STANDARD_NAMES_ALPHA.md`. +- exp-15 promotes preferred source-level `std.result.is_ok`, + `std.result.is_err`, `std.result.unwrap_ok`, and + `std.result.unwrap_err`. +- The names are accepted only for `(result i32 i32)` and + `(result string i32)`. +- Legacy unqualified result helpers remain compatibility syntax where already + supported. +- Release fixtures are `examples/supported/result-helpers.slo` and + `examples/formatter/result-helpers.slo`. +- exp-15 keeps `std.result.map`, `std.result.unwrap_or`, + `std.result.and_then`, option helper standard names, new payload families, + generic result, user-defined error payloads, runtime ABI/layout claims, + manifest schema changes, enum payloads, and beta maturity deferred. + +exp-17 project enum imports alpha decisions: + +- The precise Slovo-side target contract is + `.llm/EXP_17_PROJECT_ENUM_IMPORTS_ALPHA.md`. +- exp-17 promotes explicit export/import of top-level enum names across local + project modules and exp-5 workspace package modules. +- Imported enum types can be used in function signatures, immutable locals, + calls/returns, same-enum equality, exhaustive enum matches, and qualified + constructors. +- The enum surface is exactly exp-4 payloadless variants plus exp-16 unary + `i32` payload variants. +- The project fixture is `examples/projects/enum-imports/`. +- exp-17 keeps non-`i32` enum payloads, multiple payloads, generics, + containers, mutation, printing, ordering, hashing, reflection, stable + ABI/layout, manifest schema changes, registry/package-manager claims, and + beta maturity deferred. + +exp-18 enum struct fields alpha decisions: + +- The precise Slovo-side target contract is + `.llm/EXP_18_ENUM_STRUCT_FIELDS_ALPHA.md`. +- exp-18 promotes direct struct field declarations whose field types are + current user-defined enum type names. +- The enum field surface is exactly exp-4 payloadless variants plus exp-16 + unary `i32` payload variants. +- Struct construction, immutable struct local/parameter/return/call flow, + field access, same-enum equality on field access, enum match on field + access, tests, and `main` are in scope. +- Release fixtures are `examples/supported/enum-struct-fields.slo` and + `examples/formatter/enum-struct-fields.slo`. +- exp-18 keeps wider enum payloads, arrays/vectors/options/results containing + enum values, nested structs, struct mutation, enum mutation, printing, + ordering, hashing, reflection, import aliases/globs/re-exports, stable + ABI/layout, manifest schema changes, registry/package-manager claims, + generics, and beta maturity deferred. + +exp-19 primitive struct fields alpha decisions: + +- The precise Slovo-side target contract is + `.llm/EXP_19_PRIMITIVE_STRUCT_FIELDS_ALPHA.md`. +- exp-19 promotes direct struct field declarations whose field types are + `bool` or immutable `string`, alongside already supported direct `i32` and + exp-18 enum fields. +- Struct construction, immutable struct local/parameter/return/call flow, + field access, bool predicate/test use, string equality, `std.string.len`, + tests, and `main` are in scope. +- Release fixtures are `examples/supported/primitive-struct-fields.slo` and + `examples/formatter/primitive-struct-fields.slo`. +- exp-19 keeps arrays/vectors/options/results, nested structs, struct + mutation, string mutation, string ownership/cleanup guarantees beyond + existing string behavior, broader string operations, printing beyond + existing calls, package/import widening, stable ABI/layout, manifest schema + changes, generics, methods, traits, and beta maturity deferred. + +exp-20 f64 numeric primitive alpha decisions: + +- The precise Slovo-side target contract is + `.llm/EXP_20_F64_NUMERIC_PRIMITIVE_ALPHA.md`. +- exp-20 promotes direct `f64` function parameters, returns, immutable locals, + calls, and decimal literals in `f64` contexts. +- Same-type `f64` `+`, `-`, `*`, `/`, `=`, `<`, `>`, `<=`, and `>=` plus + `std.io.print_f64`, tests, and `main` returning `i32` are in scope. +- Release fixtures are `examples/supported/f64-numeric-primitive.slo` and + `examples/formatter/f64-numeric-primitive.slo`. +- exp-20 keeps `f32`, wider/common integer types, char/bytes/decimal, numeric + casts, mixed `i32`/`f64` arithmetic, f64 arrays/vectors/options/results, + f64 enum payloads, f64 struct fields, `parse_f64`, random floats, stable + ABI/layout, manifest schema changes, and beta maturity deferred. + +## Phase 1: Stabilize The Contract + +- [x] Split examples into compiler-supported and design/speculative groups. +- [x] Mark which examples Glagol can currently parse, check, and emit. +- [x] Keep `examples/supported/add.slo` aligned with Glagol's executable target. +- [x] Add notes to speculative examples saying they are design targets, not current compiler fixtures. +- [x] Keep `SPEC-v0.md` explicit about unimplemented v0 forms. +- [x] Keep README and examples docs explicit about the current supported subset. +- [x] Keep supported examples free of any form outside the current compiler contract. + +## Phase 2: Make Tooling Real + +- [x] Define canonical formatter rules for current supported syntax only. +- [x] Add formatter examples for modules, functions, calls, integer literals, `print_i32`, and binary `+`. +- [x] Add formatter examples for comparison and top-level `test` only after those forms become supported or are explicitly documented as design-target fixtures. +- [x] Add Slovo formatter fixtures for top-level `test` only after Glagol implements and tests the full strict contract. +- [x] Define parsed-tree printer output. +- [x] Define lowering-inspector output from surface AST to checked/core AST. +- [x] Define machine-diagnostic fixture format. + +## Phase 3: Close Spec Gaps + +- [x] Specify exact supported integer-literal range behavior for `i32`. +- [x] Specify `print_i32` as either a temporary compiler intrinsic or a standard-library/runtime binding. +- [x] Specify whether `unit` can be used as a user-declared return type in supported fixtures. +- [x] Specify when checked-but-not-lowered forms must emit `UnsupportedBackendFeature` instead of panicking. +- [x] Specify top-level `test` behavior enough for Glagol to implement it. +- [x] Coordinate remaining Glagol contract fixes and promotion tests before moving any `test` example into `examples/supported/`. +- [x] Specify `let`, `var`, and `set` checking and lowering. +- [x] Specify `while` checking and lowering. +- [x] Specify checked-expression spans as part of the compiler contract. +- [x] Specify line/column diagnostic ranges in addition to byte spans. +- [x] Specify the strict first-pass `i32` struct contract. +- [x] Specify the strict first-pass `i32` array constructor and literal + checked-indexing contract. +- [x] Specify the strict first-pass `i32` option/result constructor contract. + +## Phase 4: Grow The Core + +- [x] Promote `let`, `var`, and `set` into supported examples and formatter fixtures. +- [x] Promote value-producing `if` into supported examples and formatter fixtures. +- [x] Promote first-pass `while` into supported examples and formatter fixtures. +- [x] Promote first-pass `i32` struct definitions, construction, and field access + into supported examples and formatter fixtures. +- [x] Promote first-pass arrays and checked indexing into supported examples + and formatter fixtures. +- [x] Promote first-pass `i32` option/result constructors into supported + examples and formatter fixtures. +- [x] Promote lexical `unsafe` and unsafe-required diagnostics. +- [x] Runtime/string support or explicit unsupported diagnostics. + +## Phase 5: Close v0 + +- [x] Promote `SPEC-v0.md` from draft sketch to the supported v0 contract. +- [x] Keep Glagol's binary CLI as the implementation-facing v0 tool surface. +- [x] Keep direct executable output, package management, broader type flow, raw + memory, FFI, and ABI layout promises deferred until separate contracts. + +## Phase 6: Start v1 Value Flow + +- [x] Promote immutable struct locals, struct parameters, struct returns, calls + returning structs, and field access through stored struct values. +- [x] Keep struct field/value mutation deferred beyond v1 unless explicitly + promoted later. +- [x] Promote option/result value flow and tag observation. +- [x] Promote explicit trap-based `i32` option/result payload extraction. +- [x] Keep option/result matching, mapping, equality, printing, non-`i32` + payloads and extraction, nested option/result values, arrays/structs + containing option/results, and user-catchable exceptions deferred beyond + v1 unless explicitly promoted later. v1.4 later promotes only the narrow + `(option i32)` and `(result i32 i32)` match slice. +- [x] Promote array value flow, immutable array-local expression + initialization, and dynamic indexing after layout and bounds behavior are + specified. + +## Phase 7: Start Runtime Strings + +- [x] Promote the narrow runtime string slice: immutable source string literals + with ASCII-plus-current-escape semantics, direct `print_string` calls, and + borrowed immutable NUL-terminated compiler-emitted static storage. +- [x] Keep mutable string locals, string assignment, string + ordering/comparison beyond equality, string arrays/struct + fields/option-result payloads, slices, concatenation, indexing, + ownership, user-defined runtime bindings, and stable string ABI promises + deferred until separate contracts promote them; exp-1 later stages only + `std.string.concat`. +- [x] Keep standard-runtime printing deferred for this phase; v1.5 later + promotes only `std.io.print_i32`, `std.io.print_string`, and + `std.io.print_bool` over the existing legacy aliases. +- [x] Keep unit printing unsupported; bool printing is promoted separately in + v1.2 through `print_bool`. +- [x] Keep `unit` internal to supported unit-producing body forms; user-declared + or stored `unit` values remain unsupported in v1. + +## Phase 8: Improve v1 Tooling Contracts + +- [x] Define `slovo.diagnostic` version `1` as the stable v1 machine + diagnostic schema. +- [x] Define the v1 direction for preserving source spans through LLVM IR + emission while deferring stable debug metadata and source-map files. +- [x] Define `slovo.artifact-manifest` version `1` for compiler outputs and + test reports without requiring native executable output. +- [x] Define formatter stability guarantees for comments and nested forms: + promoted formatter fixtures are idempotent canonical output, accepted + full-line comment positions are preserved, unsupported comment/layout + positions require structured diagnostics, `if` / `while` / `unsafe` keep + stable multiline shapes, expression calls and constructors stay inline, + and v1 makes no width-based reflow promise. +- [x] Specify the Stage 3 diagnostics coverage contract: Glagol must keep a + golden machine-diagnostic fixture inventory for every current explicitly + rejected v1 boundary. This marks the Slovo obligation as specified; it + does not claim the Glagol inventory is complete. +- [x] Specify the Stage 3 lowering-inspector coverage contract: Glagol must + keep textual inspector golden fixtures for add/canonical, top-level + tests, locals, if, while, struct, struct value flow, array, array value + flow, option/result, option/result flow, option/result payload, + option/result match, string print, string value flow, print bool, unsafe, + and formatter + comments/stability only where inspector mode applies to source accepted + by lowering. +- [x] Keep stable LLVM debug metadata, DWARF emission, and source-map files + deferred; lowering-inspector fixtures are textual compiler-tree + artifacts, not debug metadata. +- [x] Require future promoted features to add diagnostic and + lowering-inspector coverage before promotion. + +## Phase 9: Productize v1.1 Toolchain + +- [x] Freeze v1.1 as a tooling release over the v1 language contract. +- [x] Define canonical single-file commands for `check`, `fmt`, `test`, and + `build`. +- [x] Keep v1 source semantics and unsupported boundaries unchanged. +- [x] Define JSON diagnostics as a serialization of `slovo.diagnostic` version + `1`. +- [x] Apply `slovo.artifact-manifest` version `1` manifests to v1.1 command + invocations. +- [x] Define the stable v1.1 exit-code table. +- [x] Define hosted native build as LLVM IR plus Glagol runtime C plus Clang. +- [x] Document compatibility aliases as secondary Glagol routes, not primary + Slovo commands. +- [x] Document v1.1 release gates and explicit deferrals. + +## Phase 10: Freeze v1.2 Practical Runtime Values + +- [x] Promote immutable string locals, string parameters, string returns, calls + returning strings, string equality, string byte length through + `string_len`, and top-level tests using string equality. +- [x] Promote `print_bool` with exact `true\n` / `false\n` output. +- [x] Keep `print_unit` unsupported because `unit` remains an internal builtin + result type rather than a user-visible value. +- [x] Keep string concatenation deferred until allocation, ownership, lifetime, + and deallocation semantics are specified; exp-1 later specifies the + first narrow `std.string.concat` operation. +- [x] Define stable runtime trap messages and process exit code `1` for array + bounds and option/result unwrap failures. +- [x] Preserve v1.1 CLI/toolchain assumptions and keep project mode/modules + deferred. + +## Phase 11: Freeze v1.3 Project Mode And Modules + +- [x] Define `slovo.toml` as the v1.3 project manifest. +- [x] Define a flat source-root convention with default `src`. +- [x] Define project mode for `glagol check`, `glagol test`, and + `glagol build`. +- [x] Keep single-file command behavior unchanged when the input is a `.slo` + file. +- [x] Define module declarations, explicit local imports, and explicit export + lists. +- [x] Limit importable declarations to exported top-level `fn` and `struct` + names. +- [x] Require deterministic module ordering, test ordering, and manifest + ordering. +- [x] Require multi-file source spans and artifact-manifest project fields. +- [x] Keep packages, dependencies, remote registries, version solving, + workspaces, macros, public ABI, cross-package visibility, incremental + builds, watch/LSP, path escapes, and generated code deferred. + +## Phase 12: Freeze v1.4 Core Language Expansion + +- [x] Promote source-level `match` for existing `(option i32)` and + `(result i32 i32)` values. +- [x] Require exhaustive `some` / `none` and `ok` / `err` arms. +- [x] Define immutable payload bindings scoped to one arm only. +- [x] Define match arm bodies as one-or-more expression bodies whose final + expression is the arm value. +- [x] Define match expression type as the common arm result type. +- [x] Name diagnostics for non-option/result subjects, unsupported payload + types, missing arms, duplicate arms, malformed patterns, arm type + mismatches, binding collisions, and unsupported mutation/container + combinations. +- [x] Keep v1.3 project mode and v1.2 runtime values unchanged. +- [x] Keep user-defined enums/ADTs, generic payloads, mutation, vectors, + `i64`, general block expressions, pattern guards, wildcard/rest + patterns, destructuring, standard-library error conventions, ownership, + and layout/ABI promises deferred. + +## Phase 13: Freeze v1.5 Standard Library Alpha + +- [x] Promote `std.io.print_i32` as the stable source-level standard-runtime + name for legacy `print_i32` behavior. +- [x] Promote `std.io.print_string` as the stable source-level standard-runtime + name for legacy `print_string` behavior. +- [x] Promote `std.io.print_bool` as the stable source-level standard-runtime + name for legacy `print_bool` behavior. +- [x] Promote `std.string.len` as the stable source-level standard-runtime + name for legacy `string_len` byte-count behavior. +- [x] Preserve legacy `print_i32`, `print_string`, `print_bool`, and + `string_len` as compatibility aliases. +- [x] Require `UnsupportedStandardLibraryCall` diagnostics for unknown or + unpromoted `std.*` calls. +- [x] Reserve promoted `std.*` names from user function/export shadowing. +- [x] Keep v1.5 names compiler-known, with no imports, packages, + user-defined standard modules, or stable C ABI symbols. +- [x] Keep `std.io.print_unit`, file IO, environment variables, process args, + time, vectors/collections, allocation, overloading, generic APIs, + Unicode length semantics, and ABI/layout promises deferred. + +## Phase 14: Freeze v1.6 Memory And Unsafe Design Slice + +- [x] Keep lexical `(unsafe ...)` as the only unsafe boundary. +- [x] Reserve `alloc`, `dealloc`, `load`, `store`, `ptr_add`, + `unchecked_index`, `reinterpret`, and `ffi_call` as compiler-known + unsafe heads. +- [x] Require `UnsafeRequired` in safe code and + `UnsupportedUnsafeOperation` inside lexical unsafe for reserved unsafe + heads. +- [x] Reject user functions, imports, exports, and parameters that shadow + reserved unsafe heads. +- [x] Keep raw-memory execution, pointer types, allocation/free, load/store, + pointer arithmetic, unchecked indexing, reinterpretation, FFI, stable + ABI, and stable layout deferred. + +## Phase 15: Freeze v1.7 Developer Experience Hardening + +- [x] Define `glagol new [--name ]` as a non-overwriting + v1.3-style project scaffold command. +- [x] Preserve plain `glagol fmt ` as stdout formatting. +- [x] Define `glagol fmt --check ` as non-writing canonical + formatting verification. +- [x] Define `glagol fmt --write ` as in-place canonical + formatting for one file or immediate project source modules. +- [x] Define `glagol doc -o ` as deterministic + Markdown documentation generation from source structure. +- [x] Keep documentation generation separate from semantic reflection, + typed-core APIs, debug metadata, source maps, ABI/layout, and runtime + reflection. +- [x] Require a repo script or documented command entry point for the full + local v1 release gate. +- [x] Keep LSP, watch mode, daemon protocols, SARIF, debug adapters, stable + debug metadata, DWARF, and stable source-map files deferred. + +## Phase 16: Freeze v2.0.0-beta.1 Experimental Integration Readiness + +- [x] Mark `v2.0.0-beta.1` as a historical experimental release contract, + released 2026-05-17, with future general-purpose beta work as the next + planning horizon. +- [x] Define the release as an experimental integration/readiness release + based on v1.7 rather than a new source-language release. +- [x] Require the small-flat-project workflow through `glagol new`, `check`, + `fmt --check`, `fmt --write`, `test`, `build`, `doc`, artifact + manifests, JSON diagnostics, and the release-gate script. +- [x] Freeze the experimental-supported language surface as the accumulated + v1.1-v1.7 surface. +- [x] Limit experimental IO to `std.io.print_i32`, `std.io.print_string`, + `std.io.print_bool`, and `std.string.len`; keep file/env/process/time IO + deferred. +- [x] Reconcile older collection roadmap text by making fixed arrays + sufficient for experimental readiness and deferring vectors/growable + collections until future beta work. +- [x] Keep stable ABI/layout, FFI, raw-memory execution, LSP, stable debug + metadata, stable source maps, and package registries out of the + experimental release. + +## Phase 17: Freeze exp-1 Owned Runtime Strings + +- [x] Define exp-1 as experimental maturity and not beta. +- [x] Promote only compiler-known `std.string.concat` as the first + heap-created owned runtime string operation. +- [x] Keep one source type, `string`, with literal-backed and runtime-owned + implementation value kinds hidden from source code. +- [x] Require existing string equality, `std.string.len`, + `std.io.print_string`, locals, parameters, returns, and calls to work + over runtime-owned strings. +- [x] Define allocation-failure trap text and exit behavior. +- [x] Keep mutable strings, string containers, indexing, slicing, + user-visible allocation/deallocation, file IO, packages, generics, + growable collections, FFI, raw memory, and stable ABI/layout deferred. +- [x] Implement and gate the matching Glagol exp-1 behavior before claiming + current compiler support. + +## Phase 18: Freeze exp-2 Collections Alpha + +- [x] Define exp-2 as experimental maturity and not beta. +- [x] Specify only one concrete vector type, `(vec i32)`, as the collections + alpha source type. +- [x] Specify only compiler-known `std.vec.i32.empty`, + `std.vec.i32.append`, `std.vec.i32.len`, and `std.vec.i32.index`. +- [x] Require `std.vec.i32.append` to return a new immutable runtime-owned + vector without mutating its input. +- [x] Require `(vec i32)` locals, parameters, returns, calls, top-level tests, + and vector equality with `=`. +- [x] Define allocation and index runtime trap texts. +- [x] Keep generic vectors, other element types, vector mutation, + vector `var`/`set`, vector literals beyond empty/append construction, + `push`, nested vectors, vectors in arrays/structs/options/results, + iterators, slices, maps, sets, user deallocation, stable ABI/layout/helper + symbols, packages, and IO expansion deferred. +- [x] Implement and gate the matching Glagol exp-2 behavior before claiming + current compiler support. + +## Phase 19: Freeze exp-3 Standard IO And Host Environment + +- [x] Define exp-3 as experimental maturity and not beta. +- [x] Specify only six compiler-known host operations: + `std.io.eprint`, `std.process.argc`, `std.process.arg`, + `std.env.get`, `std.fs.read_text`, and `std.fs.write_text`. +- [x] Define `std.process.arg` out-of-range and `std.fs.read_text` host read + failure as exact runtime traps for this stage. +- [x] Define missing environment variables as the empty string for this stage. +- [x] Define `std.fs.write_text` status as `0` on success and `1` on host + failure for this stage. +- [x] Keep networking, async IO, binary file APIs, directory traversal, + terminal control, platform abstraction, general host error ADTs, + `result string Error`, package interaction, stdin full read/line + iteration, randomness/time, and stable ABI/layout/helper symbols + deferred. +- [x] Implement and gate the matching Glagol exp-3 behavior before claiming + current compiler support. + +## Phase 20: Freeze exp-4 User Data Types And Polymorphism + +- [x] Define exp-4 as experimental maturity and not beta. +- [x] Specify only payloadless user-defined enums: + `(enum Name VariantA VariantB ...)`. +- [x] Require at least one variant per enum declaration. +- [x] Require zero-argument qualified constructors such as `(Name.VariantA)`. +- [x] Allow enum values through immutable locals, parameters, returns, calls, + same-enum equality, and top-level tests. +- [x] Define exhaustive enum `match` with payloadless qualified variant arms, + one-or-more expression bodies, and common final result types. +- [x] Keep runtime/backend enum representation compiler-owned, with no stable + layout, discriminant value, ABI, helper-symbol, reflection, or + conversion-to-`i32` promise. +- [x] Defer payload variants, tuple/record variants, generic enums, generic + functions, type aliases, traits/interfaces/protocols, methods, derives, + variant payload binding, wildcard/rest patterns, guards, nested patterns, + enum values in arrays/options/results/vectors or nested structs beyond + exp-18 direct fields, enum mutation, + enum printing/ordering/hash, reflection, explicit discriminants, + constructors with arguments, unqualified variant constructors, stable + ABI/layout, and moving option/result to ordinary standard-library types. +- [x] Implement and gate the matching Glagol exp-4 behavior before claiming + current compiler support. +- [x] Freeze exp-20 as F64 Numeric Primitive Alpha: promote only direct `f64` + value flow, decimal `f64` literals, same-type `f64` + arithmetic/comparison, `std.io.print_f64`, byte-identical + supported/formatter fixtures, and defer `f32`, wider/common integer + types, char/bytes/decimal, casts, mixed numeric arithmetic, f64 + containers, f64 enum payloads, f64 struct fields, parsing, random + floats, stable ABI/layout, manifest schema changes, and beta maturity. + +## Deferred + +These are future beta targets, not unresolved experimental release decisions. + +- [ ] Package/dependency management. +- [ ] Build profiles, optimization flags, target triples, and + cross-compilation. +- [ ] Stable object/library/header output and broad C FFI beyond exp-6 scalar + imports alpha. +- [ ] Stdin formatting, recursive directory formatting, and dependency/package + formatting. +- [ ] Project-wide test expansion beyond the existing baseline, event + streams, and stable public benchmarks. exp-9 may require benchmark + fixtures or documented benchmark smoke only. +- [ ] Editor integrations, LSP, SARIF, watch mode, and daemon protocols. +- [ ] Macros. +- [ ] Generics. +- [ ] Concurrency beyond the exp-8 host time/sleep alpha target. +- [ ] Advanced ownership or affine resources. +- [ ] String concatenation beyond exp-1 `std.string.concat`, indexing, + slicing, mutable string locals, string containers, user-visible string + allocation or deallocation, and stable string ABI/layout. +- [ ] Package manager. +- [ ] Full standard library beyond the v1.5 standard-runtime alpha. +- [ ] Direct x86/ARM backend. +- [ ] Struct field/value mutation. +- [ ] Option/result mapping, equality, printing, payloads and extraction + beyond the explicitly promoted concrete result slices, nesting, + containers, generic payloads, user-defined payload ADTs, and + user-catchable exceptions. +- [ ] Unit printing and broader standard-runtime printing. +- [ ] Host IO beyond exp-3 and the exp-10 `*_result` slice, networking, + async IO, binary file APIs, directory traversal, terminal control, + platform abstraction, general host error ADTs, stdin full read/line + iteration, time beyond the exp-8 host time/sleep target, randomness + beyond the exp-11 target, + vectors/collections beyond the current concrete `(vec i32)` and + `(vec i64)` slices, user-defined standard modules, overloading, and + generic standard-library APIs. +- [ ] Generic vectors, vector element families beyond the current concrete + `i32` and `i64` slices, vector mutation, vector literals beyond + empty/append construction, nested vectors, vectors in + arrays/structs/options/results, iterators, slices, maps, sets, user + deallocation, and stable vector ABI/layout/helper symbols. +- [ ] User-declared or stored `unit` values. +- [ ] Pointer types, raw memory operations, unchecked indexing, and broad FFI + beyond exp-6 scalar imports alpha. +- [ ] Stable ABI and stable layout promises. diff --git a/docs/language/SPEC-v0.md b/docs/language/SPEC-v0.md new file mode 100644 index 0000000..dfda57c --- /dev/null +++ b/docs/language/SPEC-v0.md @@ -0,0 +1,1743 @@ +# Slovo v0 Specification + +> This is not a complete language specification. +> +> This is the supported v0 contract for the first Slovo parser, formatter, +> checker, diagnostics, test runner, and LLVM-oriented compiler prototype. + +--- + +## 1. Status + +Version: `v0` + +File extension: `.slo` + +Primary backend target: **LLVM IR** + +Primary design goals: + +- parse Slovo forms +- format Slovo forms +- type-check a small core +- run tests +- lower simple programs toward LLVM IR +- produce structured diagnostics + +Compiler-supported fixtures for the frozen v0 contract: + +```text +examples/supported/add.slo +examples/supported/top-level-test.slo +examples/supported/local-variables.slo +examples/supported/if.slo +examples/supported/while.slo +examples/supported/struct.slo +examples/supported/array.slo +examples/supported/option-result.slo +examples/supported/unsafe.slo +``` + +For strict-manifest iteration1, "compiler-supported" means the form is parsed, +lowered, type-checked, emitted as LLVM IR or handled by its required tool mode, +and covered by an automated Glagol test. + +This file uses "current" only inside the v0 boundary. Post-v0 promotions live +in `SPEC-v1.md` and may expand the repository's supported surface without +changing the frozen v0 contract. + +Supported v0 subset, as exercised by the v0 fixtures: + +- one `(module name)` form +- top-level `(fn ...)` forms +- top-level `(struct Name (field i32)...)` forms +- top-level `(test "name" body... final-expression)` forms, where body forms + are optional local declarations, assignments, or first-pass loops +- explicitly typed `i32` function parameters and `i32` return values +- direct constructor-return functions with `(option i32)` and + `(result i32 i32)` return types under section 14 +- integer literals used as `i32` values +- function parameter references +- binary integer addition with `+` +- equality comparison `=` as a bool-producing test expression +- ordering comparison `<` as a bool-producing `i32` condition +- user-defined function calls +- the temporary compiler intrinsic `(print_i32 value)` call as a statement-like + expression returning builtin `unit` +- local `i32` bindings with `(let name i32 value)` and `(var name i32 value)` +- assignment to mutable local `i32` bindings with `(set name value)` +- local references after declaration +- value-producing `(if condition then-expression else-expression)` +- first-pass `(while condition body...)` as a non-final sequential body form +- first-pass struct constructor expressions and immediate field access +- first-pass fixed `i32` array constructors, immutable array locals, and + literal checked indexing +- first-pass `i32` option/result constructors used only as direct function + returns +- lexical `(unsafe body... final-expression)` expression blocks whose body + forms and final expression are otherwise supported safe v0 forms +- final-expression function returns + +Implementation-recognized forms are not automatically supported language +features. A form remains a design target until it has parser/lowerer behavior, +checker behavior, backend behavior or explicit unsupported diagnostics, +formatter behavior where applicable, and tests. + +All examples under `examples/speculative/` are v0 design targets, not current +compiler-supported fixtures. + +Formatter fixtures under `examples/formatter/` are canonical-layout fixtures for +the same strict supported syntax. They are not new language features and must +not include design-target forms. + +Top-level `(test "name" body... final-expression)` is supported only in the +strict form defined in section 16. + +Non-goals for v0: + +- macros +- generics +- concurrency +- advanced ownership +- package manager +- full standard library +- direct x86/ARM backend + +--- + +## 2. Source Files + +A Slovo source file uses the `.slo` extension. + +A source file contains one top-level module form. + +```slo +(module main) +``` + +A practical file usually contains: + +```slo +(module main) + +(fn main () -> i32 + 0) +``` + +--- + +## 3. Lexical Syntax + +Whitespace separates tokens but has no semantic meaning. + +Tabs and spaces are equivalent. + +Line comments begin with `;`. + +```slo +; this is a comment +(+ 1 2) +``` + +A form is a parenthesized tree: + +```slo +(name arg arg arg) +``` + +Identifiers may contain letters, digits, `_`, `-`, `?`, `!`, and selected symbolic operator names. + +String literals use double quotes. + +```slo +"Hello, Slovo" +``` + +Integer literals: + +```slo +0 +42 +-7 +``` + +Current compiler-supported integer literals are signed `i32` values in the +inclusive range `-2147483648` to `2147483647`. Glagol must reject literals +outside that range with `IntegerOutOfRange` before narrowing them into the AST. + +Floating-point literals: + +```slo +3.14 +-0.5 +``` + +Floating-point literals are a design target, not current compiler-supported +syntax. + +--- + +## 4. Top-Level Forms + +Current compiler-supported top-level forms: + +```text +module +fn +test +struct +``` + +Design-target top-level forms: + +```text +import +``` + +`test` is supported only as the top-level form described in section 16. +`struct` is supported only as the first-pass top-level form described in +section 12. `import` remains a broader design target. + +--- + +## 5. Types + +Current compiler-supported scalar type: + +```text +i32 +``` + +First-pass struct declarations introduce nominal struct types as described in +section 12. Those types are supported only as constructor temporaries consumed +by immediate field access; they are not valid current supported function +parameter types, function return types, or local declaration types. + +First-pass fixed arrays are specified in section 13. `(array i32 N)` is a +current compiler-supported immutable local declaration type only when the local +is initialized directly from an `(array i32 value...)` constructor with matching +length. It is not a current supported function parameter type or function +return type. + +First-pass option/result constructors are specified in section 14. `(option +i32)` and `(result i32 i32)` are current compiler-supported function return +types only for functions whose single body expression is the matching direct +constructor. They are not current supported function parameter types or local +declaration types. + +`print_i32` status: `print_i32` is a temporary compiler intrinsic in v0. It is +not yet specified as a user-defined function, standard-library binding, runtime +binding, import, or foreign function. Programs may use `(print_i32 value)` only +as part of the current strict compiler subset described in section 1. + +`unit` status: `unit` is a valid internal/builtin result type for unit-like +expressions such as `print_i32` and the supported `(set name value)` form. +It is not a user-declarable supported function return type until Glagol supports +parsing, checking, lowering, formatting, diagnostics, backend behavior, and +tests for user-declared `unit` returns. + +Design-target primitive types: + +```text +bool +i8 i16 i64 +u8 u16 u32 u64 +f32 f64 +char +string +unit +never +``` + +Broader design-target compound types: + +```slo +(ptr T) +(array T N) +(slice T) +(option T) +(result T E) +``` + +The first-pass `(array i32 N)`, `(option i32)`, and `(result i32 i32)` cases +above are the only promoted compound-type exceptions. Generic, nested, and +non-`i32` compound-type payloads remain design targets. + +--- + +## 6. Functions + +Function form: + +```slo +(fn name ((arg Type) ...) -> ReturnType + body...) +``` + +Example: + +```slo +(fn add ((a i32) (b i32)) -> i32 + (+ a b)) +``` + +v0 rules: + +- parameters are explicitly typed +- return type is explicit +- body contains one or more expressions +- the last expression is the return value +- all control paths must produce the declared return type +- current supported fixtures use `i32` returns, except for the direct + option/result constructor-return functions described in section 14 + +--- + +## 7. Variables + +Status: supported for explicit `i32` local bindings in function bodies and +top-level test bodies. + +Immutable binding: + +```slo +(let name type value) +``` + +Mutable binding: + +```slo +(var name type value) +(set name value) +``` + +Supported implementation scope: + +- Explicit local types are required in v0. +- `i32` locals are supported with `let` and `var`; immutable `(array i32 N)` + locals are supported only under the first-pass array contract in section 13. +- Local declarations are allowed only inside function bodies, top-level test + bodies, and promoted lexical unsafe blocks contained by those bodies. +- Local declarations and `set` forms are sequential body forms before the final + result expression. +- A function or test body must still have a final result expression. + +Binding rules: + +- `(let name type value)` creates a new immutable local binding initialized from + `value`. +- `(var name type value)` creates a new mutable local binding initialized from + `value`. +- The initializer is checked before the new local is introduced, so a binding + may not refer to itself. +- The initializer expression must exactly match the explicit local type. +- The explicit local type must be `i32`. +- Local bindings are visible only after their declaration and only within the + containing function body, test body, or lexical unsafe block. Bindings from + an outer function or test body remain visible inside a nested unsafe block. +- Lexical unsafe blocks introduce the only current nested local scope. `if` + branches and first-pass `while` bodies do not introduce local declaration + scopes. + +Shadowing and redeclaration: + +- v0 forbids shadowing in local bodies. +- A local binding name must not match any parameter name in the same function. +- A local binding name must not match any earlier local binding in the same + function or test body. +- A local binding name must not match a top-level function name or compiler + intrinsic visible in the same module. +- Parameters are immutable for v0 and are not valid `set` targets. +- Top-level function names are not local bindings. A local reference resolves to + a parameter or local binding; a call resolves to a callable top-level + function or compiler intrinsic unless a later namespace rule says otherwise. + +Assignment: + +- `(set name value)` assigns an existing mutable local binding. +- `set` produces builtin `unit`. +- `set` may appear only as a non-final sequential body form, because supported + function/test results are not `unit`. +- The assigned expression must exactly match the mutable local's type. +- Assigning an unknown name is an `UnknownVariable` diagnostic. +- Assigning a parameter is a `CannotAssignParameter` diagnostic. +- Assigning an immutable `let` binding is a `CannotAssignImmutableLocal` + diagnostic. +- Assigning a value with the wrong type is a `TypeMismatch` diagnostic. +- Duplicating a local name is a `DuplicateLocal` diagnostic. +- Redeclaring a parameter as a local is a `LocalRedeclaresParameter` + diagnostic. +- Colliding with a visible function or compiler intrinsic is a + `LocalShadowsCallable` diagnostic. +- Declaring any non-`i32` local is an `UnsupportedLocalType` diagnostic. +- Using a local declaration in an inline expression is a + `LocalDeclarationNotAllowed` diagnostic. + +Typed-core meaning: + +```text +LocalLet { + name, + type: i32, + init: TExpr, + mutable: false, + span, + name_span, + type_span, + init_span +} + +LocalVar { + name, + type: i32, + init: TExpr, + mutable: true, + span, + name_span, + type_span, + init_span +} + +SetLocal { + target: LocalId, + value: TExpr, + type: unit, + span, + name_span, + value_span +} +``` + +Lowering: + +- `let` and `var` lower to typed local storage or SSA values at the + implementation's discretion, provided source order, mutability checks, and + diagnostics are preserved. +- `set` lowers to an assignment/store to the resolved mutable local. +- A local that is never read may be accepted in v0; unused-local warnings are + outside the v0 required diagnostics. + +Formatter behavior: + +```slo +(fn add_then_double ((a i32) (b i32)) -> i32 + (let sum i32 (+ a b)) + (var doubled i32 (+ sum sum)) + (set doubled (+ doubled 1)) + doubled) +``` + +The formatter must print each local declaration and `set` as one body form +indented by two spaces. Simple `let`, `var`, and `set` forms stay inline when +their initializer or assigned expression is an inline expression under the +current expression formatter. The final body expression follows the same +two-space indentation and may refer to earlier locals. + +--- + +## 8. Expressions + +Slovo v0 is expression-oriented. + +Most forms produce a value. + +Current compiler-supported expressions are the expression forms required by the +supported fixtures: integer literals, parameter references, local references, +local declarations, local assignment, binary `+`, user function calls, +temporary intrinsic `print_i32` calls, equality comparison in tests, ordering +comparison in loop conditions, value-producing `if`, first-pass `while`, struct +construction with immediate field access, fixed `i32` array construction, +literal array indexing, first-pass option/result constructors, lexical +`unsafe` blocks containing otherwise supported safe body forms, and +final-expression returns. + +Struct construction and immediate field access are supported under the strict +first-pass contract in section 12. Array construction, immutable array locals, +and literal checked indexing are supported under the strict first-pass contract +in section 13. Option/result constructors are supported under the strict +first-pass direct-return contract in section 14. Lexical `unsafe` blocks are +supported under the strict first-pass contract in section 15. + +Arithmetic: + +```slo +(+ a b) +(- a b) +(* a b) +(/ a b) +``` + +Comparison: + +```slo +(= a b) +(< a b) +(> a b) +(<= a b) +(>= a b) +``` + +v0 rules: + +- operands must have compatible exact types +- numeric casts are explicit +- comparisons return `bool` + +Status note: arithmetic and comparison beyond the supported fixture are design +targets until covered by the strict support rule in section 1. + +The top-level `test` contract in section 16 introduces an expected-`bool` +context, but it does not promote any expression form by itself. A promoted +test fixture must use only bool-producing expressions that also satisfy the +strict support rule, such as equality comparison once it is accepted as part of +a promoted fixture. + +--- + +## 9. Canonical Formatter + +Status: current strict supported syntax only. + +The formatter is part of the Slovo language contract. For the current supported +subset, canonical output follows these rules: + +- use LF line endings and end every file with one trailing newline +- remove trailing whitespace +- use spaces, not tabs +- put top-level forms at column 1 +- keep `(module name)` on one line +- separate top-level forms with exactly one blank line +- print each supported top-level `fn` as a multi-line form +- print the function header on one line as + `(fn name ((arg Type) ...) -> ReturnType` +- print an empty parameter list as `()` +- keep supported parameter lists inline as `((name i32) (name i32))` +- indent each function body expression by two spaces +- print one supported body expression per line +- keep supported expression forms inline: integer literals, parameter + references, local references, local declarations, `set`, binary `+`, + user-defined function calls, `(print_i32 value)`, and first-pass + option/result constructors +- keep nested supported calls inline, for example `(print_i32 (add 20 22))` +- place the closing `)` for a function on the final body-expression line when + that expression is inline +- preserve full-line comments attached to the nearest following form, without + reflowing comment text + +Canonical current fixture: + +```slo +(module main) + +(fn add ((a i32) (b i32)) -> i32 + (+ a b)) + +(fn main () -> i32 + (print_i32 (add 20 22)) + 0) +``` + +Formatter rules for `let`, `var`, and `set` are part of the current supported +formatter fixture set. The `if` formatter contract and first-pass `while` +formatter contract are also part of the current formatter fixture set. +The first-pass formatter rules for `struct` are also part of the current +formatter fixture set. Section 13 specifies the first-pass array/index +formatter contract, and `examples/formatter/array.slo` is the canonical +formatter fixture for that promoted subset. Section 14 specifies the +first-pass option/result constructor formatter contract, and +`examples/formatter/option-result.slo` is the canonical formatter fixture for +that promoted subset. Section 15 specifies the lexical `unsafe` formatter +contract, and `examples/formatter/unsafe.slo` is the canonical formatter +fixture for that promoted subset. Formatter rules for strings, slices, broader +option/result value flow, pointer types, raw memory operations, and general +user-declared `unit` returns remain design targets. + +Top-level `test` formatter contract: + +```slo +(test "add works" + (= (add 2 3) 5)) +``` + +The formatter must print each top-level test as a multi-line top-level form, +keep the string name on the opening line, indent the single test expression by +two spaces, and place the closing `)` on the expression line when that +expression is inline. Test forms follow the same top-level blank-line and +comment attachment rules as `fn`. + +--- + +## 10. Conditionals + +Status: compiler-supported value expression through `examples/supported/if.slo` +under the strict support rule in section 1. + +Conditional form: + +```slo +(if condition then-expression else-expression) +``` + +v0 rules: + +- condition must be `bool` +- both branches must have the same type +- `if` returns that type +- `if` is an expression form and may be used where its result type is expected. +- Branches are expressions, not declaration scopes. A local declaration inside + an `if` branch remains unsupported in this pass. +- The formatter prints `if` as a multi-line expression: + +```slo +(if (< value 3) + 10 + 20) +``` + +Diagnostics: + +- `MalformedIfForm`: missing condition, then expression, else expression, or an + invalid operand shape. Span: whole `if` form. Expected: + `(if condition then-expression else-expression)`. +- `IfConditionNotBool`: checked condition type is not `bool`. Span: condition + expression. Expected: `bool`. Found: checked condition type. +- `IfBranchTypeMismatch`: then and else branches do not have the same checked + type. Span: whole `if` form. Expected: then-branch type. Found: + else-branch type. + +--- + +## 11. Loops + +Status: compiler-supported first-pass contract through +`examples/supported/while.slo` under the strict support rule in section 1. + +First-pass v0 loop form: + +```slo +(while condition + body-form...) +``` + +Surface placement: + +- `(while condition body-form...)` is a sequential body form. +- It is allowed only inside function bodies and top-level test bodies. +- In the first supported implementation, it may appear only before the final + result expression of supported `i32` functions and `bool` tests. +- It is not valid as an inline expression argument. +- It is not valid as the final result expression of a supported `i32` function + or `bool` test, because it returns builtin `unit`. + +Checking rules: + +- The condition is checked in the current local environment and must have type + `bool`. +- The condition is re-evaluated before each iteration. +- A false initial condition executes zero body iterations. +- The body must contain one or more body forms. +- `while` returns builtin `unit`. +- The first implementation does not introduce nested local-scope semantics. + Local declarations inside a `while` body are not supported. +- Supported first-pass loop body forms are limited to assignments to existing + mutable locals with `(set name value)` and supported unit-producing calls such + as `(print_i32 value)`. +- Loop body forms must each check as `unit`. +- Existing locals visible before the `while` are visible in the condition and + body. Assignments in the body update the existing mutable local. +- `break` and `continue` are not part of this pass. +- Nested `while` is not part of the first-pass support target. + +Typed-core meaning: + +```text +While { + condition: TExpr, + body: [TBodyForm], + type: unit, + span, + condition_span, + body_spans +} +``` + +Lowering: + +- A `while` lowers to a condition block, a body block, and an exit block. +- Control enters the condition block first. +- If the condition evaluates to true, control enters the body block; after the + body completes, control branches back to the condition block. +- If the condition evaluates to false, control branches to the exit block. +- The exit continuation has builtin `unit` as the loop result and then + continues with the next sequential body form. +- Locals assigned in the body must preserve source-order mutation semantics. + The implementation may use storage slots or equivalent SSA construction, but + it must not treat the loop body as a new declaration scope in this first pass. + +Formatter behavior: + +```slo +(fn sum_to ((n i32)) -> i32 + (var i i32 0) + (var sum i32 0) + (while (< i n) + (set sum (+ sum i)) + (set i (+ i 1))) + sum) +``` + +The formatter must print `while` as a multi-line body form. The opening line +contains `(while` and the formatted condition. Each loop body form is printed on +its own line, indented two spaces deeper than the `while` form. The closing `)` +is placed on the final body-form line when that body form is inline. The +following function or test body form resumes at the original body indentation. + +Diagnostics: + +- `MalformedWhileForm`: missing condition or an invalid operand shape. Span: + whole `while` form or offending operand. Expected: + `(while condition body...)`. +- `WhileConditionNotBool`: checked condition type is not `bool`. Span: + condition expression. Expected: `bool`. Found: checked condition type. +- `EmptyWhileBody`: a `while` has no body forms after the condition. Span: + whole `while` form or the location where the first body form was expected. +- `LocalDeclarationInWhileBodyUnsupported`: a `let` or `var` appears directly + in a first-pass `while` body. Span: offending local declaration. Hint: + declare the local before the loop and update an existing mutable local with + `set`. +- `NestedWhileUnsupported`: a nested `while` appears directly in a first-pass + `while` body. Span: nested `while`. Hint: keep first-pass loop bodies flat. +- `WhileBodyFormNotUnit`: a loop body form does not produce `unit`. Span: + offending body form. Expected: `unit`. Found: checked body-form type. +- Existing diagnostics such as `UnknownVariable`, `CannotAssignParameter`, + `CannotAssignImmutableLocal`, `TypeMismatch`, `ArityMismatch`, and + `UnsupportedBackendFeature` apply inside `while` conditions and bodies. +- Using a `while` as the final expression of an `i32` function must report + `ReturnTypeMismatch` with expected `i32` and found `unit`. +- Using a `while` as the final expression of a top-level test must report + `TestExpressionNotBool` with expected `bool` and found `unit`. + +Minimal comparison dependency: + +The promoted first fixture for `while` uses `<` to produce the loop condition. +Promoting `while` does not by itself promote the broader comparison family or +unrelated arithmetic. The supported comparison surface for this pass is the +exact `i32, i32 -> bool` `<` form exercised by the fixture, with parser/lowerer +support, checker behavior, LLVM behavior, formatter behavior, and tests. + +--- + +## 12. Structs + +Status: compiler-supported first-pass contract through +`examples/supported/struct.slo` under the strict support rule in section 1. + +Promotion boundary: + +- The supported fixture may use only top-level `i32` struct declarations, + constructor expressions, and field access returning `i32`. +- Local struct storage is a follow-up. The first pass does not support + `(let p Point ...)`, `(var p Point ...)`, assigning a struct value, passing a + struct as a parameter, or returning a struct from a function. +- Methods, generics, field mutation, nested struct fields, recursive structs, + struct params/returns, layout reflection, cross-module structs, and ABI + promises are explicitly outside the first pass. + +Struct definition: + +```slo +(struct Point + (x i32) + (y i32)) +``` + +Struct declarations are top-level forms, sibling to `fn` and `test`. + +Surface rules: + +- The form is `(struct Name (field i32)...)`. +- `Name` is a normal identifier. +- Each `field` is a normal identifier. +- A struct must declare one or more fields. +- First-pass fields must be exactly `i32`. +- Field order is source order and is part of the v0 typed-core contract. +- Struct names are nominal. Two structs with the same fields are not the same + type. +- A struct name must be unique in the module and must not collide with a + top-level function name or compiler intrinsic visible in constructor + expression position. +- Field names must be unique within the struct. +- A struct declaration is not an expression and produces no runtime value. + +Constructor expression: + +```slo +(Point (x 3) (y 4)) +``` + +Constructor rules: + +- The callee position must be the declared struct name. +- Each constructor field is written as `(field value)`, not as a keyword token. +- Constructor fields must list every declared field exactly once. +- Constructor fields must appear in declaration order for the first pass. +- Each field value is checked in source order and must exactly match the + declared field type. Since first-pass fields are `i32`, each value must check + as `i32`. +- Constructor evaluation preserves field value evaluation order. +- A constructor produces a value of the nominal struct type. +- In the first promotion target, a struct constructor may be used only as the + immediate value being inspected by field access, for example + `(. (Point (x 3) (y 4)) x)`. Wider struct value flow requires the follow-up + local/signature/storage contract. + +Field access: + +```slo +(. p x) +``` + +First-pass examples should use immediate constructor access: + +```slo +(. (Point (x 3) (y 4)) x) +``` + +Field access rules: + +- The form is `(. value field)`. +- `field` is a normal identifier, not a keyword token or string. +- `value` must check as a known struct type. +- The named field must exist on that struct type. +- The expression result type is the field type. In the first pass this is + always `i32`. +- The value expression is evaluated once. +- Field access is read-only. There is no field mutation form in the first pass. + +Checking rules: + +- The checker collects and validates top-level struct declarations before + checking function and test bodies in the same module. +- Struct declaration checking validates top-level placement, identifier shape, + module-level name uniqueness, field-name uniqueness, and `i32` field types. +- Constructor checking resolves the callee name in the struct namespace, then + checks constructor field names, field order, field completeness, duplicate + fields, extra fields, and each field value type. +- Field access checking first checks the value expression, then resolves the + requested field against the value's nominal struct type. +- A struct constructor or other struct-typed expression outside direct field + access is not part of the first pass. It must report + `StructValueFlowUnsupported`, except for the more specific local and + signature diagnostics below. +- Existing expected-type diagnostics apply after field access. For example, + `(. (Point (x 3) (y 4)) x)` checks as `i32` and may be used anywhere an + `i32` expression is currently supported. + +Typed-core meaning: + +```text +StructDecl { + name: StructId, + fields: [StructField], + span, + name_span +} + +StructField { + name: FieldId, + type: i32, + index: FieldIndex, + span, + name_span, + type_span +} + +StructConstruct { + struct_id: StructId, + fields: [(FieldId, TExpr)], + type: Struct(StructId), + span, + name_span, + field_spans +} + +StructFieldAccess { + value: TExpr, + field_id: FieldId, + type: i32, + span, + value_span, + field_span +} +``` + +Lowering: + +- A struct declaration lowers to type metadata in the checked module, not to a + runtime declaration by itself. +- A constructor lowers to an aggregate value or equivalent typed-core value with + fields in declaration order. +- Field access lowers to extracting the selected field from that aggregate. +- For the first promotion target, an implementation may lower direct + constructor field access without materializing addressable struct storage. +- No stable LLVM, C, FFI, or cross-module ABI layout is promised in v0. +- If a checked struct form reaches a backend gap, Glagol must report + `UnsupportedBackendFeature` instead of panicking. + +Formatter behavior: + +```slo +(struct Point + (x i32) + (y i32)) + +(fn point_x () -> i32 + (. (Point (x 3) (y 4)) x)) +``` + +The formatter must print each top-level `struct` as a multi-line top-level +form. The opening line is `(struct Name`; each field declaration is printed on +its own line indented two spaces; the closing `)` is placed on the final field +line. Top-level blank-line and comment attachment rules match `fn` and `test`. + +Constructor expressions with inline `i32` field values stay inline as +`(Name (field value)...)`. Field access stays inline when its value expression +is inline. If a future formatter must break a constructor over multiple lines, +the constructor opening line is `(Name`, each `(field value)` pair is indented +two spaces, and field order remains declaration order. + +Diagnostics: + +- `MalformedStructForm`: missing name, non-identifier name, missing fields, or + invalid field shape. Span: whole `struct` form or offending operand. + Expected: `(struct Name (field i32)...)`. +- `StructDeclarationNotTopLevel`: a `struct` form appears anywhere other than + the top level. Span: offending `struct` form. +- `EmptyStructUnsupported`: a struct declares no fields. Span: whole `struct` + form. Hint: declare at least one `i32` field. +- `DuplicateStruct`: duplicate struct name in the same module. Span: + duplicate name, with a related span on the original struct name. +- `StructNameConflictsCallable`: struct name collides with a top-level function + name or compiler intrinsic visible in constructor position. Span: struct + name, with a related span when the colliding source name exists. +- `DuplicateStructField`: duplicate field name within one struct. Span: + duplicate field name, with a related span on the original field name. +- `UnsupportedStructFieldType`: field type is not `i32`. Span: field type. + Expected: `i32`. Found: written field type. +- `MalformedStructConstructor`: constructor operand shape is invalid. Span: + whole constructor or offending field pair. Expected: + `(Name (field value)...)`. +- `UnknownStruct`: constructor name does not resolve to a struct. Span: + constructor name. +- `DuplicateStructConstructorField`: constructor lists a field more than once. + Span: duplicate constructor field, with a related span on the first use. +- `MissingStructField`: constructor omits a declared field. Span: + whole constructor. Expected: missing field name. +- `UnknownStructField`: constructor or field access names a field not declared + by the struct. Span: extra constructor field name or accessed field name. +- `StructConstructorFieldOrderMismatch`: constructor field order differs from + declaration order. Span: first out-of-order constructor field. Expected: + declared field name at that position. Found: written field name. +- `TypeMismatch`: constructor field value does not match its declared field + type. Span: field value. Expected: declared field type. Found: checked value + type. +- `MalformedFieldAccess`: field access is not `(. value field)`. Span: whole + access form or offending operand. +- `FieldAccessOnNonStruct`: field access value does not check as a struct type. + Span: value expression. Expected: struct value. Found: checked value type. +- `UnsupportedStructLocal`: a struct type is used as a local `let` or `var` + type in the first pass. Span: local type. +- `UnsupportedStructSignatureType`: a struct type is used as a function + parameter or return type in the first pass. Span: signature type. +- `UnsupportedStructFieldAccess`: field access uses a non-immediate struct + constructor value in the first pass. Span: field access expression. +- `FieldMutationUnsupported`: an implementation that accepts a generalized + assignment target must reject field mutation in the first pass. Span: + attempted field assignment target. + +--- + +## 13. Arrays and Checked Indexing + +Status: strict first-pass compiler-supported syntax, promoted through +`examples/supported/array.slo` and `examples/formatter/array.slo`. + +Promotion boundary: + +- The first pass supports only fixed-length arrays with `i32` elements. +- Array lengths must be positive integer literals. Zero-length arrays are not + supported in this pass. +- Array constructors produce immutable temporary array values. +- Immutable `let` locals may store `(array i32 N)` values only when initialized + directly from a matching array constructor. +- The first promoted fixture may index only an immediate array constructor or + an immutable array local with a non-negative integer literal index known at + compile time. +- Dynamic indices are not supported in this pass, so v0 does not yet define a + runtime bounds trap or test-runner trap result for arrays. +- Mutable array locals, array parameters, array returns, array mutation, nested + arrays, arrays of structs, slices, and unchecked indexing are follow-up work. + +Fixed array type: + +```slo +(array i32 N) +``` + +Type rules: + +- `N` is a source integer literal in type position, not an expression. +- `N` must be greater than `0`. +- The only first-pass element type is `i32`. +- `(array i32 N)` is supported only as an immutable local declaration type in + the first pass. Function parameter and return types remain unsupported. +- `(array T N)` with any `T` other than `i32` is rejected by the first pass. + +Array constructor expression: + +```slo +(array i32 value...) +``` + +Constructor rules: + +- The first operand after `array` is the element type and must be `i32`. +- At least one value is required. +- Each value is checked in source order and must check exactly as `i32`. +- The constructor length is inferred from the value count. +- The constructor result type is `(array i32 count)`. +- In an expected-type context, a constructor of type `(array i32 count)` is + valid for `(array i32 N)` only when `count == N`. +- Constructor value evaluation order is left to right. + +Checked index expression: + +```slo +(index array-expr index-expr) +``` + +First-pass supported example: + +```slo +(index (array i32 10 20 30) 1) +``` + +```slo +(let values (array i32 3) (array i32 4 5 6)) +(index values 2) +``` + +Index rules: + +- The form is exactly `(index array-expr index-expr)`. +- `array-expr` must check as `(array i32 N)`. +- For the first promotion target, `array-expr` must be an immediate array + constructor or an immutable array local. +- `index-expr` must be a non-negative `i32` integer literal in the first pass. +- The literal index must satisfy `0 <= index < N`. +- The result type is `i32`. +- The array expression is evaluated once. +- Because accepted first-pass indices are compile-time literals, bounds + checking happens during checking. A dynamic checked-index form must be + rejected until runtime trap behavior is specified and tested. + +Typed-core meaning: + +```text +ArrayType { + element: i32, + length: NonZeroUsize, + span, + element_span, + length_span +} + +ArrayConstruct { + element: i32, + length: NonZeroUsize, + values: [TExpr], + type: Array(i32, length), + span, + element_span, + value_spans +} + +ArrayIndex { + array: TExpr, + index: usize, + type: i32, + span, + array_span, + index_span +} +``` + +Lowering: + +- An array constructor lowers to an aggregate value or equivalent typed-core + value with elements in source order. +- A first-pass index lowers to extracting the compile-time constant in-bounds + element from that aggregate. +- An immutable array local lowers to implementation-owned storage sufficient + for literal indexing inside the current function or test body. +- The backend must not generate unchecked dynamic array access for this + contract. Dynamic indices remain unsupported until runtime checked-index trap + behavior is specified. +- For the first promotion target, an implementation may lower immediate + constructor indexing without materializing addressable array storage, and may + lower immutable local indexing through local aggregate storage. +- No stable LLVM, C, FFI, or cross-module ABI layout is promised in v0. +- If a checked array form reaches a backend gap, Glagol must report + `UnsupportedBackendFeature` instead of panicking. + +Formatter behavior: + +```slo +(fn second () -> i32 + (index (array i32 10 20 30) 1)) + +(fn local_sum () -> i32 + (let values (array i32 3) (array i32 4 5 6)) + (+ (index values 0) (index values 2))) +``` + +The formatter prints `(array i32 N)` types inline. It prints constructor +expressions with inline `i32` values as `(array i32 value...)`. It prints +first-pass index expressions inline as `(index array-expr index-expr)` when the +array expression and index expression are inline. Function and test body +indentation follows the existing one-body-form-per-line rule. + +Diagnostics: + +- `MalformedArrayType`: array type syntax is not `(array i32 N)`. Span: whole + type form or offending operand. Expected: `(array i32 N)`. +- `UnsupportedArrayElementType`: array element type is not `i32`. Span: + element type. Expected: `i32`. Found: written element type. +- `ZeroLengthArrayUnsupported`: array length is zero. Span: array type. Hint: + use one or more `i32` elements. +- `MalformedArrayConstructor`: constructor syntax is not + `(array i32 value...)`. Span: whole constructor or offending operand. +- `InvalidArrayElementType`: constructor element type syntax is invalid. Span: + element type. Hint: first-pass arrays use `i32` elements. +- `EmptyArrayUnsupported`: constructor has no element values. Span: whole + constructor. Hint: provide one or more `i32` values. +- `ArrayLengthMismatch`: constructor value count does not match the expected + `(array i32 N)` length. Span: whole constructor. Expected: expected length. + Found: value count. +- `TypeMismatch`: constructor element value does not check as `i32`. Span: + element value. Expected: `i32`. Found: checked value type. +- `MalformedArrayIndex`: index syntax is not `(index array-expr index-expr)`. + Span: whole index form or offending operand. +- `IndexOnNonArray`: `array-expr` does not check as an array type. Span: + array expression. Expected: `(array i32 N)`. Found: checked value type. +- `ArrayIndexNotI32`: `index-expr` does not check as `i32`. Span: index + expression. Expected: `i32`. Found: checked value type. +- `UnsupportedArrayIndexBase`: index uses an array value that is not an + immediate constructor or immutable array local. Span: array expression. Hint: + use `(index (array i32 ...) N)` or index a `let` array local. +- `DynamicArrayIndexUnsupported`: `index-expr` is not an integer literal. Span: + index expression. Hint: first-pass checked indexing requires a literal index. +- `ArrayIndexOutOfBounds`: literal index is outside `0 <= index < N`. Span: + index literal. Expected: valid index range. Found: literal index. +- `UnsupportedArrayLocalInitializer`: an array local is initialized from + anything other than a direct array constructor. Span: initializer expression. + Hint: use `(let values (array i32 N) (array i32 ...))`. +- `MutableArrayLocalUnsupported`: `(array i32 N)` is used as a mutable `var` + local. Span: local type. Hint: declare array locals with `let`. +- `UnsupportedArraySignatureType`: `(array i32 N)` is used as a function + parameter or return type in the first pass. Span: signature type. +- `UnsupportedArrayEquality`: an array value is compared with `=`. Span: + equality expression. +- `UnsupportedArrayPrint`: an array value is passed to `print_i32`. Span: + print argument. +- `ArrayMutationUnsupported`: an implementation that accepts a generalized + assignment target must reject element mutation in the first pass. Span: + attempted array element assignment target. + +Slices remain a broader design target. No slice type, slice constructor, +borrowed view, pointer decay, or slice indexing behavior is part of this +first-pass array contract. + +--- + +## 14. Option and Result + +Status: strict first-pass compiler-supported syntax, promoted through +`examples/supported/option-result.slo` and +`examples/formatter/option-result.slo`. + +Nullable absence uses `option`. + +Recoverable failure uses `result`. + +Promotion boundary: + +- The first pass supports only `(option i32)` and `(result i32 i32)`. +- Constructors are the only promoted option/result value operations. +- Constructors are supported only as the single final body expression of a + function whose declared return type exactly matches the constructor result. +- Function parameters, local declarations, mutable storage, assignment, + matching, unwrap, equality, printing, and calls that pass or receive + option/result values are not part of this pass. +- Nested option/result types, non-`i32` payloads, strings, arrays, structs, + slices, and pointers as payloads are not part of this pass. +- No stable LLVM, C, FFI, cross-module, or serialized ABI/layout promise is + made for option/result values in v0. + +First-pass option type: + +```slo +(option i32) +``` + +First-pass result type: + +```slo +(result i32 i32) +``` + +Constructor expressions: + +```slo +(some i32 value) +(none i32) + +(ok i32 i32 value) +(err i32 i32 value) +``` + +Type rules: + +- `(option i32)` is supported only as a function return type under the direct + constructor-return rule above. +- `(result i32 i32)` is supported only as a function return type under the + direct constructor-return rule above. +- `(some i32 value)` checks `value` as `i32` and produces `(option i32)`. +- `(none i32)` produces `(option i32)` and has no payload expression. +- `(ok i32 i32 value)` checks `value` as `i32` and produces + `(result i32 i32)`. +- `(err i32 i32 value)` checks `value` as `i32` and produces + `(result i32 i32)`. +- Constructor type operands are type names in source, not expressions. +- Constructor payload expressions use the already-supported `i32` expression + rules. The constructor itself may not be nested in another expression for + this first pass. +- Pattern matching is not v0. There is no promoted operation that observes, + destructures, compares, prints, or unwraps an option/result value. + +Typed-core meaning: + +```text +OptionType { + payload: i32, + span, + payload_span +} + +ResultType { + ok: i32, + err: i32, + span, + ok_span, + err_span +} + +OptionConstruct { + variant: Some | None, + payload: i32, + value: Option>, + type: Option(i32), + span, + payload_span, + value_span +} + +ResultConstruct { + variant: Ok | Err, + ok: i32, + err: i32, + value: TExpr, + type: Result(i32, i32), + span, + ok_span, + err_span, + value_span +} +``` + +Lowering and backend boundary: + +- A first-pass option/result constructor lowers to a compiler-owned tagged + value representation sufficient to return the value from the direct-return + function. +- The backend may choose any internal representation that preserves the + checked variant and `i32` payload for future typed-core use. +- The backend must not expose or rely on a stable option/result ABI, layout, + discriminant value, padding, or cross-module representation in v0. +- No storage, mutation, comparison, print, unwrap, pattern-match, or parameter + passing lowering is required or supported by this contract. +- If a checked option/result form reaches a backend gap, Glagol must report + `UnsupportedBackendFeature` instead of panicking. + +Formatter behavior: + +```slo +(fn some_value ((value i32)) -> (option i32) + (some i32 value)) + +(fn no_value () -> (option i32) + (none i32)) + +(fn ok_value ((value i32)) -> (result i32 i32) + (ok i32 i32 value)) + +(fn err_value ((code i32)) -> (result i32 i32) + (err i32 i32 code)) +``` + +The formatter prints `(option i32)` and `(result i32 i32)` return types inline. +It prints first-pass constructors inline as `(some i32 value)`, `(none i32)`, +`(ok i32 i32 value)`, and `(err i32 i32 value)` when the payload expression is +inline. Function body indentation follows the existing one-body-form-per-line +rule. + +Diagnostics: + +- `MalformedOptionType`: option type syntax is not `(option i32)`. Span: whole + type form or offending operand. Expected: `(option i32)`. +- `UnsupportedOptionPayloadType`: option payload type is not `i32`. Span: + payload type. Expected: `i32`. Found: written payload type. +- `MalformedResultType`: result type syntax is not `(result i32 i32)`. Span: + whole type form or offending operand. Expected: `(result i32 i32)`. +- `UnsupportedResultPayloadType`: result ok or err type is not `i32`. Span: + offending payload type. Expected: `i32`. Found: written payload type. +- `MalformedOptionConstructor`: constructor syntax is not `(some i32 value)` + or `(none i32)`. Span: whole constructor or offending operand. +- `MalformedResultConstructor`: constructor syntax is not + `(ok i32 i32 value)` or `(err i32 i32 value)`. Span: whole constructor or + offending operand. +- `TypeMismatch`: constructor payload value does not check as `i32`. Span: + payload value. Expected: `i32`. Found: checked value type. +- `UnsupportedOptionResultReturn`: an option/result return type is used by a + function whose body is not exactly one matching direct constructor + expression. Span: return type or final expression. +- `UnsupportedOptionResultSignatureType`: an option/result type is used as a + function parameter type. Span: signature type. +- `UnsupportedOptionResultLocal`: an option/result type is used as a local + declaration type. Span: local type. +- `UnsupportedOptionResultFlow`: an option/result constructor or value is + used anywhere other than the supported direct-return position. Span: value + expression. +- `UnsupportedOptionResultEquality`: an option/result value is compared with + `=`. Span: equality expression. +- `UnsupportedOptionResultPrint`: an option/result value is passed to + `print_i32`. Span: print argument. +- `UnsupportedOptionResultPatternMatch`: a match/destructure/unwrap form tries + to observe an option/result value. Span: observing expression. Hint: pattern + matching is not part of the v0 option/result constructor pass. + +--- + +## 15. Unsafe + +Status: strict lexical marker support, promoted through +`examples/supported/unsafe.slo` and `examples/formatter/unsafe.slo`. + +First-pass unsafe form: + +```slo +(unsafe + body-form... + final-expression) +``` + +Promotion boundary: + +- The first pass supports only the lexical `unsafe` expression block. +- The marker is lexically visible in source; there is no implicit unsafe + context in v0. +- An unsafe block may contain the same sequential body forms already supported + in normal function and top-level test bodies, followed by one final + expression. +- The block returns the final expression's checked type. +- The block marks a lexical unsafe context for diagnostics only. It does not + make raw memory operations supported. +- Raw allocation, deallocation, pointer loads or stores, pointer arithmetic, + unchecked indexing, raw reinterpretation, and FFI calls remain unsupported. + +Example using only supported safe forms: + +```slo +(fn add_one_in_unsafe ((value i32)) -> i32 + (unsafe + (let one i32 1) + (+ value one))) +``` + +Surface rules: + +- The form is exactly `(unsafe body-form... final-expression)`. +- The form must contain at least one expression after `unsafe`. +- `unsafe` is an expression form and may be used wherever its final + expression's type is expected. +- Body forms before the final expression use the same first-pass sequential + body-form rules as the surrounding function or top-level test body. Today + that means local declarations, local assignment, supported unit-producing + calls, and first-pass loops only where those forms are otherwise supported. +- Local declarations inside an unsafe block are scoped to that block. They are + visible only after their declaration and only until the end of the block. +- Names declared inside an unsafe block must not shadow parameters, locals from + an outer body, earlier locals in the same unsafe block, top-level functions, + or compiler intrinsics under the existing v0 no-shadowing rules. +- A nested unsafe block is allowed only if its body forms and final expression + satisfy this same lexical contract. + +Typed-core meaning: + +```text +UnsafeBlock { + body: [TBodyForm], + result: TExpr, + type: T, + span, + body_spans, + result_span +} +``` + +The checker carries an `in_unsafe` lexical flag while checking the block body +and final expression. That flag is only used to choose diagnostics for unsafe +operation heads; it does not widen the set of supported expression forms. + +Unsafe operation heads requiring the lexical marker: + +```text +alloc +dealloc +load +store +ptr_add +unchecked_index +reinterpret +ffi_call +``` + +These names are reserved as unsafe operation heads in expression position for +v0. They do not resolve as ordinary user-defined calls in this pass. + +Diagnostic boundary for those heads: + +- Outside an unsafe block, any expression whose head is one of the names above + must report `UnsafeRequired`. Span: whole operation form, with the head span + as the primary operation name span. Hint: wrap the operation in an `unsafe` + block. +- Inside an unsafe block, the same operation heads must report + `UnsupportedUnsafeOperation`. Span: whole operation form, with the head span + as the primary operation name span. Hint: raw memory operations are outside + the v0 unsafe contract. + +Lowering and backend boundary: + +- A lexical unsafe block lowers like an ordinary expression block: lower each + checked body form in source order, then lower the final expression and use + its value as the block value. +- The lowered representation must preserve source order and local block scope. +- There is no backend lowering for raw memory operations in this pass. +- If a checked lexical unsafe block containing only supported safe forms reaches + a backend gap, Glagol must report `UnsupportedBackendFeature` instead of + panicking. + +Formatter behavior: + +```slo +(fn add_one_in_unsafe ((value i32)) -> i32 + (unsafe + (let one i32 1) + (+ value one))) +``` + +The formatter must print `unsafe` as a multi-line expression block. The opening +line contains only `(unsafe`. Each body form and the final expression are +printed on their own lines, indented two spaces deeper than the `unsafe` form. +The closing `)` is placed on the final expression line when that expression is +inline. If an unsafe block appears inside another expression, the block may be +broken over multiple lines using the same indentation rule. + +Diagnostics: + +- `MalformedUnsafeForm`: an `unsafe` form has no final expression or has an + invalid operand shape. Span: whole `unsafe` form or the location where the + first expression was expected. Expected: + `(unsafe body-form... final-expression)`. +- `UnsafeRequired`: a raw unsafe operation head appears outside a lexical + unsafe block. Span: whole operation form, with the head span as the operation + name. +- `UnsupportedUnsafeOperation`: a raw unsafe operation head appears inside a + lexical unsafe block. Span: whole operation form, with the head span as the + operation name. +- Existing diagnostics for local declarations, assignments, loops, calls, + comparisons, and type mismatches apply inside unsafe blocks. + +Out of scope for v0: + +- pointer allocation +- pointer load/store +- pointer arithmetic +- unchecked indexing behavior +- raw reinterpretation +- FFI calls +- pointer locals +- stable ABI/layout promises +- unsafe abstractions + +--- + +## 16. Tests + +Status: supported in the strict top-level form below. + +Current supported top-level test form: + +```slo +(test "name" + body-form... + final-expression) +``` + +Surface rules: + +- `test` is a top-level declaration, sibling to `fn`; it is not an expression + and is not valid inside a function body or another expression. +- The form has a string-literal name followed by one or more body expressions. +- The test name is metadata for the test runner, not a runtime `string` value. +- v0 test names must be non-empty printable ASCII without embedded quotes, + backslashes, or newlines until the general string escaping contract is + specified. +- Test names must be unique within a module after decoding. +- A single final expression is supported as the degenerate body. +- Non-final body forms may be local declarations or local assignments: + `(let name i32 value)`, `(var name i32 value)`, or `(set name value)`. +- When `while` is promoted, it is also a valid non-final test body form under + the first-pass loop contract in section 11. +- The final test expression has expected type `bool`; Slovo performs no implicit + conversion to `bool`. +- Tests may refer to functions in the same module using the same name + resolution rules as function bodies. Tests are not callable and do not + introduce names into the value namespace. +- Normal compilation must parse, lower, and check tests, but must not run them. + Test mode runs top-level tests in source order. + +Typed-core meaning: + +```text +TypedTest { + name: TestName, + body: [TBodyForm], + final_expr: TExpr, + span, + name_span, + body_spans +} +``` + +Top-level tests use the same sequential local-body rule as function bodies: + +```slo +(test "local update" + (let base i32 20) + (var total i32 (+ base 1)) + (set total (+ total 1)) + (= total 22)) +``` + +Local declarations and `set` forms may appear before the final test expression. +The final expression remains the test result and must check as `bool`. This +extension does not promote any broader block syntax. + +The checker resolves and checks each test's final expression in a test context +with expected type `bool`. A false result is a failed test result, not a +compile-time diagnostic. Compile-time diagnostics are reserved for malformed +test forms, invalid names, duplicate names, name-resolution failures, type +errors, and unsupported implementation gaps. + +Lowering and execution: + +- `TypedTest` entries are kept in a module test list separate from normal + functions. +- Tests do not affect `main`, exported functions, or the normal program ABI. +- In test-runner lowering, each `TypedTest` may lower to an internal + zero-argument `bool`/LLVM `i1` thunk plus a registry entry containing the test + name and source span. +- A test passes when its expression evaluates to true, fails when it evaluates + to false, and errors if evaluation traps or the runner cannot execute it. +- In normal LLVM emission, an implementation may omit test thunks after parsing, + lowering, and checking them. It must not silently ignore malformed or invalid + tests. + +Formatter behavior: + +```slo +(test "add works" + (= (add 2 3) 5)) +``` + +The canonical formatter keeps the test name on the opening line. In the current +supported subset, it prints each body form and the final expression on separate +two-space-indented lines. Each expression itself uses the normal canonical +expression formatter. + +Diagnostics: + +- `MalformedTestForm`: missing name, non-string name, missing final expression, + wrong operand count, or invalid body form. Span: whole test form or offending + operand. Expected: `(test "name" body... final-expression)`. +- `InvalidTestName`: empty name or a name outside the v0 name subset. Span: + string literal name. +- `DuplicateTestName`: duplicate decoded name in the same module. Span: + duplicate name, with a related span on the original test name. +- `TestExpressionNotBool`: checked expression type is not `bool`. Span: test + expression. Expected: `bool`. Found: checked expression type. +- Existing expression diagnostics such as `UnknownVariable`, `ArityMismatch`, + `TypeMismatch`, and `UnsupportedBackendFeature` apply inside test + expressions. + +Supported example: + +```slo +(test "add works" + (= (add 2 3) 5)) +``` + +Supported local-body example: + +```slo +(test "locals work" + (let base i32 2) + (var value i32 (add_local base)) + (set value (+ value 1)) + (= value 5)) +``` + +--- + +## 17. Diagnostics + +Diagnostics should have both human-readable and machine-readable forms. + +Status: Glagol currently has diagnostics with byte spans and human/machine +rendering. Slovo v0 requires both byte spans and line/column ranges for +user-facing and machine-readable diagnostics. Exact diagnostic snapshots remain +an implementation/test contract, but implementations must not report only +unstructured text or only byte offsets. + +Machine-readable diagnostic example: + +```slo +(error + (code TypeMismatch) + (expected i32) + (found string) + (message "expected i32, found string") + (span "main.slo" + (bytes 42 49) + (range 12 8 12 15)) + (hint "Use an integer value or convert explicitly.")) +``` + +Required diagnostic fields: + +- code +- primary source span as a zero-based, half-open byte range +- primary source range as one-based start line, start column, end line, and end + column +- expected, when applicable +- found, when applicable +- message +- hint, when useful and safe + +Byte spans are the canonical location for tools. Line/column ranges are +required for human-readable output and machine diagnostics so editors and +external tools can display errors without reimplementing Slovo's source mapper. +Line and column values are derived from the original source file, not formatter +output. Columns are one-based byte columns within a UTF-8 source line; a tab in +source counts as one input byte for the machine range, even though canonical +formatting uses spaces. + +Checked forms that reach a backend feature gap must return a structured +`UnsupportedBackendFeature` diagnostic instead of panicking. Unsupported +signature types, string literals without runtime lowering, and other +speculative forms must remain out of `examples/supported/` until they have +backend behavior or explicit diagnostics plus tests. + +Test-specific diagnostics introduced by the top-level `test` contract are +`MalformedTestForm`, `InvalidTestName`, `DuplicateTestName`, and +`TestExpressionNotBool`. They follow the same required fields above and must use +original source spans, not formatter output. `DuplicateTestName` includes a +related span pointing to the original test name. + +Loop-specific diagnostics introduced by the first-pass `while` contract are +`MalformedWhileForm`, `WhileConditionNotBool`, `EmptyWhileBody`, +`LocalDeclarationInWhileBodyUnsupported`, `NestedWhileUnsupported`, and +`WhileBodyFormNotUnit`. They follow the same required fields above and must use +original source spans, not formatter output. + +Struct-specific diagnostics introduced by the first-pass `struct` contract are +listed in section 12. They follow the same required fields above, must use +original source spans, and must keep related spans for duplicate names or +fields when applicable. + +Array-specific diagnostics introduced by the first-pass array/indexing contract +are listed in section 13. They follow the same required fields above and must +use original source spans, including byte ranges for malformed array types, +constructors, literal indices, and unsupported array value-flow forms. + +Option/result-specific diagnostics introduced by the first-pass constructor +contract are listed in section 14. They follow the same required fields above +and must use original source spans, including byte ranges for malformed +option/result types, constructors, and unsupported option/result value-flow +forms. + +--- + +## 18. LLVM Lowering Sketch + +Slovo v0 should lower through an internal typed core before LLVM IR. + +Example Slovo: + +```slo +(fn add ((a i32) (b i32)) -> i32 + (+ a b)) +``` + +Approximate LLVM IR shape: + +```llvm +define i32 @add(i32 %a, i32 %b) { +entry: + %0 = add i32 %a, %b + ret i32 %0 +} +``` + +--- + +## 19. v0 Implementation Milestones + +1. Parse forms +2. Print parsed tree +3. Canonical formatter +4. Basic name resolution +5. Primitive type checker +6. Function checker +7. `if` checker +8. `let` / `var` / `set` +9. Struct definitions +10. Top-level `test` checker and test runner +11. Structured diagnostics +12. Lower simple functions to LLVM IR +13. Compile and run `examples/supported/add.slo` +14. Preserve the `glagol` binary CLI contract for supported v0 modes, stderr + diagnostics, and exit codes +15. Keep native executable output as an explicit LLVM-plus-runtime linking step + until a later backend contract diff --git a/docs/language/SPEC-v1.md b/docs/language/SPEC-v1.md new file mode 100644 index 0000000..ee4c171 --- /dev/null +++ b/docs/language/SPEC-v1.md @@ -0,0 +1,5545 @@ +# Slovo v1 Specification + +Status: living beta contract for `1.0.0-beta`, integrating promoted language +slices through `exp-125` and the historical publication baseline through +`exp-123`. `1.0.0-beta` is the first real general-purpose beta release. +`exp-125` completed the unsigned numeric and stdlib breadth precursor scope, +`exp-124` is the last tagged experimental alpha language contract before beta, +and `exp-123` is the last tagged experimental documentation/tooling contract +before beta. `1.0.0-beta` includes direct `u32` and `u64` value flow, decimal +suffixed literals `42u32` / `42u64`, same-type unsigned +arithmetic/comparison, unsigned print/format/parse-result runtime lanes, and +matching staged stdlib helper parity alongside the previously promoted +project/package, collection, composite-data, docs, and diagnostics surface. +exp-121 broadens +unary enum payload variants to current known non-recursive struct types while +keeping the same-payload-type-per-enum rule when payload variants are present +and reusing existing immutable enum flow and `match` payload binding behavior. +The earlier exp-120 broadens direct struct field declarations to the current +promoted fixed immutable array family over `i32`, `i64`, `f64`, `bool`, and +`string`. The earlier exp-118 broadens fixed immutable arrays with one +connected `string` element lane alongside the earlier exp-117 direct scalar +array family `i32`, `i64`, `f64`, and `bool`. The earlier exp-119 keeps the +exp-118 language surface unchanged while refreshing the earlier benchmark/ +publication baseline around five kernels: `math-loop`, `branch-loop`, +`parse-loop`, `array-index-loop`, and `string-eq-loop`. The earlier exp-114 +promotes same-type mutable whole-value +local reassignment for already-supported non-array composite value families. +The earlier exp-113 promotes direct mutable `bool`, `i64`, and `f64` locals, +and the earlier exp-112 promotes immutable `bool` locals. The earlier exp-111 +broadens the concrete `std.io` facade with one connected stdin helper package +over the already promoted `std.io.read_stdin_result`, +`std.string.parse_*_result`, and the exp-109 bridge surface. The earlier +`v2.0.0-beta.1` tag name is historical and is not a beta maturity claim. + +Base release: Slovo v0 + +Date started: 2026-05-16 + +## 1. Purpose + +Slovo v1 grows the v0 language kernel without weakening the project rule that +made v0 reliable: + +> A feature is supported only when Slovo docs and Glagol behavior agree across +> surface syntax, typed-core meaning, lowering behavior, formatter behavior, +> diagnostics, examples, and tests. + +This file is a living post-v0 contract. A section is compiler-supported only +when it is marked promoted, has fixtures in `examples/supported/` and +`examples/formatter/`, is implemented in Glagol, and is covered by tests. + +Current v1 release surface and explicit experimental targets: + +- struct value flow through immutable locals, parameters, returns, calls + returning structs, and field access through stored struct values +- option/result value flow through immutable locals, parameters, calls + returning option/result values, and tag observation with `is_some`, `is_none`, + `is_ok`, and `is_err`, plus explicit trap-based `i32` payload extraction + with `unwrap_some`, `unwrap_ok`, and `unwrap_err` +- array value flow for fixed-size arrays over direct scalar `i32`, `i64`, + `f64`, `bool`, fixed-size `string`, direct known enum, and current known + non-recursive struct element families through immutable locals, parameters, + returns, calls returning arrays, and runtime-checked dynamic indexing with + `i32` index expressions +- runtime string support through direct immutable source string literals, + immutable and whole-value mutable string locals, string parameters, string + returns, calls returning strings, string equality, string byte length, and + legacy compiler/runtime compatibility aliases `(print_string value)` and + `(string_len value)` +- local binding surface through immutable `let` plus same-type mutable `var` / + `set` flows for direct `bool`, `i64`, `f64`, `string`, current concrete vec + families, current concrete option/result families, current known struct + values, and current enum values, while fixed arrays remain immutable +- legacy compiler/runtime compatibility alias `(print_bool value)` +- v1 tooling contracts for `slovo.diagnostic` machine diagnostics, + `slovo.artifact-manifest` output manifests, and LLVM span/source-map + direction +- v1.3 project mode for flat local projects with `slovo.toml`, explicit local + imports, explicit export lists, deterministic module ordering, multi-file + diagnostics, and project artifact-manifest fields, once the matching Glagol + v1.3 gates pass +- v1.4 source-level `match` for existing `(option i32)`, `(option i64)`, + `(option f64)`, `(option bool)`, `(option string)`, and `(result i32 i32)` + values, with exhaustive arms, + immutable arm payload bindings, arm-local expression bodies, common arm + result types, formatter layout, and named diagnostics, once the matching + Glagol v1.4 gates pass +- v1.5 standard-runtime alpha names `std.io.print_i32`, + `std.io.print_string`, `std.io.print_bool`, and `std.string.len` as + compiler-known source-level names over the existing runtime behavior, with + legacy `print_i32`, `print_string`, `print_bool`, and `string_len` names + remaining compatibility aliases, once the matching Glagol v1.5 gates pass +- v1.6 memory and unsafe design slice: lexical `unsafe` remains the only unsafe + boundary; `alloc`, `dealloc`, `load`, `store`, `ptr_add`, + `unchecked_index`, `reinterpret`, and `ffi_call` are compiler-known reserved + unsafe heads that require `UnsafeRequired` in safe code and + `UnsupportedUnsafeOperation` inside lexical `unsafe` until a future release + defines execution semantics +- v1.7 developer experience hardening: tooling-only contracts for + `glagol new`, `glagol fmt --check`, `glagol fmt --write`, `glagol doc`, and + a local release-gate script, without adding source language syntax, + semantics, LSP, stable debug metadata, or stable source maps +- `v2.0.0-beta.1` experimental integration/readiness release: the accumulated + v1.1-v1.7 surface is supported for small real flat local projects through + `glagol new`, `check`, `fmt --check/--write`, `test`, `build`, `doc`, + artifact manifests, JSON diagnostics, and the release-gate script +- `exp-1` owned runtime strings: compiler-known `std.string.concat` accepts two + `string` values and returns an immutable runtime-owned `string`; existing + string equality, length, printing, locals, parameters, returns, and calls work + over both literal-backed and runtime-owned strings + +- `exp-2` collections alpha plus `exp-94` vec-i64 baseline, `exp-99` + vec-string baseline, and `exp-103` vec-f64 baseline: concrete growable + vector types `(vec i32)`, `(vec i64)`, `(vec f64)`, and `(vec string)`, + compiler-known + `std.vec.i32.empty`, `std.vec.i32.append`, `std.vec.i32.len`, + `std.vec.i32.index`, compiler-known `std.vec.i64.empty`, + `std.vec.i64.append`, `std.vec.i64.len`, and `std.vec.i64.index`, + compiler-known `std.vec.f64.empty`, `std.vec.f64.append`, + `std.vec.f64.len`, and `std.vec.f64.index`, + compiler-known `std.vec.string.empty`, `std.vec.string.append`, + `std.vec.string.len`, and `std.vec.string.index`, vector equality with `=` + on same-family operands, and immutable vector flow through locals, + parameters, returns, calls, and top-level tests +- `exp-3` standard IO and host environment: compiler-known + `std.io.eprint`, `std.process.argc`, `std.process.arg`, `std.env.get`, + `std.fs.read_text`, and `std.fs.write_text` with stage-specific runtime + traps and integer write status +- `exp-4` user enum alpha: payloadless user-defined enum declarations, + zero-argument qualified variant constructors, immutable local, parameter, + return, and call flow, same-enum equality, and exhaustive payloadless enum + `match` +- `exp-16` unary i32 enum payloads alpha: user-defined enum variants may be + payloadless or carry one `i32` payload; qualified constructors pass zero or + one argument accordingly; enum `match` arms bind exactly one immutable + arm-local payload name for payload variants; same-enum equality compares + tag plus payload for payload variants and tag for payloadless variants +- `exp-17` project enum imports alpha: project/workspace module export lists + may include top-level enum names, import lists may import exported enum + names, and imported enum types preserve exactly the exp-4 payloadless plus + exp-16 unary `i32` payload surface +- `exp-18` enum struct fields alpha: struct field declarations may use current + user-defined enum type names directly, struct construction may initialize + those fields with payloadless or unary `i32` enum values, and field access + may flow into same-enum equality or exhaustive enum `match` +- `exp-19` primitive struct fields alpha: struct field declarations may use + direct `bool` and immutable `string` field types alongside direct `i32` and + exp-18 enum fields; field access may flow into existing bool predicate + positions, string equality, and `std.string.len` +- `exp-112` immutable bool locals alpha: direct immutable `bool` locals + declared with `let`, initialized from already promoted bool expressions, and + reusable in existing predicate, return, call, test, and `main` positions +- `exp-113` mutable scalar locals alpha: direct mutable `bool`, `i64`, and + `f64` locals declared with `var`, plus same-type `set` reassignment from + already promoted scalar expressions +- `exp-114` mutable composite locals alpha: same-type mutable whole-value + reassignment with `var` / `set` for `string`, current concrete vec + families, current concrete option/result families, current known struct + values, and current enum values +- `exp-20` f64 numeric primitive alpha: direct `f64` parameters, returns, + immutable locals, calls, decimal literals, same-type `f64` + arithmetic/comparison, and `std.io.print_f64` +- `exp-21` i64 numeric primitive alpha: direct `i64` parameters, returns, + immutable locals, calls, explicit signed decimal `i64` literal atoms, + same-type `i64` arithmetic/comparison, and `std.io.print_i64` +- `exp-22` numeric widening conversions alpha: explicit + `std.num.i32_to_i64 : (i32) -> i64`, + `std.num.i32_to_f64 : (i32) -> f64`, and + `std.num.i64_to_f64 : (i64) -> f64` calls over the existing numeric + primitives +- `exp-23` checked i64-to-i32 conversion alpha: exactly one checked narrowing + standard-runtime call, + `std.num.i64_to_i32_result : (i64) -> (result i32 i32)`, returning `ok` + for signed `i32` range values and `err 1` for out-of-range values +- `exp-24` integer to string alpha: exactly two integer formatting + standard-runtime calls, + `std.num.i32_to_string : (i32) -> string` and + `std.num.i64_to_string : (i64) -> string`, returning decimal signed ASCII + strings with `-` only for negative values and no leading `+` +- `exp-25` string parse i64 result alpha: exactly one string parse + standard-runtime call, + `std.string.parse_i64_result : (string) -> (result i64 i32)`, returning + `ok value` for in-range ASCII signed decimal `i64` text and `err 1` for + parse failure or out-of-range input +- `exp-125` current Slovo-side unsigned target: direct `u32`/`u64` + parameters, returns, immutable locals, calls, decimal suffixed literals + `42u32` / `42u64`, same-type unsigned arithmetic/comparison, + `std.io.print_u32`, `std.io.print_u64`, `std.num.u32_to_string`, + `std.num.u64_to_string`, `std.string.parse_u32_result`, + `std.string.parse_u64_result`, and matching staged source-helper parity + across `std.result`, `std.option`, `std.string`, `std.num`, `std.io`, + `std.env`, `std.fs`, `std.process`, and `std.cli` +- `exp-26` f64 to string alpha: exactly one finite `f64` formatting + standard-runtime call, + `std.num.f64_to_string : (f64) -> string`, returning finite decimal ASCII + text for promoted finite `f64` inputs +- `exp-27` checked f64 to i32 result alpha: exactly one checked `f64` to + `i32` result standard-runtime call, + `std.num.f64_to_i32_result : (f64) -> (result i32 i32)`, returning `ok` + value only for finite, exactly integral inputs inside the signed `i32` range + and `err 1` for non-finite, fractional, or out-of-range input +- `exp-28` string parse f64 result alpha: exactly one string parse + standard-runtime call, + `std.string.parse_f64_result : (string) -> (result f64 i32)`, returning + `ok value` for complete finite ASCII decimal `f64` text and `err 1` for + ordinary parse failure, non-finite text, trailing or leading unsupported + characters, or out-of-domain input +- `exp-29` numeric struct fields alpha: direct immutable struct field + declarations whose field types are exactly `i64` or `f64`, alongside + already supported direct `i32`, `bool`, immutable `string`, and current enum + fields; accessed fields may flow into existing same-type numeric + arithmetic/comparison and existing numeric print/format helpers +- `exp-120` fixed array struct fields alpha: direct immutable struct field + declarations whose field types are exactly the current promoted fixed-array + families `(array i32 N)`, `(array i64 N)`, `(array f64 N)`, `(array bool N)`, + and `(array string N)` with positive literal lengths; field access returns + the declared array type and may flow into existing checked `index` +- `exp-121` non-recursive struct enum payloads alpha: unary enum payload + variants may use current known non-recursive struct types, payload variants + in one enum still share the same payload type when payload variants are + present, and bound payload values reuse existing field access behavior +- `exp-30` standard library source layout alpha: establish `std/` as the + source home for staged standard library modules and examples, including + narrow source-authored `std/math.slo` helpers, without automatic + standard-library import/search, replacing compiler-known `std.*` calls, + stable APIs, manifest schema changes, or beta maturity +- `exp-31` checked f64 to i64 result alpha: exactly one checked `f64` to + `i64` result standard-runtime call, + `std.num.f64_to_i64_result : (f64) -> (result i64 i32)`, returning `ok` + value only for finite, exactly integral inputs inside the signed `i64` range + and `err 1` for non-finite, fractional, or out-of-range input +- `exp-32` standard math source helpers alpha: staged `std/math.slo` includes + source-authored `abs_i64`, `min_i64`, `max_i64`, `clamp_i64`, + `square_i64`, `abs_f64`, `min_f64`, `max_f64`, and `clamp_f64` alongside + the existing `i32` helpers and `square_f64`, without automatic + standard-library import/search or new compiler-known runtime names +- `exp-33` standard result source helpers alpha: staged `std/result.slo` + includes concrete `is_err_*` and `unwrap_err_*` helpers for the already + promoted result families, plus `unwrap_or_i32`, `unwrap_or_i64`, + `unwrap_or_string`, and `unwrap_or_f64`, without automatic standard-library + import/search or new compiler-known runtime names +- `exp-34` string parse bool result alpha: exactly one string parse + standard-runtime call, + `std.string.parse_bool_result : (string) -> (result bool i32)`, returning + `ok true` for exactly `true`, `ok false` for exactly `false`, and `err 1` + for empty input, uppercase/mixed-case text, leading/trailing whitespace, + numeric text, or any other text +- `exp-35` standard result bool source helpers alpha: staged + `std/result.slo` includes `is_ok_bool`, `is_err_bool`, `unwrap_ok_bool`, + `unwrap_err_bool`, and `unwrap_or_bool` for `(result bool i32)`, without + automatic standard-library import/search, compiler-known runtime names, + generic result helpers, or stable ABI/layout claims +- `exp-36` standard option source helpers alpha: staged `std/option.slo` + includes `is_some_i32`, `is_none_i32`, `unwrap_some_i32`, and + `unwrap_or_i32` for `(option i32)`, without automatic standard-library + import/search, compiler-known runtime names, generic option helpers, option + payloads beyond `i32`, or stable ABI/layout claims +- `exp-37` standard time source facade alpha: staged `std/time.slo` includes + `monotonic_ms` and `sleep_ms_zero` wrappers over the already released exp-8 + `std.time.*` calls, without automatic standard-library import/search, + compiler-loaded standard-library source, compiler-known runtime names, + wall-clock/calendar/timezone APIs, high-resolution timers, async timers, + cancellation, scheduling guarantees, or stable ABI/layout claims +- `exp-38` standard host source facades alpha: staged `std/random.slo`, + `std/env.slo`, and `std/fs.slo` add narrow source wrappers over already + released random, environment, and text filesystem calls, without automatic + standard-library import/search, compiler-loaded standard-library source, + compiler-known runtime names, random seed/range/bytes/float/UUID/crypto + APIs, environment mutation/enumeration, binary/directory/streaming/async + filesystem APIs, rich host error ADTs, or stable ABI/layout claims +- `exp-39` standard math extensions and benchmark scaffold alpha: staged + `std/math.slo` adds source-authored `neg`, `cube`, zero/positive/negative + predicates, and inclusive `in_range` helpers for `i32`, `i64`, and finite + `f64`, while the matching Glagol benchmark scaffold remains local timing + evidence only +- `exp-44` standard library source search alpha: project-mode source may + explicitly import `(import std.math (...))`, resolving to repo-root + `std/math.slo` with its own export list; still no automatic std imports, + workspace/package std imports, registry behavior, `std.slo` aggregator, + broad standard-library APIs, stable ABI/layout, or beta maturity +- `exp-45` standard result and option source search alpha: project-mode + source may explicitly import `(import std.result (...))` and + `(import std.option (...))`, resolving to repo-root `std/result.slo` and + `std/option.slo` with their own export lists; still no automatic std + imports, workspace/package std imports, generic result/option helpers, + registry behavior, broad standard-library APIs, stable ABI/layout, or beta + maturity +- `exp-46` workspace standard source search alpha: workspace packages may + explicitly import standard source modules such as + `(import std.option (...))`, using the same repo-root standard source + lookup; still no automatic std imports, workspace dependency syntax for + std, installed stdlib paths, registry behavior, stable ABI/layout, or beta + maturity +- `exp-47` standard host facade source search alpha: project-mode source may + explicitly import `std.time`, `std.random`, `std.env`, and `std.fs`, + resolving to staged repo-root source facades with their own export lists; + still no automatic std imports, installed stdlib paths, broad host APIs, + stable ABI/layout, or beta maturity +- `exp-48` standard core facade source search alpha: project-mode source may + explicitly import `std.string` and `std.num`, resolving to staged repo-root + source facades with their own export lists; still no automatic std imports, + installed stdlib paths, generic parse/format APIs, broad numeric casts, + stable ABI/layout, or beta maturity +- `exp-49` standard IO facade source search alpha: project-mode source may + explicitly import `std.io`, resolving to the staged repo-root source facade + with its own export list; still no automatic std imports, installed stdlib + paths, broad IO APIs, formatted output APIs, stable ABI/layout, or beta + maturity +- `exp-50` installed standard library discovery alpha: explicit + standard-source imports may resolve staged `std/*.slo` files from an + installed `share/slovo/std` toolchain layout; still no automatic std + imports, package registry behavior, lockfiles, package std dependencies, + stable install layout guarantees, broad standard-library APIs, stable + ABI/layout, or beta maturity +- `exp-51` standard library path list alpha: `SLOVO_STD_PATH` may name an + ordered OS path list of standard-library roots; still no automatic std + imports, package registry behavior, lockfiles, semantic version solving, + stable package manager behavior, broad standard-library APIs, stable + ABI/layout, or beta maturity +- `exp-52` standard process facade source search alpha: project-mode source + may explicitly import `std.process`, resolving to the staged repo-root + source facade with its own export list; still no automatic std imports, + package registry behavior, process spawning, exit/status control, + current-directory APIs, signal handling, stable ABI/layout, or beta maturity +- `exp-53` standard CLI source facade alpha: project-mode source may + explicitly import `std.cli`, resolving to the staged repo-root source + facade with its own export list; the facade composes `std.process` and + `std.string`; still no automatic std imports, package registry behavior, + shell parsing, option/flag parsing, subcommands, environment-backed + configuration, stable CLI framework APIs, stable ABI/layout, or beta + maturity +- `exp-54` standard CLI typed arguments alpha: staged `std.cli` adds + `arg_i64_result`, `arg_i64_or_zero`, `arg_f64_result`, + `arg_f64_or_zero`, `arg_bool_result`, and `arg_bool_or_false` +- `exp-55` result f64 bool source flow alpha: source may construct and match + the concrete `(result f64 i32)` and `(result bool i32)` families directly; + still no generic results, broad payload families, generic result + combinators, stable ABI/layout, or beta maturity +- `exp-56` integer remainder alpha: `%` is supported for same-width `i32` and + `i64` operands, and `std.math` adds concrete remainder/even/odd helpers; + still no floating-point remainder, modulo semantics beyond signed integer + remainder, bit operations, generic math, stable ABI/layout, or beta maturity +- `exp-57` integer bitwise alpha: `bit_and`, `bit_or`, and `bit_xor` are + supported for same-width `i32` and `i64` operands, and `std.math` adds + concrete bitwise wrappers; still no shifts, bit-not, unsigned arithmetic, + bit-width-specific integer families, generic math, stable ABI/layout, or + beta maturity +- `exp-58` boolean logic alpha: `and` and `or` are short-circuiting boolean + forms, and `not` negates a boolean value; still no truthiness, variadic + boolean operators, pattern guards, macro expansion, stable ABI/layout, or + beta maturity +- `exp-119` benchmark/publication refresh alpha: no language-surface change + over exp-118; current benchmark/publication evidence widens from three + kernels to five by adding `array-index-loop` and `string-eq-loop`, while + keeping local-machine-only framing and no threshold claim +- `exp-122` composite data benchmark/publication refresh alpha: no + language-surface change over exp-121; current benchmark/publication + evidence target widens from five kernels to seven by adding + `array-struct-field-loop` and `enum-struct-payload-loop`, while keeping + local-machine-only framing and no threshold claim +- `exp-120` fixed array struct fields alpha: current promoted fixed immutable + arrays may also appear as direct struct fields with immutable struct flow and + checked indexing after field access, without adding array mutation, field + mutation, nested arrays, or beta maturity +- `exp-121` non-recursive struct enum payloads alpha: current known + non-recursive struct types may also appear as unary enum payload variants + with immutable enum flow and `match` payload binding exposing existing field + access behavior, without adding direct array/vec/option/result payloads, + equality requirements for struct-payload enums, mutation, or beta maturity +- `exp-59` benchmark/publication alpha: no language-surface change over + exp-58; an earlier benchmark/publication baseline was refreshed against the + paired Glagol hosted-build optimization +- `exp-5` local packages/workspaces: explicit workspace members, package + name/version metadata, local path dependencies, package-qualified imports, + deterministic package graph ordering, and artifact-manifest package graph + recording +- `exp-6` C FFI scalar imports alpha: top-level imported C function + declarations with `i32` parameters, `i32` or internal `unit` returns, + lexical `(unsafe ...)` at imported C call sites, explicit local C source + linking, and artifact-manifest foreign-import metadata +- `exp-7` test selection and test-run metadata alpha: `glagol test + --filter `, legacy + `glagol --run-tests --filter `, deterministic + display-name substring filtering, skipped accounting, zero-match success, + and additive test-report manifest metadata +- `exp-8` host time and sleep alpha: compiler-known + `std.time.monotonic_ms: () -> i32` and + `std.time.sleep_ms: (i32) -> unit`, non-negative host monotonic elapsed + milliseconds with implementation-owned epoch, non-negative millisecond + sleep with `0` valid, exact negative-duration trap text, deterministic + `sleep_ms 0` test-runner behavior, and conservative `monotonic_ms` + structural or non-negative host-value test-runner support +- `exp-9` reliability, performance, and ecosystem hardening: released + hardening-only experimental contract over the exp-8 baseline, with no source + syntax, type-system, standard-runtime, manifest-schema, ABI/layout, + package, runtime-capability, public-performance, or beta-maturity expansion +- `exp-10` result-based host errors alpha: concrete `(result string i32)`, + additive `std.process.arg_result`, `std.env.get_result`, + `std.fs.read_text_result`, and `std.fs.write_text_result`, ordinary host + failure as `err 1`, and exp-3 calls unchanged +- `exp-11` basic randomness alpha: exactly one compiler-known + standard-runtime operation, `std.random.i32: () -> i32`, returning a + non-negative implementation-owned pseudo-random or host-random `i32` for + basic CLI use only +- `exp-12` standard input result alpha: exactly one compiler-known + standard-runtime operation, `std.io.read_stdin_result: () -> (result string + i32)`, reading remaining stdin as text and returning `ok` text, with + ordinary EOF as `ok ""` and ordinary host/input failure as `err 1` +- `exp-13` string parse i32 result alpha: exactly one compiler-known + standard-runtime operation, + `std.string.parse_i32_result: (string) -> (result i32 i32)`, parsing an + entire ASCII decimal signed `i32` string with optional leading `-`, success + as `ok` value, and ordinary parse failure as `err 1` +- `exp-14` standard runtime conformance alignment: released experimental + alignment over the exp-13 surface, adding `STANDARD_RUNTIME.md`, byte-identical + exp-8 time/sleep fixtures, explicit fixture inventory and byte-alignment + gates, and no new source syntax, type forms, runtime APIs, `std.*` names, + manifest schema version, ABI/layout promise, runtime headers/libraries, or + beta maturity +- `exp-15` result helper standard names alpha: compiler-known source-level + `std.result.is_ok`, `std.result.is_err`, `std.result.unwrap_ok`, and + `std.result.unwrap_err` for only the already supported `(result i32 i32)`, + `(result i64 i32)`, `(result string i32)`, and exp-28 returned + `(result f64 i32)` families, plus exp-34 returned `(result bool i32)`, with + legacy unqualified helper names retained as compatibility syntax + +v1 scope-freeze decisions are also part of this contract. They clarify what +remains outside v1 rather than adding language features: + +- struct field mutation and whole-struct value mutation remain deferred beyond + v1 unless a later explicit promotion updates this spec, examples, + diagnostics, and Glagol tests +- option/result mapping, equality, printing, non-`i32` payloads and + extraction, nested option/result values, arrays or structs containing + option/results, enum payloads beyond exp-16 unary `i32` variants, exp-116 + unary direct scalar/string variants, and exp-121 unary current known + non-recursive struct variants, enum import behavior beyond exp-17 explicit + local export/import lists, enum values in containers or nested structs + beyond exp-18 direct fields, generic payloads, string containers beyond + exp-19 direct struct fields and exp-118 fixed immutable arrays, and + user-catchable exceptions remain deferred + unless explicitly promoted later; + exp-10 promotes only the concrete `(result string i32)` family and does not + broaden general option/result support +- `bool` printing is promoted in v1.2 through legacy compiler/runtime + compatibility alias `print_bool` +- `unit` remains an internal builtin result type for supported unit-producing + body forms; `print_unit`, user-declared `unit`, and stored `unit` values + remain unsupported in v1.2 +- runtime printing remains limited to legacy compatibility aliases + `print_i32`, `print_string`, and `print_bool` plus v1.5 + `std.io.print_i32`, `std.io.print_string`, and `std.io.print_bool`, plus + exp-20 `std.io.print_f64` and exp-21 `std.io.print_i64`; + broader standard-runtime printing remains deferred +- lexical `unsafe` remains the only accepted unsafe boundary; pointer types, + allocation, deallocation, load, store, pointer arithmetic, reinterpretation, + unchecked indexing, raw memory execution, and FFI remain deferred +- stable ABI and stable layout promises remain deferred; v1.6 keeps memory + model direction staged toward safe values by default, future affine + ownership, and explicit unsafe regions +- exp-1 does not change the v1 stable ABI/layout deferral; runtime-owned string + representation, allocator behavior, cleanup timing, and C symbols remain + implementation-owned +- exp-3 does not change the v1 stable ABI/layout deferral; host helper + symbols, path handling, process/environment shims, and runtime string + storage remain implementation-owned +- exp-4 does not change the v1 stable ABI/layout deferral; enum runtime + representation, discriminant values, helper symbols, reflection, and + conversions remain implementation-owned +- exp-5 does not add remote registries, version solving, package publishing, + package generics, package re-exports, or cross-package ABI stability +- exp-6 does not stabilize pointer types, allocation/deallocation, raw unsafe + head execution, ownership/lifetime rules, C exports, callbacks, headers, + libraries, broad linker configuration, ABI, or layout +- exp-7 does not add source language syntax, LSP, debug metadata, source maps, + SARIF, watch/daemon protocols, documentation comments, lint categories, + benchmarks, or richer test-runner features beyond the filtered alpha +- exp-8 does not add source language syntax, threads, tasks, channels, async, + cancellation, actors, shared memory, data-race freedom, scheduling + guarantees, timers, wall-clock/calendar/timezone APIs, high-resolution + timers, signal handling, stable ABI/layout, or stable runtime helper symbols +- exp-10 does not change exp-3 host calls or add general host error ADTs, + platform-specific codes, error messages, `result string Error`, broader host + APIs, manifest schema versions, stable ABI/layout, or beta maturity +- exp-11 does not add seed APIs, cryptographic or security promises, bytes + APIs, ranges or bounds arguments, floats, random strings, UUIDs, stable + helper ABI/layout, randomness-specific manifest fields, or beta maturity +- exp-12 does not add trap-based `std.io.read_stdin`, line iteration, prompt + APIs, terminal mode, binary stdin, streaming, async, encoding or Unicode + promises beyond existing string bytes, stable helper ABI/layout, manifest + schema changes, or beta maturity +- exp-13 does not add trap-based `std.string.parse_i32`, parsing floats, + bools, strings, or bytes, whitespace/locale/base-prefix/underscore/plus-sign + parsing, generic parse APIs, parse error messages or richer codes, Unicode + digit parsing, string indexing or slicing, tokenizer/scanner APIs, stdin + line APIs, stable helper ABI/layout, manifest schema changes, or beta + maturity +- exp-15 does not add `std.result.map`, `std.result.unwrap_or`, + `std.result.and_then`, option helper standard names, new payload families, + generic result support, user-defined error payloads, enum payloads, runtime + ABI/layout claims, manifest schema changes, or beta maturity +- exp-33 does not add generic `std.result.map`, generic + `std.result.unwrap_or`, `std.result.and_then`, option helper standard names, + new result payload families, automatic `std` imports, compiler-loaded + `std/` source, runtime ABI/layout claims, manifest schema changes, or beta + maturity +- exp-34 does not add generic parse APIs, trap-based parse, bool parsing + beyond exactly complete ASCII lowercase `true` and `false`, string/bytes + parse, whitespace trimming, case-insensitive parsing, locale/Unicode + boolean parsing, numeric boolean parsing, rich parse errors, + source-authored `std/result.slo` bool wrappers, automatic `std` imports, + compiler-loaded `std` source, runtime ABI/layout claims, manifest schema + changes, or beta maturity +- exp-35 does not add source-authored generic result helpers, + `std.result.map`, generic `std.result.unwrap_or`, `std.result.and_then`, + option helper names, broad result payload families, automatic `std` + imports, compiler-loaded `std` source, source constructors for + `(result bool i32)`, general source `match` over `(result bool i32)` beyond + compiler-supported fixture flow, runtime ABI/layout claims, manifest schema + changes, or beta maturity +- exp-36 does not add automatic `std` imports, repo-root `std/` search, + compiler-loaded `std` source, new compiler-known `std.*` runtime names, + generic option helpers, option payload families beyond `(option i32)`, + option mapping/chaining helpers, stable standard-library APIs, runtime + ABI/layout claims, manifest schema changes, or beta maturity +- exp-37 does not add automatic `std` imports, repo-root `std/` search, + compiler-loaded `std` source, new compiler-known `std.*` runtime names, + wall-clock/calendar/timezone APIs, high-resolution timers, async timers, + cancellation, scheduling guarantees, stable standard-library APIs, runtime + ABI/layout claims, manifest schema changes, or beta maturity +- exp-38 does not add automatic `std` imports, repo-root `std` search, + compiler-loaded `std` source, new compiler-known `std.*` runtime names, + random seed/range/bytes/float/UUID/crypto APIs, environment mutation/ + enumeration, binary/directory/streaming/async filesystem APIs, rich host + error ADTs, runtime ABI/layout claims, manifest schema changes, or beta + maturity +- exp-39 does not add automatic `std` imports, repo-root `std` search, + compiler-loaded `std` source, new compiler-known `std.*` runtime names, + trigonometry, `sqrt`, `pow`, logarithms, modulo, bit operations, generic + math, overloads, traits, mixed numeric arithmetic, optimizer guarantees, + benchmark thresholds, runtime ABI/layout claims, manifest schema changes, + or beta maturity +- exp-20 does not add `f32`, `i64/u64/u32/u16/u8/i16/i8`, `char`, `bytes`, + `decimal`, numeric casts, implicit promotion, mixed `i32`/`f64` arithmetic, + generic numeric operators, f64 arrays/vectors/options/results/enum payloads/ + struct fields, mutable `f64` locals, `parse_f64`, random floats, broader + math library calls, stable NaN/infinity/rounding semantics, stable print + formatting beyond newline-terminated output, stable ABI/layout, manifest + schema changes, or beta maturity +- exp-21 does not add `f32`, unsigned integers, narrower integer widths beyond + the existing `i32`, `char`, `bytes`, `decimal`, numeric casts, implicit + promotion, mixed `i32`/`i64`/`f64` arithmetic, generic numeric operators, + i64 arrays/vectors/options/results/enum payloads/struct fields, mutable + `i64` locals, random `i64`, broader math library calls, stable + divide-by-zero or overflow diagnostics, stable ABI/layout, manifest schema + changes, or beta maturity +- exp-22 does not add implicit numeric promotion, mixed numeric arithmetic or + comparison, narrowing or checked numeric conversions, cast syntax, `f32`, + unsigned or narrower integer families, numeric parse/format APIs, stable + floating rounding/formatting guarantees, stable ABI/layout, manifest schema + changes, or beta maturity +- exp-23 does not add implicit numeric promotion, cast syntax, `std.num.cast`, + checked cast generics, mixed numeric operators, any narrowing conversion + other than `std.num.i64_to_i32_result`, `f64` conversions, unsigned or + narrower integer families, numeric parse/format APIs, stable ABI/layout, + manifest schema changes, or beta maturity +- exp-24 does not add `f64` formatting, parse APIs beyond the released + i32/i64 result calls, locale/base/radix/grouping/padding controls, generic + format/display traits, implicit conversions, stable standard-library + implementation source, stable ABI/layout/ownership, manifest schema changes, + or beta maturity +- exp-25 does not add `f64` parse, generic parse, leading `+`, whitespace + trimming, underscores, base/radix prefixes, locale-aware parsing, Unicode + digit parsing, rich parse errors, stable helper ABI/layout/ownership, + manifest schema changes, or beta maturity +- exp-26 does not add `f32`, `f64` parse, generic format/display/ + interpolation APIs, locale/base/radix/grouping/padding/precision controls, + stable NaN/infinity text, implicit conversion, stable helper ABI/layout/ + ownership, manifest schema changes, or beta maturity +- exp-27 does not add unchecked `f64` to `i32`, casts or cast syntax, generic + `cast_checked`, `f32`, unsigned or narrower integer families, additional + numeric conversion families, `f64` parse, mixed numeric arithmetic, numeric + containers, stable helper ABI/layout/ownership, manifest schema changes, or + beta maturity +- exp-28 does not add generic parse, bool/string/bytes parse, locale parsing, + Unicode digit parsing, underscores, rich parse errors, stable helper + ABI/layout/ownership, result genericity, f64 containers, mixed numeric + arithmetic, manifest schema changes, or beta maturity + +## 2. Compatibility Baseline + +All Slovo v0 supported fixtures remain valid unless a future section explicitly +defines a migration. Migration-level changes must follow +`MIGRATION_POLICY.md`. + +The v0 baseline includes: + +- modules +- functions +- top-level tests +- `i32` parameters and returns +- direct `(option i32)` and `(result i32 i32)` constructor returns +- local `i32` `let` and `var` +- `set` +- `+`, `=`, and `<` +- user calls +- `print_i32` +- value-producing `if` +- first-pass `while` +- first-pass structs with immediate field access +- first-pass fixed `i32` arrays and literal indexing +- first-pass option/result constructors +- lexical `unsafe` blocks for otherwise supported safe forms + +## 3. v1 Release Gate + +Slovo v1 is releasable only when: + +- every supported v1 feature is specified in this file +- every supported v1 feature has a fixture under `examples/supported/` +- formatter fixtures exist for every supported v1 surface form +- Glagol keeps a golden machine-diagnostic fixture inventory for every + current explicitly rejected v1 boundary +- Glagol keeps textual lowering-inspector golden fixtures for every promoted + v1 feature family named in section 9.5 +- speculative examples remain clearly separated from supported fixtures +- Glagol implements every supported v1 form or rejects source-reachable + unsupported forms with structured diagnostics +- stable LLVM debug metadata, DWARF emission, and source-map files remain + deferred; textual lowering-inspector fixtures are sufficient for v1 +- future features add diagnostic and lowering-inspector coverage before + promotion +- Slovo and Glagol roadmaps agree on support status +- v0 compatibility fixtures still pass + +## 4. Proposed v1 Priority Order + +v1 should complete existing v0 value families before adding unrelated syntax. + +Recommended order: + +1. Struct value flow +2. Option/result value flow and observation +3. Array value flow and dynamic indexing +4. Runtime string contract +5. Tooling contract improvements + +The first accepted v1 slice is struct value flow. The second accepted v1 slice +is option/result value flow and tag observation. The option/result payload +access extension promotes explicit trap-based `i32` extraction forms. The third +accepted v1 slice is array value flow and dynamic indexing for fixed-size +direct scalar arrays. The fourth accepted v1 slice is direct runtime +string-literal printing through `print_string`. The fifth accepted v1 slice is the tooling +contract for versioned machine diagnostics, textual artifact manifests, and +LLVM/source-span direction. The v1.2 practical runtime values release extends +the string slice conservatively and promotes `print_bool` without changing the +v1.1 single-file CLI/toolchain assumptions. The v1.3 project-mode release adds +flat local projects and explicit local modules without adding packages, +dependencies, registries, version solving, workspaces, macros, public ABI, +cross-package visibility, incremental builds, watch/LSP, path escapes, or +generated code. The v1.4 core-language release promotes only source-level +`match` for the existing `(option i32)`, `(option i64)`, `(option f64)`, +`(option bool)`, `(option string)`, and `(result i32 i32)` families without +adding user-defined enums/ADTs, generic payloads, mutation, vectors, broad +numeric expansion, ownership, or layout/ABI promises. The v1.5 standard +library alpha promotes only compiler-known `std.*` source names for the +existing print and string-length runtime behavior, without adding imports, +packages, new data types, IO breadth, allocation, Unicode length semantics, or +ABI promises. The v1.6 memory and unsafe design slice reserves raw unsafe +operation heads for `UnsafeRequired` and `UnsupportedUnsafeOperation` gates +without promoting raw-memory execution. The v1.7 developer experience +hardening release adds only tooling contracts for scaffolding, formatter +check/write modes, deterministic Markdown documentation generation, and a local +release-gate script. `v2.0.0-beta.1` promotes the integrated v1.1-v1.7 +workflow as experimental-ready for small flat local projects without adding +new source language syntax or semantics. The later `1.0.0-beta` release is +the first real general-purpose beta milestone. The released compiler-supported +experimental steps are exp-1 owned runtime strings, exp-2 collections alpha, +exp-3 host IO/environment, exp-4 payloadless user enums, exp-5 local +packages/workspaces, exp-6 C FFI scalar imports alpha, exp-7 test +selection/test-run metadata alpha, exp-8 host time/sleep alpha, exp-9 +hardening, exp-10 result-based host errors, exp-11 basic randomness, and +exp-12 standard input result alpha, and exp-13 string parse i32 result alpha. +The current released conformance-alignment stage is exp-14 Standard Runtime +Conformance Alignment over the exp-13 surface; it seeds documentation, +fixture, and release-gate conformance without adding source syntax, type +forms, runtime APIs, `std.*` names, manifest schema versions, ABI/layout +promises, runtime headers/libraries, or beta maturity. exp-15 promotes only +preferred `std.result.*` helper names for already +supported concrete result families. These stages do not +promote +generic vectors, broader collections, broad host error ADTs, broad parsing, +result mapping/chaining or generic result helpers, +registries, version solving, package publishing, generics, user-visible +deallocation, broad FFI, LSP, debug metadata, source maps, or stable +ABI/layout promises. + +Unsafe raw memory, FFI, pointers, allocation, unchecked indexing, and stable +ABI/layout promises are no longer open v1 execution decisions. They remain +deferred unless an explicit later promotion changes this file. + +## 4.1 v1.3 Project Mode And Modules + +Status: frozen v1.3 Slovo-side contract. Implementation support requires the +matching Glagol v1.3 gates. + +Project mode is selected only when `glagol check`, `glagol test`, or +`glagol build` receives a project root containing `slovo.toml` or the manifest +file itself. Passing one `.slo` file keeps the v1.2 single-file behavior +unchanged. + +The manifest is named exactly `slovo.toml`: + +```toml +[project] +name = "example" +source_root = "src" +entry = "main" +``` + +`project.name` is required and must be an ASCII lowercase package-shaped +identifier using `a-z`, `0-9`, and `-`, starting with `a-z`. +`project.source_root` is optional and defaults to `src`; it must be a relative +path under the project root. `project.entry` is optional and defaults to +`main`; it must be a flat module identifier. Unknown manifest sections or keys +are diagnostics. Path escapes from the project root or source root remain +outside v1.3. + +v1.3 project modules are immediate `.slo` children of the source root. The +module name is flat, the file stem must match the module declaration, and the +project module set is every immediate `.slo` file in the source root. + +The module declaration may include an explicit export list: + +```slo +(module math (export add_one Point)) +``` + +The only import form is: + +```slo +(import math (add_one Point)) +``` + +Import forms appear after the module declaration and before local `struct`, +`fn`, and `test` declarations. Imported names are unqualified top-level names +inside the importing module. v1.3 has no import aliases, glob imports, +qualified access, or re-exports. + +Only exported top-level `fn` and `struct` declarations are importable in the +v1.3 baseline. The later exp-17 experimental contract extends the same +export/import mechanism to top-level enum names in project and workspace +modules. Top-level tests, local bindings, parameters, fields, module names as +values, imported names, builtins, legacy compiler/runtime aliases, +compiler-known standard-runtime names, generated declarations, and +macro-expanded declarations are not exportable or importable. + +Name resolution is local declarations, then explicit imported names, then +builtins, legacy aliases, and compiler-known standard-runtime names. v1.3 +rejects duplicate local names, duplicate import-list names, duplicate +export-list names, and local/import collisions as `DuplicateName`. Importing +the same unqualified name from two different modules is `AmbiguousName` at the +later import name span; v1.3 does not defer that case to a later use-site. + +Project operations are deterministic: source files are sorted +lexicographically by UTF-8 byte order; graph validation uses topological order +with lexicographic tie breaking; tests run by deterministic module order and +source order within each module; artifact manifests list source files, modules, +diagnostics, imports, and outputs deterministically. + +Required project diagnostics are: + +- `DuplicateName` +- `MissingImport` +- `ImportCycle` +- `AmbiguousName` +- `Visibility` + +Source-reachable multi-file diagnostics must include file identity, byte spans, +line/column spans, and related spans for cross-file context. Project artifact +manifests remain `slovo.artifact-manifest` version `1` and record project root, +manifest path, source root, project name, entry module, modules, import edges, +test counts when applicable, diagnostics counts, and build outputs. + +v1.3 explicitly defers packages, dependencies, remote registries, version +solving, lockfiles, workspaces, hierarchical modules, import aliases, glob +imports, qualified access, re-exports, macros, public ABI, cross-package +visibility, incremental builds, watch mode, LSP, SARIF, path escapes, +generated code, project formatting, build profiles, cross-compilation, object +output, library output, header output, stable debug metadata, DWARF, and +standalone source-map files. + +## 4.2 v1.4 Core Language Expansion + +Status: frozen v1.4 Slovo-side contract. Implementation support requires the +matching Glagol v1.4 gates. + +v1.4 promotes source-level `match` for already-supported option/result values: + +```slo +(match value + ((some payload) + payload) + ((none) + 0)) + +(match value + ((ok payload) + payload) + ((err code) + code)) +``` + +The only promoted match subject types are `(option i32)`, `(option i64)`, +`(option f64)`, `(option bool)`, `(option string)`, and `(result i32 i32)`. +An option match must cover exactly `some` and `none`; a result match must +cover exactly `ok` and `err`. Duplicate arms are diagnostics. Arm order is +preserved and does not change semantics. + +The only promoted patterns are `(some binding)`, `(none)`, `(ok binding)`, and +`(err binding)`. Payload bindings are immutable and scoped to the selected arm +body only. A payload binding may not collide with a name visible at the match +site or with a local introduced in the same arm body. Identical binding names +in different arms are allowed because arm scopes are disjoint. + +Each arm body contains one or more expressions. Non-final arm expressions are +evaluated in source order, and the final expression is the arm value. v1.4 does +not add a general `(block ...)` expression; match arms are the only new nested +body context. + +The match expression type is the common final-expression type of all arms. Arm +type mismatches are diagnostics. The subject is evaluated once, only the +selected arm body is evaluated, and matching does not use the trap-based +`unwrap_*` runtime path. + +Required match diagnostics are: + +- `MatchSubjectTypeMismatch` +- `UnsupportedMatchPayloadType` +- `NonExhaustiveMatch` +- `DuplicateMatchArm` +- `MalformedMatchPattern` +- `MatchArmTypeMismatch` +- `MatchBindingCollision` +- `UnsupportedMatchMutation` +- `UnsupportedMatchContainer` + +Existing tag observers and `unwrap_*` forms remain supported, but new examples +prefer `match` for payload-dependent option/result control flow. + +The normative v1.4 release contract is +`.llm/V1_4_CORE_LANGUAGE_EXPANSION.md`. + +## 4.3 v1.5 Standard Library Alpha + +Status: frozen v1.5 Slovo-side contract. Implementation support requires the +matching Glagol v1.5 gates. + +v1.5 promotes a conservative source-level standard-runtime namespace over +existing v1.2 runtime behavior: + +```slo +(std.io.print_i32 value) +(std.io.print_string value) +(std.io.print_bool value) +(std.string.len value) +``` + +These names are compiler-known v1.5 standard-runtime names. They do not require +imports, do not name user modules, do not create package dependencies, and are +not stable C ABI symbols. + +The exact promoted signatures are: + +- `std.io.print_i32`: exactly one `i32`, returns builtin `unit`, same stdout + behavior as legacy `print_i32` +- `std.io.print_string`: exactly one `string`, returns builtin `unit`, same + stdout behavior and string-literal constraints as legacy `print_string` +- `std.io.print_bool`: exactly one `bool`, returns builtin `unit`, same stdout + behavior as legacy `print_bool` +- `std.string.len`: exactly one `string`, returns `i32`, same decoded-byte + count semantics as legacy `string_len` + +Existing legacy intrinsic names `print_i32`, `print_string`, `print_bool`, and +`string_len` remain compatibility aliases. New examples prefer `std.*` names. + +The promoted `std.*` names are reserved from user function/export shadowing. +Unknown or unpromoted `std.*` calls must produce a structured diagnostic; the +suggested code is `UnsupportedStandardLibraryCall`. Arity and type mismatches +for promoted `std.*` calls should use the existing arity/type diagnostics where +applicable. + +v1.5 explicitly defers `std.io.print_unit`, file IO, environment variables, +process arguments, time, vectors/collections, allocation, user-defined +standard modules, imports/packages, overloading, generic APIs, Unicode length +semantics, and ABI/layout promises. + +The normative v1.5 release contract is +`.llm/V1_5_STANDARD_LIBRARY_ALPHA.md`. + +## 4.4 v1.7 Developer Experience Hardening + +Status: frozen v1.7 Slovo-side contract. Implementation support requires the +matching Glagol v1.7 gates. + +v1.7 adds tooling contracts only. It does not add source language syntax, +type-system behavior, runtime behavior, standard-runtime names, package +management, dependency management, LSP support, stable debug metadata, DWARF, +or stable source-map files. + +`glagol new [--name ]` scaffolds a valid v1.3-style +project with `slovo.toml`, `src/main.slo`, and a small testable `main` module +using only already-supported language forms. It must fail without overwriting +non-empty target directories. + +`glagol fmt ` remains stdout formatting. `glagol fmt --check +` checks canonical formatting without writing files. +`glagol fmt --write ` writes canonical formatting for one file +or for all immediate `.slo` project source modules in deterministic v1.3 +module order. `--check` and `--write` are mutually exclusive. + +`glagol doc -o ` generates deterministic Markdown +documentation from current source structure: modules, imports/exports, +structs, functions, and tests. It is a documentation generator, not a semantic +reflection API, and it does not expose stable typed-core, debug metadata, +source-map, ABI, layout, runtime reflection, or compiler-internal contracts. + +The repository must provide a local release-gate script or documented command +entry point that runs the full v1 release gate for the matching Slovo and +Glagol release without network, tag, push, or release-publication side effects. + +The normative v1.7 release contract is +`.llm/V1_7_DEVELOPER_EXPERIENCE_HARDENING.md`. + +## 4.5 v2.0.0-beta.1 Experimental Integration Readiness + +Status: current experimental Slovo-side release contract, released 2026-05-17. +Implementation support requires the matching Glagol experimental gate. + +`v2.0.0-beta.1` is an experimental integration/readiness release based on +v1.7. It makes the accumulated v1.1-v1.7 surface supported for small real flat +local projects using `glagol new`, `glagol check`, `glagol fmt --check`, +`glagol fmt --write`, `glagol test`, `glagol build`, `glagol doc`, artifact +manifests, JSON diagnostics, and the release-gate script. + +The experimental language surface is exactly the gate-proven v1.1-v1.7 surface: +`i32`, `bool`, builtin internal `unit`, tests, locals, `if`, `while`, structs, +fixed direct scalar and fixed string arrays with checked indexing, string value +flow, option/result values and the exact v1.4 match slice, standard-runtime +alpha names, and the v1.6 lexical unsafe boundary with reserved unsafe heads. + +Basic IO is limited to `std.io.print_i32`, `std.io.print_string`, +`std.io.print_bool`, and `std.string.len`. File IO, environment variables, +process arguments, time, broader runtime IO, vectors, growable collections, +stable ABI/layout, FFI, raw-memory execution, LSP, stable debug metadata, +stable source-map files, and package registries remain future post-beta work. + +Vectors and other growable collections are not required for this experimental +release unless they are separately implemented and gate-proven in Glagol. +Fixed direct scalar arrays with checked indexing satisfy the experimental gate +because this release is an integration milestone, while growable collections +require allocation, ownership/lifetime, mutation, capacity, diagnostics, +lowering, runtime tests, and docs that remain future post-beta work. + +The normative experimental release contract is +`.llm/V2_0_0_BETA_1_RELEASE_CONTRACT.md`. + +## 4.6 exp-1 Owned Runtime Strings + +Status: current experimental compiler-supported contract after matching Glagol +exp-1 gates. + +exp-1 promotes exactly one new standard-runtime string operation: + +```slo +(std.string.concat left right) +``` + +The signature is: + +```text +std.string.concat: (string, string) -> string +``` + +The result is an immutable runtime-owned `string` whose decoded byte sequence +is the left operand followed by the right operand. Existing string equality, +`std.string.len`, `std.io.print_string`, string locals, string parameters, +string returns, and calls returning `string` apply to both literal-backed +strings and runtime-owned strings. + +`std.string.concat` is compiler-known. It requires no import, does not name a +user module, does not create a package dependency, and is not a stable C ABI +symbol. No legacy `string_concat` alias is introduced. + +The compiler/runtime owns cleanup for runtime-owned strings. Cleanup timing, +allocator choice, runtime helper names, object layout, and pointer +representation are not source-visible and are not stable ABI/layout promises. +Safe Slovo code cannot manually free, retain, borrow, or mutate a string. + +Allocation failure traps with: + +```text +slovo runtime error: string allocation failed +``` + +The message is written to stderr with a trailing newline and the process exits +with code `1`. + +The exact `std.string.concat` name is reserved from user function, export, +import, local, and parameter shadowing. Unknown or unpromoted `std.*` calls +continue to use structured standard-library diagnostics. + +exp-1 explicitly defers mutable strings, string containers, string indexing, +string slicing, interpolation, formatting APIs, user-visible deallocation, +file IO, packages, generics, growable collections, raw-memory execution, FFI, +and stable ABI/layout promises. + +The normative exp-1 contract is +`.llm/EXP_1_OWNED_RUNTIME_STRINGS.md`. + +## 4.7 exp-2 Collections Alpha + +Status: current experimental compiler-supported contract after matching Glagol +exp-103 gates. + +exp-2 promotes one concrete growable vector type, and exp-94 extends the slice +with exactly one additional concrete growable vector type. exp-99 extends the +same slice with one more concrete growable vector type, and exp-103 extends +it again with one more concrete growable vector type: + +```text +(vec i32) +(vec f64) +(vec i64) +(vec string) +``` + +Together the slice promotes exactly sixteen compiler-known standard-runtime +operations: + +```text +std.vec.i32.empty: () -> (vec i32) +std.vec.i32.append: ((vec i32), i32) -> (vec i32) +std.vec.i32.len: ((vec i32)) -> i32 +std.vec.i32.index: ((vec i32), i32) -> i32 +std.vec.f64.empty: () -> (vec f64) +std.vec.f64.append: ((vec f64), f64) -> (vec f64) +std.vec.f64.len: ((vec f64)) -> i32 +std.vec.f64.index: ((vec f64), i32) -> f64 +std.vec.i64.empty: () -> (vec i64) +std.vec.i64.append: ((vec i64), i64) -> (vec i64) +std.vec.i64.len: ((vec i64)) -> i32 +std.vec.i64.index: ((vec i64), i32) -> i64 +std.vec.string.empty: () -> (vec string) +std.vec.string.append: ((vec string), string) -> (vec string) +std.vec.string.len: ((vec string)) -> i32 +std.vec.string.index: ((vec string), i32) -> string +``` + +The source call forms are: + +```slo +(std.vec.i32.empty) +(std.vec.i32.append values value) +(std.vec.i32.len values) +(std.vec.i32.index values index) +(std.vec.f64.empty) +(std.vec.f64.append values value) +(std.vec.f64.len values) +(std.vec.f64.index values index) +(std.vec.i64.empty) +(std.vec.i64.append values value) +(std.vec.i64.len values) +(std.vec.i64.index values index) +(std.vec.string.empty) +(std.vec.string.append values value) +(std.vec.string.len values) +(std.vec.string.index values index) +``` + +An exp-2 `(vec i32)`, exp-94 `(vec i64)`, exp-99 `(vec string)`, and +exp-103 `(vec f64)` are immutable runtime-owned vectors. They may flow +through immutable `let` locals, parameters, returns, calls returning the +same vector family, and top-level tests. +`std.vec.i32.append` returns a new immutable runtime-owned vector containing +the input vector's elements followed by the appended `i32`; it must not mutate +the input vector. `std.vec.i64.append` returns a new immutable runtime-owned +vector containing the input vector's elements followed by the appended `i64`; +it must not mutate the input vector. `std.vec.f64.append` returns a new +immutable runtime-owned vector containing the input vector's elements +followed by the appended `f64`; it must not mutate the input vector. +`std.vec.string.append` returns a new +immutable runtime-owned vector containing the input vector's elements followed +by the appended `string`; it must not mutate the input vector. +`std.vec.i32.len`, `std.vec.i64.len`, `std.vec.f64.len`, and +`std.vec.string.len` return vector length as `i32`. `std.vec.i32.index` +returns an `i32` element, `std.vec.i64.index` returns an `i64` element, +`std.vec.f64.index` returns an `f64` element, and `std.vec.string.index` +returns a `string` element, after the same runtime bounds check equivalent +to `0 <= index && index < len`. + +Vector equality uses `=` with either two `(vec i32)` operands, two +`(vec i64)` operands, two `(vec f64)` operands, or two `(vec string)` +operands, returns `bool`, and compares length and element values in order. +It is not pointer identity, allocation identity, capacity equality, or +layout equality. + +Allocation failure traps with: + +```text +slovo runtime error: vector allocation failed +``` + +Index failure traps with: + +```text +slovo runtime error: vector index out of bounds +``` + +Both messages are written to stderr with a trailing newline and exit the +process with code `1`. + +The `std.vec.i32.*`, `std.vec.i64.*`, `std.vec.f64.*`, `std.vec.bool.*`, and +`std.vec.string.*` names are compiler-known. They require no import, do not +name user modules, do not create package dependencies, and are not stable C +ABI symbols or stable runtime helper symbols. The exact promoted names are +reserved from user function, export, import, local, and parameter +shadowing. + +The latest released Slovo-side collection target after exp-107 is `exp-108`, +Standard Vec String, F64, And Bool Prefix And Suffix Helpers Alpha. It +broadens `std/vec_string.slo`, `std/vec_f64.slo`, and `std/vec_bool.slo` +with exactly `starts_with`, `without_prefix`, `ends_with`, and +`without_suffix` over the already released concrete vec-string, vec-f64, and +vec-bool helper surfaces. The helper lanes stay source-authored, +recursive, and immutable and do not widen the promoted collection runtime +surface beyond those connected prefix/suffix helpers. + +The collections alpha slice explicitly defers generic vectors, vector element +types other than `i32`, `i64`, `f64`, `bool`, and `string`, vector mutation, vector +`var`, vector `set`, vector literals besides empty/append construction, a `push` +alias, nested vectors, vectors in arrays, vectors in structs, vectors in +options, vectors in results, iterators, slices, maps, sets, user-visible +deallocation, stable ABI/layout/helper-symbol promises, package expansion, +and IO expansion. + +The collection fixture targets are: + +```text +examples/supported/vec-i32.slo +examples/formatter/vec-i32.slo +examples/supported/vec-bool.slo +examples/formatter/vec-bool.slo +examples/supported/vec-i64.slo +examples/formatter/vec-i64.slo +examples/supported/vec-string.slo +examples/formatter/vec-string.slo +``` + +They are current compiler-supported fixtures. + +The normative contracts are `.llm/EXP_2_COLLECTIONS_ALPHA.md` and +`.llm/EXP_94_STANDARD_VEC_I64_BASELINE_ALPHA.md` plus +`.llm/EXP_99_STANDARD_VEC_STRING_BASELINE_ALPHA.md`. + +## 4.8 exp-3 Standard IO And Host Environment + +Status: current experimental compiler-supported contract after matching Glagol +exp-3 gates. + +exp-3 promotes exactly six compiler-known host functions: + +```text +std.io.eprint: (string) -> unit +std.process.argc: () -> i32 +std.process.arg: (i32) -> string +std.env.get: (string) -> string +std.fs.read_text: (string) -> string +std.fs.write_text: (string string) -> i32 +``` + +The source call forms are: + +```slo +(std.io.eprint value) +(std.process.argc) +(std.process.arg index) +(std.env.get name) +(std.fs.read_text path) +(std.fs.write_text path text) +``` + +`std.io.eprint` writes the string to stderr without appending a newline and +returns builtin `unit`. `std.process.argc` returns the process argument count +as `i32`. `std.process.arg` uses zero-based indexing and returns a `string`. +Out-of-range argument access traps with: + +```text +slovo runtime error: process argument index out of bounds +``` + +`std.env.get` returns the environment variable value as `string`; a missing +variable returns the empty string in exp-3. + +`std.fs.read_text` reads a whole text file and returns `string`. Host read +failure traps with: + +```text +slovo runtime error: file read failed +``` + +`std.fs.write_text` writes a whole string to a path and returns `0` on success +or `1` on host failure. + +The trap messages are written to stderr with a trailing newline and exit with +code `1`. Strings returned by process, environment, and file APIs are +immutable runtime-provided strings. exp-3 does not promise whether host strings +are copied, borrowed from host process storage, or allocated by private runtime +helpers; source code cannot observe storage identity or cleanup timing. File +read allocation failure is reported through the exp-3 file-read trap in this +stage. + +The promoted names are compiler-known. They require no import, do not name user +modules, do not create package dependencies, and are not stable C ABI symbols +or stable runtime helper symbols. The exact promoted names are reserved from +user function, export, import, local, and parameter shadowing. + +exp-3 explicitly defers networking, async IO, binary file APIs, directory +traversal, terminal control, platform abstraction, general host error ADTs, +`result string Error`, package interaction, stdin full read or line iteration, +randomness, time, stable ABI/layout, and stable runtime helper symbols. + +The exp-3 fixtures are: + +```text +examples/supported/host-io.slo +examples/formatter/host-io.slo +``` + +They are current compiler-supported fixtures. + +The normative exp-3 contract is +`.llm/EXP_3_STANDARD_IO_HOST_ENV.md`. + +## 4.9 exp-4 User Data Types And Polymorphism + +Status: current experimental compiler-supported contract after matching Glagol +exp-4 gates. + +exp-4 promotes payloadless user-defined enum declarations only: + +```slo +(enum Name VariantA VariantB) +``` + +An enum declaration must have at least one variant. Variant values are +constructed through zero-argument qualified calls: + +```slo +(Name.VariantA) +``` + +An exp-4 enum value may flow through immutable `let` locals, parameters, +returns, calls returning the enum type, equality with `=`, and top-level tests. +Enum equality is defined only for two values of the same enum type and compares +variant identity. + +`match` supports enum subjects with exhaustive payloadless variant arms: + +```slo +(match value + ((Name.VariantA) + body...) + ((Name.VariantB) + body...)) +``` + +Every variant of the subject enum must appear exactly once. Enum match arms +have one or more expression bodies, and the final expression of each arm must +share a common result type, following the existing option/result match rule. + +The runtime/backend representation is compiler-owned. exp-4 makes no stable +layout, discriminant value, ABI, helper-symbol, reflection, or conversion to +`i32` promise. + +exp-4 explicitly defers payload variants, tuple variants, record variants, +generic enums, generic functions, type aliases, traits, interfaces, protocols, +methods, derives, variant payload binding, wildcard patterns, rest patterns, +guards, nested patterns, enum values in arrays, enum values in struct fields, +enum values in options, enum values in results, enum values in vectors, enum +mutation, enum printing, enum ordering, enum hashing, reflection, explicit +discriminants, enum constructors with arguments, unqualified variant +constructors, stable ABI/layout, stable helper symbols, and moving +option/result to ordinary standard-library types. + +The exp-4 fixtures are: + +```text +examples/supported/enum-basic.slo +examples/formatter/enum-basic.slo +``` + +They are current compiler-supported fixtures. + +The normative exp-4 contract is +`.llm/EXP_4_USER_ADTS_ALPHA.md`. + +### 4.9.1 exp-16 Unary I32 Enum Payloads Alpha + +Status: released experimental Slovo contract. Matching Glagol exp-16 gates are +required before broader compiler-support claims. + +exp-16 extends user-defined enum declarations to allow payloadless variants +and unary `i32` payload variants in the same enum: + +```slo +(enum Reading + Missing + (Value i32) + (Offset i32)) +``` + +Payloadless variants continue to use zero-argument qualified constructors: + +```slo +(Reading.Missing) +``` + +Payload variants use qualified constructors with exactly one `i32` argument: + +```slo +(Reading.Value value) +``` + +Enum values may flow through immutable locals, parameters, returns, calls, +top-level tests, and `main`. + +`match` supports exhaustive arms for both payloadless and unary payload +variants: + +```slo +(match reading + ((Reading.Missing) + 0) + ((Reading.Value payload) + payload)) +``` + +Payload variants require exactly one binding in match arms. Payloadless +variants take no binding. Payload bindings are immutable names scoped only to +the selected arm body. + +Enum equality is defined only for two values of the same enum type. Payload +variants compare by tag and payload value. Payloadless variants compare by tag. + +The runtime/backend representation is compiler-owned. exp-16 makes no stable +layout, discriminant value, ABI, helper-symbol, reflection, printing, hashing, +ordering, or conversion to `i32` promise. + +exp-16 explicitly defers payload types other than `i32`, multiple payloads, +record variants, tuple variants beyond one `i32`, string/bool/struct/enum +payloads, generic enums, generic functions, type aliases, traits, methods, +derives, wildcard/rest/nested enum patterns, pattern guards, enum payload +mutation, enum values inside arrays/structs/options/results/vectors, enum +printing/ordering/hash/reflection/discriminants/stable ABI/layout, moving +option/result to ordinary standard-library types, manifest schema changes, +runtime ABI/layout claims, and beta maturity. + +The exp-16 fixtures are: + +```text +examples/supported/enum-payload-i32.slo +examples/formatter/enum-payload-i32.slo +``` + +The normative exp-16 contract is +`.llm/EXP_16_UNARY_I32_ENUM_PAYLOADS_ALPHA.md`. + +### 4.9.2 exp-17 Project Enum Imports Alpha + +Status: released experimental Slovo contract. Matching Glagol exp-17 gates are +required before broader compiler-support claims. + +exp-17 extends explicit project and workspace module boundaries so export +lists may include top-level enum names: + +```slo +(module readings (export Reading)) +``` + +Import lists may import those exported enum names: + +```slo +(import readings (Reading)) +``` + +In exp-5 workspace packages, the existing package-qualified module import form +may also import exported enum names from direct local dependencies: + +```slo +(import sensors.readings (Reading)) +``` + +Imported enum types may be used in function signatures, immutable local type +annotations, calls/returns, same-enum equality, exhaustive enum matches, and +qualified constructors. + +The enum surface is exactly the exp-4 payloadless enum surface plus the exp-16 +unary `i32` payload enum surface. No other payload types, generic enums, +containers, mutation, printing, ordering, hashing, reflection, stable +ABI/layout, manifest schema changes, registry/package-manager behavior, or +beta maturity are added. + +The exp-17 project fixture is: + +```text +examples/projects/enum-imports/slovo.toml +examples/projects/enum-imports/src/readings.slo +examples/projects/enum-imports/src/main.slo +``` + +No formatter project fixture is defined because the current Slovo formatter +fixture convention is single-file fixtures under `examples/formatter/`, not +whole project directories. + +The normative exp-17 contract is +`.llm/EXP_17_PROJECT_ENUM_IMPORTS_ALPHA.md`. + +### 4.9.3 exp-18 Enum Struct Fields Alpha + +Status: released experimental Slovo contract. Matching Glagol exp-18 gates are +required before broader compiler-support claims. + +exp-18 extends struct field declarations so field types may be current +user-defined enum type names: + +```slo +(enum Status Ready Blocked) + +(enum Reading + Missing + (Value i32)) + +(struct TaggedReading + (status Status) + (reading Reading)) +``` + +Struct construction uses the existing field initializer form, with enum values +provided through the already promoted qualified enum constructors: + +```slo +(TaggedReading (status (Status.Ready)) (reading (Reading.Value payload))) +``` + +Field access through an enum-typed field returns the declared enum type: + +```slo +(. tagged reading) +``` + +That field access expression may be used in same-enum equality and exhaustive +enum `match`: + +```slo +(= (. tagged status) (Status.Ready)) + +(match (. tagged reading) + ((Reading.Missing) + 0) + ((Reading.Value payload) + payload)) +``` + +The enum field surface is exactly the exp-4 payloadless enum surface plus the +exp-16 unary `i32` payload enum surface. Enum-typed struct fields may flow +through struct construction, immutable struct locals, struct parameters, +struct returns, calls returning structs, field access, top-level tests, and +`main`. + +exp-18 does not add enum payload types other than `i32`, multiple payloads, +record variants, tuple variants beyond one `i32`, enum values inside arrays, +options, results, or vectors, arrays/options/results/vectors containing +enum-typed structs, nested structs, struct mutation, enum mutation, printing, +ordering, hashing, reflection, import aliases/globs/re-exports, manifest +schema changes, package-manager behavior, stable ABI/layout, generics, or beta +maturity. + +The exp-18 fixtures are: + +```text +examples/supported/enum-struct-fields.slo +examples/formatter/enum-struct-fields.slo +``` + +The normative exp-18 contract is +`.llm/EXP_18_ENUM_STRUCT_FIELDS_ALPHA.md`. + +### 4.9.4 exp-19 Primitive Struct Fields Alpha + +Status: released experimental Slovo contract. Matching Glagol exp-19 gates are +required before broader compiler-support claims. + +exp-19 extends struct field declarations so field types may be direct `bool` +and immutable `string`, alongside already supported direct `i32` and exp-18 +enum fields: + +```slo +(struct PrimitiveRecord + (id i32) + (active bool) + (label string)) +``` + +Struct construction uses the existing field initializer form: + +```slo +(PrimitiveRecord (id id) (active (= id 7)) (label label)) +``` + +Field access through a primitive field returns the declared primitive type: + +```slo +(. record active) +(. record label) +``` + +Those field access expressions may be used in existing operations for the +declared type: + +```slo +(if (. record active) (. record id) 0) +(= (. record label) "alpha") +(std.string.len (. record label)) +``` + +The primitive field surface is exactly direct `i32`, direct `bool`, and +immutable direct `string`. Primitive-typed struct fields may flow through +struct construction, immutable struct locals, struct parameters, struct +returns, calls returning structs, field access, top-level tests, and `main`. + +exp-19 does not add arrays, vectors, options, or results containing +primitive-field structs, primitive fields inside containers, nested structs, +struct mutation, mutable string locals, string assignment, string mutation, +ownership/cleanup guarantees beyond existing string behavior, broader string +operations, printing beyond existing legacy and v1.5 calls, package/import +widening, manifest schema changes, stable ABI/layout, generics, methods, +traits, or beta maturity. + +The exp-19 fixtures are: + +```text +examples/supported/primitive-struct-fields.slo +examples/formatter/primitive-struct-fields.slo +``` + +The normative exp-19 contract is +`.llm/EXP_19_PRIMITIVE_STRUCT_FIELDS_ALPHA.md`. + +### 4.9.4a exp-112 Immutable Bool Locals Alpha + +Status: released experimental Slovo contract. Matching Glagol exp-112 gates +are required before broader compiler-support claims. + +exp-112 broadens local bindings with direct immutable `bool` locals: + +```slo +(let flag bool true) +``` + +The promoted surface is: + +- immutable `let` locals declared as `bool` +- local initializers from already promoted bool literals, parameters, calls + returning bool, and `if` expressions returning bool +- local references in existing predicate, return, call, top-level test, and + `main` positions + +Mutable `bool` locals remain unsupported; exp-112 does not broaden `var` or +`set`. + +The exp-112 fixtures are: + +```text +examples/supported/local-variables.slo +examples/formatter/local-variables.slo +examples/projects/std-import-io/ +``` + +The supported/formatter local-variables fixture keeps the earlier i32 local +surface and now also includes direct immutable bool locals. The current +`std-import-io` explicit import fixture uses bool locals in its stdin bool +fallback helper path once the compiler accepts that local family. + +exp-112 does not add mutable `bool` locals, bool assignment, new boolean +operators, broader local-type widening, automatic imports, compiler-known +runtime names, manifest schema changes, stable ABI/layout, or beta maturity. + +The normative exp-112 contract is +`.llm/EXP_112_IMMUTABLE_BOOL_LOCALS_ALPHA.md`. + +### 4.9.4b exp-113 Mutable Scalar Locals Alpha + +Status: released experimental Slovo contract. Matching Glagol exp-113 gates +are required before broader compiler-support claims. + +exp-113 broadens local bindings with mutable scalar var/set flows: + +```slo +(var total i64 40i64) +(set total (+ total 2i64)) +``` + +The promoted surface is: + +- mutable `var` locals declared as `bool`, `i64`, or `f64` +- `set` reassignment from already promoted same-type scalar expressions for + those declared local types +- use in ordinary function bodies, top-level tests, and `main` + +The exp-113 fixtures are: + +```text +examples/supported/local-variables.slo +examples/formatter/local-variables.slo +``` + +The supported/formatter local-variables fixture retains the earlier i32 local +surface and exp-112 immutable bool locals, and now also covers mutable +`bool`, `i64`, and `f64` local var/set flows. + +exp-113 does not add mutable `string` locals, vector mutation, option/result +mutation, struct or enum mutation, mixed-type assignment, automatic imports, +compiler-known runtime names, manifest schema changes, stable ABI/layout, or +beta maturity. + +The normative exp-113 contract is +`.llm/EXP_113_MUTABLE_SCALAR_LOCALS_ALPHA.md`. + +### 4.9.4c exp-114 Mutable Composite Locals Alpha + +Status: released experimental Slovo contract. Matching Glagol exp-114 gates +are required before broader compiler-support claims. + +exp-114 broadens local bindings with mutable whole-value composite var/set +flows: + +```slo +(var label string "oak") +(set label "slovo") +``` + +The promoted surface is: + +- mutable `var` locals declared as `string` +- mutable `var` locals declared as the current concrete vec families: + `(vec i32)`, `(vec i64)`, `(vec f64)`, `(vec bool)`, and `(vec string)` +- mutable `var` locals declared as the current concrete option families: + `(option i32)`, `(option i64)`, `(option f64)`, `(option bool)`, and + `(option string)` +- mutable `var` locals declared as the current concrete result families: + `(result i32 i32)`, `(result i64 i32)`, `(result f64 i32)`, + `(result bool i32)`, and `(result string i32)` +- mutable `var` locals declared as current known struct value types +- mutable `var` locals declared as current enum value types +- `set` reassignment from already promoted expressions of the exact same local + type +- use in ordinary function bodies, top-level tests, and `main` + +The reassignment is whole-value only. Existing field access, vec indexing, +option/result observers and payload extraction, equality, and enum/option/ +result `match` continue to operate after the reassignment. + +The exp-114 fixtures are: + +```text +examples/supported/composite-locals.slo +examples/formatter/composite-locals.slo +``` + +The supported/formatter composite-locals fixture covers same-type mutable +whole-value local reassignment for strings, the current concrete vec families, +the current concrete option/result families, current known struct values, and +current enum values. + +exp-114 does not add mutable arrays, array mutation, field mutation, vector +element mutation, option/result payload mutation, enum payload mutation, +mixed-type assignment, automatic imports, compiler-known runtime names, +manifest schema changes, stable ABI/layout, or beta maturity. + +The normative exp-114 contract is +`.llm/EXP_114_MUTABLE_COMPOSITE_LOCALS_ALPHA.md`. + +### 4.9.5 exp-20 F64 Numeric Primitive Alpha + +Status: released experimental Slovo contract. Matching Glagol exp-20 gates are +required before broader compiler-support claims. + +exp-20 introduces direct `f64` as the first non-`i32` numeric primitive: + +```slo +(fn half ((value f64)) -> f64 + (/ value 2.0)) +``` + +The promoted direct `f64` surface is: + +- `f64` function parameters and returns +- immutable `let` locals declared as `f64` +- calls passing and returning `f64` +- decimal `f64` literals in `f64` contexts +- same-type `f64` `+`, `-`, `*`, and `/` +- same-type `f64` `=`, `<`, `>`, `<=`, and `>=` +- `std.io.print_f64` +- top-level tests and `main` bodies; `main` still returns `i32` + +`std.io.print_f64` accepts exactly one `f64`, returns builtin `unit`, and +prints an implementation-owned finite `f64` textual representation plus a +newline. Source-level NaN and infinity literals are unsupported in exp-20. +exp-20 does not stabilize exact printed digits beyond newline-terminated +output. + +All promoted arithmetic and comparison is same-type `f64`. exp-20 does not add +mixed `i32`/`f64` arithmetic, numeric casts, implicit promotion, overloaded +operators, or generic numeric abstractions. + +The exp-20 fixtures are: + +```text +examples/supported/f64-numeric-primitive.slo +examples/formatter/f64-numeric-primitive.slo +``` + +The normative exp-20 contract is +`.llm/EXP_20_F64_NUMERIC_PRIMITIVE_ALPHA.md`. + +### 4.9.6 exp-22 Numeric Widening Conversions Alpha + +Status: released experimental alpha Slovo contract. Matching Glagol exp-22 +gates are required before broader compiler-support claims. + +exp-22 promotes exactly three explicit compiler-known standard-runtime calls +over the current `i32`, `i64`, and `f64` numeric primitives: + +- `std.num.i32_to_i64 : (i32) -> i64` +- `std.num.i32_to_f64 : (i32) -> f64` +- `std.num.i64_to_f64 : (i64) -> f64` + +The promoted calls are ordinary source calls and can feed same-type arithmetic +or comparison after conversion. exp-22 does not introduce implicit promotion, +mixed numeric arithmetic/comparison, narrowing or checked conversions, cast +syntax, `f32`, unsigned or narrower integer families, numeric parse/format +APIs, stable ABI/layout, manifest schema changes, or beta maturity. + +The exp-22 fixtures are byte-identical: + +```text +examples/supported/numeric-widening-conversions.slo +examples/formatter/numeric-widening-conversions.slo +``` + +The normative exp-22 contract is +`.llm/EXP_22_NUMERIC_WIDENING_CONVERSIONS_ALPHA.md`. + +### 4.9.7 exp-23 Checked I64-To-I32 Conversion Alpha + +Status: released experimental alpha Slovo contract. Matching Glagol exp-23 +gates are required before broader compiler-support claims. + +exp-23 promotes exactly one checked narrowing compiler-known standard-runtime +call: + +- `std.num.i64_to_i32_result : (i64) -> (result i32 i32)` + +The promoted call is an ordinary source call. It returns `ok value` when the +signed `i64` input is within the signed `i32` range `-2147483648` through +`2147483647`, and returns `err 1` when the input is outside that range. Range +failure does not trap. + +The returned `(result i32 i32)` value uses the existing result helper surface. +exp-23 does not introduce implicit promotion, mixed numeric +arithmetic/comparison, cast syntax, `std.num.cast`, checked cast generics, any +other narrowing conversion, `f64` conversions, unsigned or narrower integer +families, numeric parse/format APIs, stable ABI/layout, manifest schema +changes, or beta maturity. + +The exp-23 fixtures are byte-identical: + +```text +examples/supported/checked-i64-to-i32-conversion.slo +examples/formatter/checked-i64-to-i32-conversion.slo +``` + +The normative exp-23 contract is +`.llm/EXP_23_CHECKED_I64_TO_I32_CONVERSION_ALPHA.md`. + +### 4.9.8 exp-24 Integer To String Alpha + +Status: released experimental alpha Slovo contract. Matching Glagol exp-24 +gates are required before broader compiler-support claims. + +exp-24 promotes exactly two integer formatting compiler-known +standard-runtime calls: + +- `std.num.i32_to_string : (i32) -> string` +- `std.num.i64_to_string : (i64) -> string` + +The promoted calls are ordinary source calls. Each returns the decimal signed +ASCII string for the input value. Negative values include a leading `-`; +non-negative values have no leading `+`. + +Returned strings use the existing `string` surface: string equality, +`std.string.len`, immutable locals, function returns, calls, and +`std.io.print_string`. The implementation is compiler/runtime-backed for now +and may later move into Slovo `std` source once the language can host it. + +exp-24 does not introduce `f64` formatting, parse APIs beyond the released +i32/i64 result calls, locale/base/radix, grouping, or padding controls, +generic format/display traits, implicit conversions, stable standard-library +implementation source, stable helper ABI/layout/ownership, manifest schema +changes, or beta maturity. + +The exp-24 fixtures are byte-identical: + +```text +examples/supported/integer-to-string.slo +examples/formatter/integer-to-string.slo +``` + +The normative exp-24 contract is +`.llm/EXP_24_INTEGER_TO_STRING_ALPHA.md`. + +### 4.9.9 exp-25 String Parse I64 Result Alpha + +Status: released experimental alpha Slovo contract. Matching Glagol exp-25 +gates are required before broader compiler-support claims. + +exp-25 promotes exactly one string parse compiler-known standard-runtime call: + +- `std.string.parse_i64_result : (string) -> (result i64 i32)` + +The promoted call parses the entire input string as a non-empty ASCII signed +decimal `i64`. Accepted input has an optional leading `-`, one or more ASCII +digits, and a numeric value in the signed `i64` range. Success returns +`ok value`; ordinary parse failure and out-of-range input return `err 1`. + +The empty string, a lone `-`, any leading `+`, whitespace, non-digits, +underscores, base/radix prefixes, suffixes, locale-specific digits or +separators, Unicode digits, trailing bytes, and out-of-range values all return +`err 1`. Ordinary parse failure and range failure do not trap. + +exp-25 extends the supported concrete result families to include +`(result i64 i32)` for result observation, extraction, direct matching, and +fixture value flow. It does not add generic results, stable result ABI/layout, +or result helper behavior beyond the explicitly supported concrete family. + +The exp-25 fixtures are byte-identical: + +```text +examples/supported/string-parse-i64-result.slo +examples/formatter/string-parse-i64-result.slo +``` + +The normative exp-25 contract is +`.llm/EXP_25_STRING_PARSE_I64_RESULT_ALPHA.md`. + +### 4.9.10 exp-26 F64 To String Alpha + +Status: released experimental alpha Slovo contract. Matching Glagol exp-26 +gates are required before broader compiler-support claims. + +exp-26 promotes exactly one finite `f64` formatting compiler-known +standard-runtime call: + +- `std.num.f64_to_string : (f64) -> string` + +The promoted call returns finite decimal ASCII text for promoted finite `f64` +inputs. The exp-26 fixture values are `0.0`, `3.5`, `-1.5`, and `10.0`. + +This slice does not define `f32`, `f64` parse, generic format/display/ +interpolation APIs, locale/base/radix/grouping/padding/precision controls, +stable NaN/infinity text, implicit conversion, stable helper ABI/layout/ +ownership, manifest schema changes, or beta maturity. + +The exp-26 fixtures are byte-identical: + +```text +examples/supported/f64-to-string.slo +examples/formatter/f64-to-string.slo +``` + +The normative exp-26 contract is `.llm/EXP_26_F64_TO_STRING_ALPHA.md`. + +### 4.9.11 exp-27 Checked F64 To I32 Result Alpha + +Status: released experimental alpha Slovo contract. Matching Glagol exp-27 +gates are required before broader compiler-support claims. + +exp-27 promotes exactly one checked `f64` to `i32` result compiler-known +standard-runtime call: + +- `std.num.f64_to_i32_result : (f64) -> (result i32 i32)` + +The promoted call returns `ok value` only when the input is finite, exactly +integral, and within the signed `i32` range `-2147483648` through +`2147483647`. It returns `err 1` for non-finite, fractional, or out-of-range +input. Ordinary conversion failure does not trap. + +The exp-27 fixture values are `0.0 -> ok 0`, `-12.0 -> ok -12`, +`3.5 -> err 1`, and `2147483648.0 -> err 1`. + +This slice does not define unchecked `f64` to `i32`, casts or cast syntax, +generic `cast_checked`, `f32`, unsigned or narrower integer families, +additional numeric conversion families, `f64` parse, mixed numeric arithmetic, +numeric containers, stable helper ABI/layout/ownership, manifest schema +changes, or beta maturity. + +The exp-27 fixtures are byte-identical: + +```text +examples/supported/f64-to-i32-result.slo +examples/formatter/f64-to-i32-result.slo +``` + +The normative exp-27 contract is +`.llm/EXP_27_F64_TO_I32_RESULT_ALPHA.md`. + +### 4.9.12 exp-28 String Parse F64 Result Alpha + +Status: released experimental alpha Slovo contract. Matching Glagol exp-28 +gates are required before broader compiler-support claims. + +exp-28 promotes exactly one string parse compiler-known standard-runtime call: + +- `std.string.parse_f64_result : (string) -> (result f64 i32)` + +The promoted call parses the entire input string as finite ASCII decimal +`f64` text. Accepted fixture input has an optional leading `-`, one or more +ASCII digits before a decimal point, a decimal point `.`, one or more ASCII +digits after the decimal point, and a finite value representable as `f64`. +Success returns `ok value`. + +Ordinary parse failure, non-finite text, leading or trailing unsupported +characters, and out-of-domain input return `err 1`. Ordinary parse failure +and domain failure do not trap. + +The exp-28 fixture values are `12.5 -> ok 12.5`, `-0.25 -> ok -0.25`, +`abc -> err 1`, and `nan -> err 1`. + +This slice does not define generic parse, bool/string/bytes parse, locale +parsing, Unicode digit parsing, underscores, rich parse errors, stable helper +ABI/layout/ownership, result genericity, f64 containers, mixed numeric +arithmetic, manifest schema changes, or beta maturity. + +The exp-28 fixtures are byte-identical: + +```text +examples/supported/string-parse-f64-result.slo +examples/formatter/string-parse-f64-result.slo +``` + +The normative exp-28 contract is +`.llm/EXP_28_STRING_PARSE_F64_RESULT_ALPHA.md`. + +### 4.9.13 exp-29 Numeric Struct Fields Alpha + +Status: released experimental alpha Slovo contract. Matching Glagol exp-29 +gates are required before broader compiler-support claims. + +exp-29 promotes direct immutable struct field declarations whose field types +are exactly `i64` or `f64`, alongside the already supported direct `i32`, +`bool`, immutable `string`, and current enum fields. + +Struct constructors may initialize `i64` fields from `i64` values and explicit +signed decimal `i64` literal atoms. They may initialize `f64` fields from +finite `f64` values and finite decimal `f64` literals. The exp-20 finite-only +policy remains in force; non-finite `f64` literals are not promoted. + +Immutable struct values carrying numeric fields may flow through locals, +parameters, returns, and calls. Field access returns the declared `i64` or +`f64` type. Accessed fields may be used with existing same-type +arithmetic/comparison and existing numeric print/format helpers where already +supported. + +This slice does not define struct field mutation, nested structs, +arrays/vectors/options/results as fields, enum payload widening, numeric +containers, `f32`, unsigned or narrower integer families, mixed numeric +arithmetic, implicit conversions, new parse/format APIs, generic structs, +methods, traits, stable ABI/layout/ownership, manifest schema changes, FFI +layout claims, or beta maturity. + +The exp-29 fixtures are byte-identical: + +```text +examples/supported/numeric-struct-fields.slo +examples/formatter/numeric-struct-fields.slo +``` + +The normative exp-29 contract is +`.llm/EXP_29_NUMERIC_STRUCT_FIELDS_ALPHA.md`. + +### 4.9.14 exp-30 Standard Library Source Layout Alpha + +Status: released experimental alpha Slovo contract. Matching Glagol exp-30 +gates are required before broader compiler-support claims. + +exp-30 establishes `std/` as the Slovo source home for staged standard library +modules and examples: + +```text +std/README.md +std/io.slo +std/string.slo +std/num.slo +std/result.slo +std/math.slo +``` + +The source files use current valid Slovo where practical. Because current +project import support does not promote a repo-root `std/` search path, the +staged files use plain flat module declarations such as `(module math)`. + +`std/math.slo` contains narrow source-authored helpers expressible with the +current language: `abs_i32`, `min_i32`, `max_i32`, `clamp_i32`, +`square_i32`, and `square_f64`. The other staged modules may wrap already +promoted compiler-known standard-runtime calls with short module-local names. + +This slice does not define automatic standard-library imports, replacement of +compiler-known `std.*` calls with source implementations, new compiler-known +`std.*` operation names, generics, traits, overloads, module/package registry +changes, stable standard-library APIs, stable ABI/layout/ownership, manifest +schema changes, broad math, trigonometry, `pow`, `sqrt`, `f32`, unsigned or +narrower integers, mixed numeric arithmetic, numeric containers, or beta +maturity. + +The exp-30 formatter example is: + +```text +examples/formatter/std-source-layout-alpha.slo +``` + +The normative exp-30 contract is +`.llm/EXP_30_STANDARD_LIBRARY_SOURCE_LAYOUT_ALPHA.md`. + +### 4.9.15 exp-31 Checked F64 To I64 Result Alpha + +Status: released experimental alpha Slovo contract. Matching Glagol exp-31 +gates are required before broader compiler-support claims. + +exp-31 promotes exactly one checked `f64` to `i64` result compiler-known +standard-runtime call: + +- `std.num.f64_to_i64_result : (f64) -> (result i64 i32)` + +The promoted call returns `ok value` only when the input is finite, exactly +integral, and within the signed `i64` range `-9223372036854775808` through +`9223372036854775807`. It returns `err 1` for non-finite, fractional, or +out-of-range input. Ordinary conversion failure does not trap. + +The exp-31 fixture values are `0.0 -> ok 0i64`, `-12.0 -> ok -12i64`, +`3.5 -> err 1`, and `9223372036854776000.0 -> err 1`. The out-of-range value +is the formatter-stable spelling of the accepted exact positive out-of-range +literal `9223372036854775808.0`; detailed edge behavior around the `f64`/`i64` +limits is implementation-owned Glagol test coverage rather than a larger Slovo +fixture matrix. + +This slice does not define unchecked casts, unchecked `f64` to `i64`, casts or +cast syntax, generic `cast_checked`, `f32`, unsigned or narrower integer +families, additional numeric conversion families, mixed numeric arithmetic, +broad math APIs, numeric containers, stable helper ABI/layout/ownership, +manifest schema changes, or beta maturity. + +The exp-31 fixtures are byte-identical: + +```text +examples/supported/f64-to-i64-result.slo +examples/formatter/f64-to-i64-result.slo +``` + +The normative exp-31 contract is +`.llm/EXP_31_CHECKED_F64_TO_I64_RESULT_ALPHA.md`. + +### 4.9.16 exp-32 Standard Math Source Helpers Alpha + +Status: released experimental alpha Slovo contract. Matching Glagol exp-32 +source-check and formatter gates are required before broader compiler-support +claims. + +exp-32 extends staged `std/math.slo` with narrow source-authored helpers over +existing numeric primitives. The existing exp-30 helpers remain: + +- `abs_i32 : (i32) -> i32` +- `min_i32 : (i32, i32) -> i32` +- `max_i32 : (i32, i32) -> i32` +- `clamp_i32 : (i32, i32, i32) -> i32` +- `square_i32 : (i32) -> i32` +- `square_f64 : (f64) -> f64` + +exp-32 adds: + +- `abs_i64 : (i64) -> i64` +- `min_i64 : (i64, i64) -> i64` +- `max_i64 : (i64, i64) -> i64` +- `clamp_i64 : (i64, i64, i64) -> i64` +- `square_i64 : (i64) -> i64` +- `abs_f64 : (f64) -> f64` +- `min_f64 : (f64, f64) -> f64` +- `max_f64 : (f64, f64) -> f64` +- `clamp_f64 : (f64, f64, f64) -> f64` + +These are ordinary Slovo source functions using existing same-type arithmetic +and comparison, `if`, literals, calls, and explicit signatures. `clamp_*` is +defined as `max(low, min(value, high))` in source and does not reorder or +validate bounds. + +Current Slovo promotes two ways to use these helpers. Projects may keep the +earlier local-copy pattern and import a local `math.slo`, or, as of exp-44, +may explicitly import the repo-root standard module with +`(import std.math (...))`. exp-44 does not make the helpers implicit. + +This slice does not define new compiler-known `std.*` operation names, +standard-runtime catalog entries, replacement of compiler-known `std.*` calls +with source implementations, broad math, trigonometry, `sqrt`, `pow`, `f32`, +unsigned or narrower integers, generic math, overloads, traits, mixed numeric +arithmetic, numeric containers, stable standard-library APIs, stable +ABI/layout/ownership, manifest schema changes, or beta maturity. + +The exp-32 source fixture is: + +```text +std/math.slo +``` + +The normative exp-32 contract is +`.llm/EXP_32_STANDARD_MATH_SOURCE_HELPERS_ALPHA.md`. + +### 4.9.16a exp-44 Standard Library Source Search Alpha + +Status: released experimental alpha Slovo contract. Matching Glagol exp-44 +project-mode gates are required before broader compiler-support claims. + +exp-44 promotes the first explicit standard-library source import address: + +```slo +(import std.math (abs_i32 square_i32 abs_i64 square_i64 abs_f64 square_f64)) +``` + +The external import name `std.math` resolves to the staged repo-root source +file `std/math.slo`. The source file still declares the flat module +`(module math ...)`, but now carries an explicit export list for the promoted +math helper names. + +The exp-44 fixture is: + +```text +examples/projects/std-import-math/ +``` + +This slice does not define automatic standard-library imports, a `std.slo` +aggregator, aliases, glob imports, qualified member access, workspace/package +standard-library imports, package registry behavior, broad standard-library +APIs, stable standard-library APIs, stable ABI/layout/ownership, optimizer +guarantees, benchmark thresholds, or beta maturity. + +The normative exp-44 contract is +`.llm/EXP_44_STANDARD_LIBRARY_SOURCE_SEARCH_ALPHA.md`. + +### 4.9.17 exp-33 Standard Result Source Helpers Alpha + +Status: released experimental alpha Slovo contract. Matching Glagol exp-33 +source-check and formatter gates are required before broader compiler-support +claims. + +exp-33 extends staged `std/result.slo` with narrow source-authored helpers for +already promoted concrete result families. The existing i32 wrappers remain: + +- `is_ok_i32 : ((result i32 i32)) -> bool` +- `is_err_i32 : ((result i32 i32)) -> bool` +- `unwrap_ok_i32 : ((result i32 i32)) -> i32` +- `unwrap_err_i32 : ((result i32 i32)) -> i32` + +exp-33 fills out concrete err wrappers: + +- `is_err_i64 : ((result i64 i32)) -> bool` +- `unwrap_err_i64 : ((result i64 i32)) -> i32` +- `is_err_string : ((result string i32)) -> bool` +- `unwrap_err_string : ((result string i32)) -> i32` +- `is_err_f64 : ((result f64 i32)) -> bool` +- `unwrap_err_f64 : ((result f64 i32)) -> i32` + +exp-33 also adds concrete fallback helpers: + +- `unwrap_or_i32 : ((result i32 i32), i32) -> i32` +- `unwrap_or_i64 : ((result i64 i32), i64) -> i64` +- `unwrap_or_string : ((result string i32), string) -> string` +- `unwrap_or_f64 : ((result f64 i32), f64) -> f64` + +These are ordinary Slovo source functions using existing `std.result.is_ok`, +`std.result.is_err`, `std.result.unwrap_ok`, `std.result.unwrap_err`, `if`, +parameters, and explicit signatures. Each `unwrap_or_*` helper returns the ok +payload when the result is ok and returns the supplied fallback otherwise. +`unwrap_or_string` uses the existing `(result string i32)` `match` shape to +return the payload or fallback without requiring string-valued `if`. +exp-74 later broadens the same staged module with concrete source +constructors `ok_i32`, `err_i32`, `ok_i64`, `err_i64`, `ok_string`, +`err_string`, `ok_f64`, `err_f64`, `ok_bool`, and `err_bool`. exp-109 later +adds the concrete bridge helpers `ok_or_none_i32`, `ok_or_none_i64`, +`ok_or_none_string`, `ok_or_none_f64`, and `ok_or_none_bool`. + +At exp-33, Slovo did not yet promote automatic `std` imports, a repo-root +`std/` search path, or compiler-loaded standard library source. exp-45 later +promotes the explicit `std.result` source-search path while keeping automatic +imports deferred. + +This slice does not define new compiler-known `std.*` operation names, +standard-runtime catalog entries, replacement of compiler-known `std.*` calls +with source implementations, generic `std.result.map`, generic +`std.result.unwrap_or`, `std.result.and_then`, option helper names, new result +payload families, generic result helpers, stable standard-library APIs, stable +ABI/layout/ownership, manifest schema changes, or beta maturity. + +The exp-33 source fixture is: + +```text +std/result.slo +``` + +The normative exp-33 contract is +`.llm/EXP_33_STANDARD_RESULT_SOURCE_HELPERS_ALPHA.md`. + +### 4.9.18 exp-34 String Parse Bool Result Alpha + +Status: released experimental alpha Slovo contract. Matching Glagol exp-34 +gates are required before broader compiler-support claims. + +exp-34 promotes exactly one string parse compiler-known standard-runtime call: + +- `std.string.parse_bool_result : (string) -> (result bool i32)` + +The promoted call parses the entire input string as exactly one ASCII +lowercase boolean token. Accepted input is exactly `true` or `false`. Success +returns `ok true` for `true` and `ok false` for `false`. + +Ordinary parse failure returns `err 1`. The empty string, uppercase or +mixed-case text, leading or trailing whitespace, numeric text, locale-specific +text, Unicode lookalikes, suffixes, prefixes, and any other text all return +`err 1`. Ordinary parse failure does not trap. + +exp-34 extends the supported concrete result families only as needed for the +returned `(result bool i32)` parse value and existing compiler-known result +observation/extraction. It does not add source-authored `std/result.slo` bool +wrappers, generic results, stable result ABI/layout, or result helper behavior +beyond the explicitly supported concrete family. + +The exp-34 fixtures are byte-identical: + +```text +examples/supported/string-parse-bool-result.slo +examples/formatter/string-parse-bool-result.slo +``` + +The normative exp-34 contract is +`.llm/EXP_34_STRING_PARSE_BOOL_RESULT_ALPHA.md`. + +### 4.9.19 exp-35 Standard Result Bool Source Helpers Alpha + +Status: released experimental alpha Slovo contract. Matching Glagol exp-35 +source-check and formatter gates are required before broader compiler-support +claims. + +exp-35 extends staged `std/result.slo` with the next conservative +source-authored concrete result helper slice for the exp-34 returned +`(result bool i32)` family: + +- `is_ok_bool : ((result bool i32)) -> bool` +- `is_err_bool : ((result bool i32)) -> bool` +- `unwrap_ok_bool : ((result bool i32)) -> bool` +- `unwrap_err_bool : ((result bool i32)) -> i32` +- `unwrap_or_bool : ((result bool i32), bool) -> bool` + +These are ordinary Slovo source functions using existing compiler-supported +`std.result.is_ok`, `std.result.is_err`, `std.result.unwrap_ok`, +`std.result.unwrap_err`, `if`, parameters, and explicit signatures. +`unwrap_or_bool` returns the ok payload when the result is ok and returns the +supplied fallback otherwise. + +At exp-35, Slovo did not yet promote automatic `std` imports, a repo-root +`std/` search path, or compiler-loaded standard library source. exp-45 later +promotes the explicit `std.result` source-search path while keeping automatic +imports deferred. + +This slice does not define new compiler-known `std.*` operation names, +standard-runtime catalog entries, replacement of compiler-known `std.*` calls +with source implementations, source-authored generic result helpers, +generic `std.result.map`, generic `std.result.unwrap_or`, +`std.result.and_then`, option helper names, broad result payload families, +source constructors for `(result bool i32)`, general source `match` over +`(result bool i32)` beyond compiler-supported fixture flow, stable +standard-library APIs, stable ABI/layout/ownership, manifest schema changes, +or beta maturity. + +The exp-35 source fixture is: + +```text +std/result.slo +``` + +The normative exp-35 contract is +`.llm/EXP_35_STANDARD_RESULT_BOOL_SOURCE_HELPERS_ALPHA.md`. + +### 4.9.20 exp-36 Standard Option Source Helpers Alpha + +Status: released experimental alpha Slovo contract. Matching Glagol exp-36 +source-check and formatter gates are required before broader compiler-support +claims. + +exp-36 adds staged `std/option.slo` with a conservative source-authored helper +slice for the already supported `(option i32)` family: + +- `is_some_i32 : ((option i32)) -> bool` +- `is_none_i32 : ((option i32)) -> bool` +- `unwrap_some_i32 : ((option i32)) -> i32` +- `unwrap_or_i32 : ((option i32), i32) -> i32` + +exp-75 later adds `some_i32 : (i32) -> (option i32)` and +`none_i32 : () -> (option i32)`. exp-95 broadens the same staged module and +the same conservative option slice to concrete `(option i64)` with: + +- `some_i64 : (i64) -> (option i64)` +- `none_i64 : () -> (option i64)` +- `is_some_i64 : ((option i64)) -> bool` +- `is_none_i64 : ((option i64)) -> bool` +- `unwrap_some_i64 : ((option i64)) -> i64` +- `unwrap_or_i64 : ((option i64), i64) -> i64` + +exp-100 broadens the same staged module again to concrete `(option string)` +with: + +- `some_string : (string) -> (option string)` +- `none_string : () -> (option string)` +- `is_some_string : ((option string)) -> bool` +- `is_none_string : ((option string)) -> bool` +- `unwrap_some_string : ((option string)) -> string` +- `unwrap_or_string : ((option string), string) -> string` + +exp-102 broadens the same staged module again to concrete `(option f64)` and +`(option bool)` with: + +- `some_f64 : (f64) -> (option f64)` +- `none_f64 : () -> (option f64)` +- `is_some_f64 : ((option f64)) -> bool` +- `is_none_f64 : ((option f64)) -> bool` +- `unwrap_some_f64 : ((option f64)) -> f64` +- `unwrap_or_f64 : ((option f64), f64) -> f64` +- `some_bool : (bool) -> (option bool)` +- `none_bool : () -> (option bool)` +- `is_some_bool : ((option bool)) -> bool` +- `is_none_bool : ((option bool)) -> bool` +- `unwrap_some_bool : ((option bool)) -> bool` +- `unwrap_or_bool : ((option bool), bool) -> bool` + +exp-109 later adds concrete option-to-result bridge helpers for all current +concrete option families: + +- `some_or_err_i32 : ((option i32), i32) -> (result i32 i32)` +- `some_or_err_i64 : ((option i64), i32) -> (result i64 i32)` +- `some_or_err_f64 : ((option f64), i32) -> (result f64 i32)` +- `some_or_err_bool : ((option bool), i32) -> (result bool i32)` +- `some_or_err_string : ((option string), i32) -> (result string i32)` + +These are ordinary Slovo source functions using existing compiler-supported +`is_some`, `is_none`, `unwrap_some`, `ok`, `err`, `if`, parameters, and +explicit signatures. `unwrap_or_i32` returns the `some` payload when present +and returns the supplied fallback for `none`. Each `some_or_err_*` helper +returns `ok` with the option payload when present and returns `err err_code` +for `none`. + +Current Slovo keeps standard-library organization as flat staged `std/*.slo` +facades. exp-45 later promotes explicit `std.option` source search for this +module. A future `std.slo` aggregator/reexport layer, inspired by Zig's +facade/reexport approach, remains deferred until broader standard-library +source organization is promoted. + +This slice does not define new compiler-known `std.*` operation names, +standard-runtime catalog entries, automatic `std` imports, repo-root `std/` +search, compiler-loaded standard-library source, replacement of +compiler-known calls with source implementations, generic option helpers, +option payload families beyond `(option i32)`, `(option i64)`, `(option f64)`, +`(option bool)`, and `(option string)`, stable +standard-library APIs, stable ABI/layout/ownership, manifest schema changes, +or beta maturity. + +The exp-36 source fixture is: + +```text +std/option.slo +``` + +The normative exp-36 contract is +`.llm/EXP_36_STANDARD_OPTION_SOURCE_HELPERS_ALPHA.md`. + +### 4.9.20a exp-45 Standard Result And Option Source Search Alpha + +Status: released experimental alpha Slovo contract. Matching Glagol exp-45 +project-mode gates are required before broader compiler-support claims. + +exp-45 extends explicit standard-library source search to result and option +helpers: + +```slo +(import std.result (unwrap_or_i32 is_ok_bool unwrap_or_bool)) +(import std.option (is_some_i32 unwrap_or_i32)) +``` + +The external import names resolve to staged repo-root source files: + +```text +std/result.slo +std/option.slo +``` + +Both files keep flat module declarations, `(module result ...)` and +`(module option ...)`, and now carry explicit export lists for their promoted +helper names. + +The exp-45 fixtures are: + +```text +examples/projects/std-import-result/ +examples/projects/std-import-option/ +``` + +This slice does not define automatic standard-library imports, a `std.slo` +aggregator, aliases, glob imports, qualified member access, workspace/package +standard-library imports, package registry behavior, generic result helpers, +generic option helpers, broad standard-library APIs, stable ABI/layout/ +ownership, optimizer guarantees, benchmark thresholds, or beta maturity. + +The normative exp-45 contract is +`.llm/EXP_45_STANDARD_RESULT_OPTION_SOURCE_SEARCH_ALPHA.md`. + +### 4.9.20b exp-46 Workspace Standard Source Search Alpha + +Status: released experimental alpha Slovo contract. Matching Glagol exp-46 +workspace gates are required before broader compiler-support claims. + +exp-46 extends explicit standard-library source search from single projects to +workspace packages. Package source may import a staged standard module without +declaring a package dependency: + +```slo +(import std.option (is_some_i32 unwrap_or_i32)) +``` + +The exp-46 fixture is: + +```text +examples/workspaces/std-import-option/ +``` + +This slice does not define automatic standard-library imports, workspace +dependency syntax for std, package registry behavior, installed toolchain +stdlib paths, a `std.slo` aggregator, generic option helpers, broad +standard-library APIs, stable ABI/layout/ownership, optimizer guarantees, +benchmark thresholds, or beta maturity. + +The normative exp-46 contract is +`.llm/EXP_46_WORKSPACE_STANDARD_SOURCE_SEARCH_ALPHA.md`. + +### 4.9.20c exp-47 Standard Host Facade Source Search Alpha + +Status: released experimental alpha Slovo contract. Matching Glagol exp-47 +project-mode gates are required before broader compiler-support claims. + +exp-47 extends explicit standard-library source search to the host facade +modules staged in exp-37 and exp-38: + +```slo +(import std.time (monotonic_ms sleep_ms_zero)) +(import std.random (random_i32 random_i32_non_negative)) +(import std.env (get get_result)) +(import std.fs (read_text read_text_result write_text_status write_text_result)) +``` + +The external import names resolve to staged repo-root source files: + +```text +std/time.slo +std/random.slo +std/env.slo +std/fs.slo +``` + +The files keep flat module declarations, `(module time ...)`, +`(module random ...)`, `(module env ...)`, and `(module fs ...)`, and now +carry explicit export lists for their promoted facade names. + +The exp-47 fixtures are: + +```text +examples/projects/std-import-time/ +examples/projects/std-import-random/ +examples/projects/std-import-env/ +examples/projects/std-import-fs/ +``` + +This slice does not define automatic standard-library imports, a `std.slo` +aggregator, aliases, glob imports, qualified member access, workspace +dependency syntax for std, package registry behavior, installed toolchain +stdlib paths, broad host APIs, wall-clock/calendar/timezone APIs, +high-resolution timers, random ranges/seeds/bytes/floats/UUIDs, environment +mutation/enumeration, binary/directory/streaming/async filesystem APIs, rich +host error ADTs, stable ABI/layout/ownership, optimizer guarantees, benchmark +thresholds, or beta maturity. + +The normative exp-47 contract is +`.llm/EXP_47_STANDARD_HOST_FACADE_SOURCE_SEARCH_ALPHA.md`. + +### 4.9.20d exp-48 Standard Core Facade Source Search Alpha + +Status: released experimental alpha Slovo contract. Matching Glagol exp-48 +project-mode gates are required before broader compiler-support claims. + +exp-48 extends explicit standard-library source search to the pure core +facade modules: + +```slo +(import std.string (len concat parse_i32_result parse_i64_result parse_f64_result parse_bool_result)) +(import std.num (i32_to_i64 i32_to_f64 i64_to_f64 i64_to_i32_result f64_to_i32_result f64_to_i64_result i32_to_string i64_to_string f64_to_string)) +``` + +The external import names resolve to staged repo-root source files: + +```text +std/string.slo +std/num.slo +``` + +The files keep flat module declarations, `(module string ...)` and +`(module num ...)`, and now carry explicit export lists for their promoted +facade names. + +The exp-48 fixtures are: + +```text +examples/projects/std-import-string/ +examples/projects/std-import-num/ +``` + +This slice does not define automatic standard-library imports, a `std.slo` +aggregator, aliases, glob imports, qualified member access, package registry +behavior, installed toolchain stdlib paths, generic parse/format APIs, string +indexing/slicing/tokenization, mutable strings, string containers, broad +numeric casts, unsigned or narrower integer families, mixed numeric +arithmetic, decimal/char/byte types, stable ABI/layout/ownership, optimizer +guarantees, benchmark thresholds, or beta maturity. + +The normative exp-48 contract is +`.llm/EXP_48_STANDARD_CORE_FACADE_SOURCE_SEARCH_ALPHA.md`. + +### 4.9.20e exp-49 Standard IO Facade Source Search Alpha + +Status: released experimental alpha Slovo contract. Matching Glagol exp-49 +project-mode gates are required before broader compiler-support claims. + +exp-49 extends explicit standard-library source search to the staged IO +facade module: + +```slo +(import std.io (print_i32_zero print_i64_zero print_f64_zero print_string_zero print_bool_zero)) +``` + +The external import name resolves to the staged repo-root source file: + +```text +std/io.slo +``` + +The file keeps its flat module declaration, `(module io ...)`, and now carries +an explicit export list for its promoted facade names. + +The exp-49 fixture is: + +```text +examples/projects/std-import-io/ +``` + +Later staged-source expansions keep the same `std/io.slo` module path. +exp-73 adds `print_i32_value`, `print_i64_value`, `print_f64_value`, +`print_string_value`, and `print_bool_value` as source-authored print +wrappers that preserve the printed value. exp-111 adds a connected stdin +helper package: +`read_stdin_result`, `read_stdin_option`, `read_stdin_or`, +`read_stdin_i32_result`, `read_stdin_i32_option`, `read_stdin_i32_or_zero`, +`read_stdin_i32_or`, `read_stdin_i64_result`, `read_stdin_i64_option`, +`read_stdin_i64_or_zero`, `read_stdin_i64_or`, `read_stdin_f64_result`, +`read_stdin_f64_option`, `read_stdin_f64_or_zero`, `read_stdin_f64_or`, +`read_stdin_bool_result`, `read_stdin_bool_option`, +`read_stdin_bool_or_false`, and `read_stdin_bool_or`. These later helpers +remain ordinary source over the promoted `std.io.read_stdin_result` runtime +lane, the concrete `std.string.parse_*_result` helpers, and the exp-109 +`std.result.ok_or_none_*` bridge helpers, and do not add new runtime names. + +This slice does not define automatic standard-library imports, a `std.slo` +aggregator, aliases, glob imports, qualified member access, package registry +behavior, installed toolchain stdlib paths, new compiler-known IO names, +formatted output APIs, stdin expansion, terminal control, stream abstractions, +async IO, stable stdout/stderr buffering semantics beyond existing runtime +behavior, stable ABI/layout/ownership, optimizer guarantees, benchmark +thresholds, or beta maturity. + +The normative exp-49 contract is +`.llm/EXP_49_STANDARD_IO_FACADE_SOURCE_SEARCH_ALPHA.md`. + +### 4.9.20f exp-50 Installed Standard Library Discovery Alpha + +Status: released experimental alpha Slovo contract. Matching Glagol exp-50 +installed-discovery gates are required before broader toolchain-support +claims. + +exp-50 extends explicit standard-library source search to installed +toolchain layouts. Project and workspace source still imports staged standard +modules explicitly: + +```slo +(import std.math (abs_i32)) +``` + +The staged source modules remain flat files under `std/*.slo`. A compiler may +discover those files from an installed standard-library directory such as: + +```text +share/slovo/std +``` + +This slice does not define automatic standard-library imports, a `std.slo` +aggregator, aliases, glob imports, qualified member access, package registry +behavior, lockfiles, package std dependencies, a stable package manager, +stable install layout guarantees beyond this alpha discovery candidate, new +compiler-known runtime names, broad standard-library APIs, stable +ABI/layout/ownership, optimizer guarantees, benchmark thresholds, or beta +maturity. + +The normative exp-50 contract is +`.llm/EXP_50_INSTALLED_STANDARD_LIBRARY_DISCOVERY_ALPHA.md`. + +### 4.9.20g exp-51 Standard Library Path List Alpha + +Status: released experimental alpha Slovo contract. Matching Glagol exp-51 +path-list gates are required before broader package-resolution claims. + +exp-51 refines explicit standard-library source search by allowing +`SLOVO_STD_PATH` to name an ordered OS path list of standard-library roots. +A tool resolves a requested `std.` import from the first listed root +that contains the corresponding module file. + +This slice supports partial override roots during standard-library +development. It does not define automatic standard-library imports, a +`std.slo` aggregator, aliases, glob imports, qualified member access, package +registry behavior, lockfiles, package std dependencies, semantic version +solving, stable package manager behavior, broad standard-library APIs, stable +ABI/layout/ownership, optimizer guarantees, benchmark thresholds, or beta +maturity. + +The normative exp-51 contract is +`.llm/EXP_51_STANDARD_LIBRARY_PATH_LIST_ALPHA.md`. + +### 4.9.20h exp-52 Standard Process Facade Source Search Alpha + +Status: released experimental alpha Slovo contract. Matching Glagol exp-52 +project-mode gates are required before broader compiler-support claims. + +exp-52 extends explicit standard-library source search to the staged process +facade module: + +```slo +(import std.process (argc arg arg_result has_arg)) +``` + +The external import name resolves to the staged repo-root source file: + +```text +std/process.slo +``` + +The file keeps its flat module declaration, `(module process ...)`, and +exports wrappers over existing `std.process.argc`, `std.process.arg`, and +`std.process.arg_result`, plus the source-authored `has_arg` helper. + +The exp-52 fixture is: + +```text +examples/projects/std-import-process/ +``` + +This slice does not define automatic standard-library imports, a `std.slo` +aggregator, aliases, glob imports, qualified member access, package registry +behavior, lockfiles, package std dependencies, new compiler-known process +runtime names, process spawning, exit/status control, current-directory APIs, +signal handling, stable ABI/layout/ownership, optimizer guarantees, benchmark +thresholds, or beta maturity. + +The normative exp-52 contract is +`.llm/EXP_52_STANDARD_PROCESS_FACADE_SOURCE_SEARCH_ALPHA.md`. + +### 4.9.20i exp-53 Standard CLI Source Facade Alpha + +Status: released experimental alpha Slovo contract. Matching Glagol exp-53 +project-mode gates are required before broader compiler-support claims. + +exp-53 extends explicit standard-library source search to the staged CLI +facade module: + +```slo +(import std.cli (arg_text_result arg_i32_result arg_i32_or_zero)) +``` + +The external import name resolves to the staged repo-root source file: + +```text +std/cli.slo +``` + +The file keeps its flat module declaration, `(module cli ...)`, imports +`std.process` and `std.string` as ordinary standard source modules, and +exports `arg_text_result`, `arg_i32_result`, and `arg_i32_or_zero`. + +The exp-53 fixture is: + +```text +examples/projects/std-import-cli/ +``` + +This slice does not define automatic standard-library imports, a `std.slo` +aggregator, aliases, glob imports, qualified member access, package registry +behavior, lockfiles, package std dependencies, new compiler-known runtime +names, shell parsing, option/flag parsing, subcommands, environment-backed +configuration, stable CLI framework APIs, stable ABI/layout/ownership, +optimizer guarantees, benchmark thresholds, or beta maturity. + +The normative exp-53 contract is +`.llm/EXP_53_STANDARD_CLI_SOURCE_FACADE_ALPHA.md`. + +### 4.9.20j exp-54 Standard CLI Typed Arguments Alpha + +Status: released experimental alpha Slovo contract. Matching Glagol exp-54 +project-mode gates are required before broader compiler-support claims. + +exp-54 extends the staged CLI facade helper list: + +```slo +(import std.cli (arg_text_result arg_i32_result arg_i32_or_zero arg_i64_result arg_i64_or_zero arg_f64_result arg_f64_or_zero arg_bool_result arg_bool_or_false)) +``` + +The external import name still resolves to: + +```text +std/cli.slo +``` + +The file remains ordinary source and continues to import `std.process` and +`std.string`. It adds typed result and fallback helpers for the current +`i32`, `i64`, `f64`, and `bool` parse-result families. + +The exp-54 fixture remains: + +```text +examples/projects/std-import-cli/ +``` + +This slice does not define automatic standard-library imports, a `std.slo` +aggregator, aliases, glob imports, qualified member access, package registry +behavior, lockfiles, package std dependencies, new compiler-known runtime +names, shell parsing, option/flag parsing, subcommands, environment-backed +configuration, stable CLI framework APIs, stable ABI/layout/ownership, +optimizer guarantees, benchmark thresholds, or beta maturity. + +The normative exp-54 contract is +`.llm/EXP_54_STANDARD_CLI_TYPED_ARGUMENTS_ALPHA.md`. + +### 4.9.20k exp-55 Result F64 Bool Source Flow Alpha + +Status: released experimental alpha Slovo contract. Matching Glagol exp-55 +checker, formatter, test-runner, and LLVM gates are required before broader +compiler-support claims. + +exp-55 promotes source constructors for: + +```slo +(ok f64 i32 1.5) +(err f64 i32 1) +(ok bool i32 true) +(err bool i32 1) +``` + +Source `match` may bind the `f64` or `bool` ok payload and the `i32` err +payload for those concrete result families: + +```slo +(match value + ((ok payload) + payload) + ((err code) + fallback)) +``` + +The exp-55 fixture is: + +```text +examples/supported/result-f64-bool-match.slo +``` + +This slice does not define generic result types, result payload families +beyond the already promoted concrete families, generic `map`/`and_then`, +exception handling, stable ABI/layout/ownership, optimizer guarantees, +benchmark thresholds, or beta maturity. + +The normative exp-55 contract is +`.llm/EXP_55_RESULT_F64_BOOL_SOURCE_FLOW_ALPHA.md`. + +### 4.9.20l exp-56 Integer Remainder Alpha + +Status: released experimental alpha Slovo contract. Matching Glagol exp-56 +parser, checker, formatter, test-runner, LLVM, and standard-source gates are +required before broader compiler-support claims. + +exp-56 promotes the `%` binary operator for same-width signed integer +operands: + +```slo +(% 17 5) +(% -17 5) +(% 42i64 5i64) +(% -17i64 5i64) +``` + +The result type is `i32` for `i32` operands and `i64` for `i64` operands. +The sign behavior follows signed remainder semantics: the remainder carries +the dividend sign for negative dividends. Remainder by zero is a test-runner +runtime error and remains an invalid program behavior for hosted code, just +like division by zero in the current numeric slice. + +exp-56 also extends `std/math.slo` with source-authored helpers: + +```text +rem_i32, is_even_i32, is_odd_i32 +rem_i64, is_even_i64, is_odd_i64 +``` + +The exp-56 fixture is: + +```text +examples/supported/integer-remainder.slo +examples/formatter/integer-remainder.slo +``` + +This slice does not define floating-point remainder, Euclidean modulo, +unsigned arithmetic, bit operations, generic math, mixed numeric arithmetic, +stable ABI/layout/ownership, optimizer guarantees, benchmark thresholds, or +beta maturity. + +The normative exp-56 contract is `.llm/EXP_56_INTEGER_REMAINDER_ALPHA.md`. + +### 4.9.20m exp-57 Integer Bitwise Alpha + +Status: released experimental alpha Slovo contract. Matching Glagol exp-57 +parser, checker, formatter, test-runner, LLVM, and standard-source gates are +required before broader compiler-support claims. + +exp-57 promotes explicit bitwise binary heads for same-width signed integer +operands: + +```slo +(bit_and 6 3) +(bit_or 4 2) +(bit_xor 7 3) +(bit_and 6i64 3i64) +(bit_or 4i64 2i64) +(bit_xor 7i64 3i64) +``` + +The result type is `i32` for `i32` operands and `i64` for `i64` operands. +The heads are intentionally named forms rather than symbolic operators so +they remain clear in the S-expression surface. + +exp-57 also extends `std/math.slo` with source-authored helpers: + +```text +bit_and_i32, bit_or_i32, bit_xor_i32 +bit_and_i64, bit_or_i64, bit_xor_i64 +``` + +The exp-57 fixtures are: + +```text +examples/supported/integer-bitwise.slo +examples/formatter/integer-bitwise.slo +``` + +This slice does not define shifts, bit-not, unsigned arithmetic, +bit-width-specific integer families, floating-point bitwise operations, +generic math, mixed numeric arithmetic, stable ABI/layout/ownership, +optimizer guarantees, benchmark thresholds, or beta maturity. + +The normative exp-57 contract is `.llm/EXP_57_INTEGER_BITWISE_ALPHA.md`. + +### 4.9.20n exp-58 Boolean Logic Alpha + +Status: released experimental alpha Slovo contract. Matching Glagol exp-58 +parser, formatter, checker, test-runner, LLVM, and fixture gates are required +before broader compiler-support claims. + +exp-58 promotes boolean logic forms: + +```slo +(and left right) +(or left right) +(not value) +``` + +`and` and `or` short-circuit and are specified as source forms lowered +through existing `if` semantics: + +- `(and left right)` evaluates `right` only when `left` is true. +- `(or left right)` evaluates `right` only when `left` is false. +- `(not value)` returns `false` for true input and `true` for false input. + +The exp-58 fixtures are: + +```text +examples/supported/boolean-logic.slo +examples/formatter/boolean-logic.slo +``` + +This slice does not define truthiness, variadic boolean operators, pattern +guards, macro expansion, stable ABI/layout/ownership, optimizer guarantees, +benchmark thresholds, or beta maturity. + +The normative exp-58 contract is `.llm/EXP_58_BOOLEAN_LOGIC_ALPHA.md`. + +### 4.9.20p exp-119 Benchmark Suite Extension And Whitepaper Refresh Alpha + +Status: released experimental alpha Slovo contract. Matching Glagol exp-119 +benchmark-suite extension, publication refresh, and verification gates are +required before broader compiler-support claims. + +exp-119 does not add new source syntax or new type forms. It keeps the +exp-118 language surface unchanged while widening the current benchmark/ +publication baseline from three kernels to five: + +- `math-loop` +- `branch-loop` +- `parse-loop` +- `array-index-loop` +- `string-eq-loop` + +The exp-119 publication fixtures are: + +```text +docs/SLOVO_WHITEPAPER.md +docs/SLOVO_WHITEPAPER.pdf +docs/SLOVO_MANIFEST.pdf +WHITEPAPER.pdf +``` + +This slice does not define language-surface changes, standard-library API +changes, compiler-known runtime names, optimizer guarantees, benchmark +thresholds, cross-machine performance claims, stable ABI/layout/ownership, or +beta maturity. + +The normative exp-119 contract is +`.llm/EXP_119_BENCHMARK_SUITE_EXTENSION_AND_WHITEPAPER_REFRESH_ALPHA.md`. + +### 4.9.20r exp-121 Non-Recursive Struct Enum Payloads Alpha + +Status: released experimental alpha Slovo contract. Matching Glagol exp-121 +gates are required before broader compiler-support claims. + +exp-121 promotes unary enum payload variants whose payload type is a current +known non-recursive struct type. Where payload variants are present in one +enum, those payload variants still share the same payload type. + +Qualified constructors may build those enum values from matching struct-valued +expressions. Immutable enum values carrying those payloads may flow through +locals, parameters, returns, calls, tests, and `main`. Exhaustive `match` +continues to bind exactly one immutable payload name per payload arm, and the +bound struct payload reuses existing field access behavior, including checked +indexing through array-bearing struct fields where already promoted by +exp-120. + +This slice does not define enum equality requirements for struct-payload +enums, direct array/vec/option/result payloads, multiple payload fields, +recursive/cyclic payloads, mutation, generics, stable ABI/layout/ownership +claims, or beta maturity. + +The exp-121 fixtures are: + +```text +examples/supported/enum-payload-structs.slo +examples/formatter/enum-payload-structs.slo +``` + +The normative exp-121 contract is +`.llm/EXP_121_NON_RECURSIVE_STRUCT_ENUM_PAYLOADS_ALPHA.md`. + +### 4.9.20t exp-123 Owned Vector Benchmark Suite Extension And Whitepaper Refresh Alpha + +Status: released experimental alpha Slovo contract. Matching Glagol exp-123 +benchmark-suite extension, publication refresh, and verification gates are +required before broader compiler-support claims. + +exp-123 does not add new source syntax or new type forms. It keeps the +exp-121 language surface unchanged while widening the current benchmark/ +publication baseline from seven kernels to nine: + +- `math-loop` +- `branch-loop` +- `parse-loop` +- `array-index-loop` +- `string-eq-loop` +- `array-struct-field-loop` +- `enum-struct-payload-loop` +- `vec-i32-index-loop` +- `vec-string-eq-loop` + +The two additional kernels are publication evidence for the already promoted +runtime-owned vector lanes over `i32` and `string`. + +The exp-123 publication fixtures are: + +```text +docs/SLOVO_WHITEPAPER.md +docs/SLOVO_WHITEPAPER.pdf +docs/SLOVO_MANIFEST.pdf +WHITEPAPER.pdf +``` + +This slice does not define language-surface changes, standard-library API +changes, compiler-known runtime names, optimizer guarantees, benchmark +thresholds, cross-machine performance claims, stable ABI/layout/ownership, or +beta maturity. + +The normative exp-123 contract is +`.llm/EXP_123_OWNED_VECTOR_BENCHMARK_SUITE_EXTENSION_AND_WHITEPAPER_REFRESH_ALPHA.md`. + +### 4.9.20s exp-122 Composite Data Benchmark Suite Extension And Whitepaper Refresh Alpha + +Status: released experimental alpha Slovo contract. Matching Glagol exp-122 +benchmark-suite extension, publication refresh, and verification gates are +required before broader compiler-support claims. + +exp-122 does not add new source syntax or new type forms. It keeps the +exp-121 language surface unchanged while widening the current benchmark/ +publication baseline from five kernels to seven: + +- `math-loop` +- `branch-loop` +- `parse-loop` +- `array-index-loop` +- `string-eq-loop` +- `array-struct-field-loop` +- `enum-struct-payload-loop` + +The two additional kernels are composite-data publication evidence for the +already promoted exp-120 fixed-array struct-field surface and exp-121 unary +non-recursive struct enum payload surface. + +The exp-122 publication fixtures are: + +```text +docs/SLOVO_WHITEPAPER.md +docs/SLOVO_WHITEPAPER.pdf +docs/SLOVO_MANIFEST.pdf +WHITEPAPER.pdf +``` + +This slice does not define language-surface changes, standard-library API +changes, compiler-known runtime names, optimizer guarantees, benchmark +thresholds, cross-machine performance claims, stable ABI/layout/ownership, or +beta maturity. + +The normative exp-122 contract is +`.llm/EXP_122_COMPOSITE_DATA_BENCHMARK_SUITE_EXTENSION_AND_WHITEPAPER_REFRESH_ALPHA.md`. + +### 4.9.20q exp-120 Fixed Array Struct Fields Alpha + +Status: released experimental alpha Slovo contract. Matching Glagol exp-120 +gates are required before broader compiler-support claims. + +exp-120 promotes direct immutable struct field declarations whose field types +are exactly the current promoted fixed immutable array families: + +- `(array i32 N)` +- `(array i64 N)` +- `(array f64 N)` +- `(array bool N)` +- `(array string N)` + +where `N` is a positive literal length. + +Struct constructors may initialize those fields from matching fixed-array +expressions. Immutable struct values carrying those fields may flow through +locals, parameters, returns, and calls. Field access returns the declared +fixed-array type, and the existing checked `index` form may consume that field +access result with `i32` index expressions. + +This slice does not define zero-length arrays, mutable arrays, element +mutation, field mutation, array equality, array printing, nested arrays, +arrays of unsupported element kinds, slices, generics, stable +ABI/layout/ownership claims, or beta maturity. + +The exp-120 fixtures are: + +```text +examples/supported/array-struct-fields.slo +examples/formatter/array-struct-fields.slo +``` + +The normative exp-120 contract is +`.llm/EXP_120_FIXED_ARRAY_STRUCT_FIELDS_ALPHA.md`. + +### 4.9.20o exp-59 Hosted Build Optimization And Benchmark Publication Alpha + +Status: released experimental alpha Slovo contract. Matching Glagol exp-59 +hosted-build, publication, and verification gates are required before broader +compiler-support claims. + +exp-59 does not add new source syntax or new type forms. It keeps the exp-58 +language surface unchanged while recording the paired Glagol hosted-build +optimization and an earlier benchmark/publication baseline. + +The exp-59 publication fixtures are: + +```text +docs/SLOVO_WHITEPAPER.md +docs/SLOVO_WHITEPAPER.pdf +docs/SLOVO_MANIFEST.pdf +WHITEPAPER.pdf +``` + +This slice does not define language-surface changes, standard-library API +changes, optimizer guarantees, benchmark thresholds, cross-machine +performance claims, stable ABI/layout/ownership, or beta maturity. + +The normative exp-59 contract is +`.llm/EXP_59_HOSTED_BUILD_OPTIMIZATION_AND_BENCHMARK_PUBLICATION_ALPHA.md`. + +### 4.9.21 exp-37 Standard Time Source Facade Alpha + +Status: released experimental alpha Slovo contract. Matching Glagol exp-37 +source-check and formatter gates are required before broader compiler-support +claims. + +exp-37 adds staged `std/time.slo` with a conservative source facade over the +already released exp-8 time calls: + +- `monotonic_ms : () -> i32` +- `sleep_ms_zero : () -> i32` + +`monotonic_ms` returns `std.time.monotonic_ms`. `sleep_ms_zero` calls +`std.time.sleep_ms 0` and then returns `0`. The integer-returning sleep +wrapper is intentionally narrow and zero-duration because user-defined +unit-return functions are not generally supported today and positive-duration +timing assertions remain deferred. + +Current Slovo keeps standard-library organization as flat staged `std/*.slo` +facades. A future `std.slo` aggregator/reexport layer, inspired by Zig's flat +standard facade discipline, waits until Slovo has promoted import/search +semantics for standard-library source. + +This slice does not define new compiler-known `std.*` operation names, +standard-runtime catalog entries, automatic `std` imports, repo-root `std/` +search, compiler-loaded standard-library source, replacement of +compiler-known calls with source implementations, wall-clock/calendar/timezone +APIs, high-resolution timers, async timers, cancellation, scheduling +guarantees, stable standard-library APIs, stable ABI/layout/ownership, +manifest schema changes, or beta maturity. + +The exp-37 source fixture is: + +```text +std/time.slo +``` + +The normative exp-37 contract is +`.llm/EXP_37_STANDARD_TIME_SOURCE_FACADE_ALPHA.md`. + +### 4.9.22 exp-38 Standard Host Source Facades Alpha + +Status: released experimental alpha Slovo contract. Matching Glagol exp-38 +source-check and formatter gates are required before broader compiler-support +claims. + +exp-38 adds staged host source facades over already released random, +environment, and text filesystem calls: + +- `std/random.slo` +- `std/env.slo` +- `std/fs.slo` + +The source facade functions are: + +- `random_i32 : () -> i32` +- `random_i32_non_negative : () -> bool` +- `get : (string) -> string` +- `get_result : (string) -> (result string i32)` +- `read_text : (string) -> string` +- `read_text_result : (string) -> (result string i32)` +- `write_text_status : (string, string) -> i32` +- `write_text_result : (string, string) -> (result i32 i32)` + +Current Slovo keeps standard-library organization as flat staged `std/*.slo` +facades. A future `std.slo` aggregator/reexport layer, inspired by Zig's flat +standard facade discipline, waits until Slovo has promoted import/search +semantics for standard-library source. + +This slice does not define new compiler-known `std.*` operation names, +standard-runtime catalog entries, automatic `std` imports, repo-root `std/` +search, compiler-loaded standard-library source, replacement of +compiler-known calls with source implementations, seed/range/bytes/float/ +UUID/crypto random APIs, environment mutation/enumeration, platform-specific +error codes, rich host error ADTs, binary file APIs, directory traversal, +streaming file IO, async file IO, richer filesystem metadata APIs, stable +standard-library APIs, stable ABI/layout/ownership, manifest schema changes, +or beta maturity. + +The normative exp-38 contract is +`.llm/EXP_38_STANDARD_HOST_SOURCE_FACADES_ALPHA.md`. + +### 4.9.23 exp-39 Standard Math Extensions And Benchmark Scaffold Alpha + +Status: released experimental alpha Slovo contract. Matching Glagol exp-39 +source-check, formatter, benchmark scaffold, and promotion gates are required +before broader compiler-support claims. + +exp-39 extends staged `std/math.slo` with ordinary source-authored helpers +over existing numeric primitives. The exp-32 `abs`, `min`, `max`, `clamp`, and +`square` helper families remain. + +exp-39 adds these helper families for `i32`, `i64`, and finite `f64`: + +- `neg_* : (T) -> T` +- `cube_* : (T) -> T` +- `is_zero_* : (T) -> bool` +- `is_positive_* : (T) -> bool` +- `is_negative_* : (T) -> bool` +- `in_range_* : (T, T, T) -> bool` + +The concrete promoted names are `neg_i32`, `cube_i32`, `is_zero_i32`, +`is_positive_i32`, `is_negative_i32`, `in_range_i32`, `neg_i64`, +`cube_i64`, `is_zero_i64`, `is_positive_i64`, `is_negative_i64`, +`in_range_i64`, `neg_f64`, `cube_f64`, `is_zero_f64`, `is_positive_f64`, +`is_negative_f64`, and `in_range_f64`. + +These are ordinary Slovo source functions using existing same-type arithmetic +and comparison, `if`, literals, calls, and explicit signatures. `cube_*` is +defined in terms of multiplication through `square_*`. `in_range_*` is +inclusive and returns false when the lower bound check fails; it does not +validate or reorder bounds. + +The matching Glagol `benchmarks/math-loop/` scaffold is tooling evidence only. +It compares local Slovo executable timing against C, Rust, and Python on the +same machine with a shared deterministic checksum. It does not define a Slovo +semantic feature, optimizer guarantee, or public performance threshold. + +This slice does not define new compiler-known `std.*` operation names, +standard-runtime catalog entries, automatic `std` imports, repo-root `std/` +search, compiler-loaded standard-library source, replacement of +compiler-known calls with source implementations, trigonometry, `sqrt`, `pow`, +logarithms, modulo, bit operations, unsigned or narrower integers, `f32`, +generic math, overloads, traits, mixed numeric arithmetic, numeric containers, +stable standard-library APIs, stable ABI/layout/ownership, optimizer +guarantees, benchmark thresholds, manifest schema changes, or beta maturity. + +The exp-39 source fixture is: + +```text +std/math.slo +``` + +The normative exp-39 contract is +`.llm/EXP_39_STANDARD_MATH_EXTENSIONS_AND_BENCHMARK_SCAFFOLD_ALPHA.md`. + +## 4.10 exp-5 Local Packages And Workspaces + +Status: current experimental compiler-supported contract after matching Glagol +exp-5 gates. + +exp-5 promotes only a closed local package/workspace hygiene slice: + +- workspace `slovo.toml` manifests with explicit local members +- package `slovo.toml` manifests with package name/version metadata +- local path dependencies between workspace packages +- package-qualified imports for dependency modules +- deterministic package graph ordering +- artifact-manifest recording of the local package graph + +The minimal exp-5 fixture is: + +```text +examples/workspaces/exp-5-local/ +``` + +exp-5 explicitly defers remote registries, version solving, broad lockfiles, +build scripts, generated code, package publishing, semver compatibility, +package-level generics, package re-exports, package archives, optional/dev/ +target dependencies, registry authentication, and cross-package ABI stability. + +The normative exp-5 contract is `.llm/EXP_5_LOCAL_PACKAGES_ALPHA.md`. + +## 4.11 exp-6 C FFI Scalar Imports Alpha + +Status: current experimental compiler-supported contract after matching Glagol +exp-6 gates. + +exp-6 promotes only top-level imported C function declarations with `i32` +parameters and `i32` or internal `unit` returns: + +```slo +(import_c c_add ((lhs i32) (rhs i32)) -> i32) +``` + +Calls to imported C functions must occur inside lexical `(unsafe ...)`. +Hosted builds use explicit local C source link inputs for the fixture, and +artifact manifests record source module, import name, C symbol name, ordered +parameter types, return type, link inputs, and an experimental fixture ABI +marker. + +The minimal exp-6 fixture is: + +```text +examples/ffi/exp-6-c-add/ +``` + +exp-6 explicitly defers pointer types, allocation/deallocation, raw unsafe +head execution, ownership/lifetime rules, C exports, callbacks, headers, +libraries, broad linker configuration, foreign globals, foreign structs, +unions, enums, stable ABI, and stable layout. + +The normative exp-6 contract is `.llm/EXP_6_C_FFI_SCALAR_IMPORTS_ALPHA.md`. + +## 4.12 exp-7 Test Selection And Test-Run Metadata Alpha + +Status: current experimental compiler-supported contract after matching Glagol +exp-7 gates. + +exp-7 promotes only test selection and richer test-run metadata: + +```text +glagol test --filter +glagol --run-tests --filter +``` + +Filtering is a deterministic, case-sensitive substring match against decoded +test display names. Selected tests execute in existing order. Non-selected +tests are skipped before evaluation and counted as skipped. Zero matches is a +successful run. Test reports and artifact manifests expose total discovered, +selected, passed, failed, skipped, and optional filter metadata. + +exp-7 explicitly defers LSP, editor protocols, debug metadata, source maps, +SARIF, watch/daemon protocols, documentation comments, lint categories, +benchmarks, test tags/groups/retries/sharding/parallelism, and new source +language syntax. + +The normative exp-7 contract is `.llm/EXP_7_TEST_SELECTION_ALPHA.md`. + +## 4.13 exp-8 Host Time And Sleep Alpha + +Status: released experimental compiler-supported contract after matching +Slovo and Glagol exp-8 gates, 2026-05-18. + +exp-8 promotes exactly two compiler-known standard-runtime operations: + +```text +std.time.monotonic_ms: () -> i32 +std.time.sleep_ms: (i32) -> unit +``` + +The source call forms are ordinary calls: + +```slo +(std.time.monotonic_ms) +(std.time.sleep_ms ms) +``` + +`std.time.monotonic_ms` returns host monotonic elapsed time in milliseconds as +a non-negative `i32`. The epoch is implementation-owned and is not wall-clock +time. If host monotonic time is unavailable, the implementation may trap with: + +```text +slovo runtime error: monotonic time unavailable +``` + +`std.time.sleep_ms` accepts a non-negative `i32` duration in milliseconds and +returns builtin `unit`. `0` is valid and is a no-op/yield-style sleep. +Negative runtime values trap with: + +```text +slovo runtime error: sleep_ms negative duration +``` + +If a non-negative host sleep fails, the implementation may trap with: + +```text +slovo runtime error: sleep failed +``` + +The promoted names are compiler-known. They require no import, do not name user +modules, do not create package dependencies, and are not stable C ABI symbols +or stable runtime helper symbols. The exact promoted names are reserved from +user function, export, import, local, and parameter shadowing. + +The exp-8 test-runner contract is intentionally narrow: `sleep_ms 0` is +deterministic and returns `unit`; positive sleeps are not required in +conformance tests; `monotonic_ms` tests are structural or assert only that an +executed host value is non-negative. exp-8 tests must not assert elapsed-time +precision, wall-clock correspondence, scheduling behavior, or high-resolution +timing. + +Artifact manifests record `std.time.monotonic_ms` and `std.time.sleep_ms` +usage only if existing manifest patterns already record standard-runtime usage +metadata. Otherwise exp-8 defers extra time-specific manifest metadata. + +exp-8 explicitly defers threads, tasks, channels, async, cancellation, actors, +shared memory, data-race freedom, scheduling guarantees, timers, wall-clock +time, calendar dates, time zones, high-resolution timers, signal handling, +source language syntax, stable ABI/layout, and stable runtime helper symbols. + +The normative exp-8 release contract is `.llm/EXP_8_HOST_TIME_SLEEP_ALPHA.md`. + +## 4.14 exp-9 Reliability Performance And Ecosystem Hardening + +Status: released experimental compiler-supported contract after matching +Slovo and Glagol exp-9 gates, 2026-05-18. + +exp-9 is a hardening-only target over the released exp-8 baseline. It accepts +no new source syntax, type-system behavior, standard-runtime names, manifest +schema version, runtime capabilities, package features, ABI/layout promises, +public performance thresholds, or beta maturity claim. + +The exp-9 release requires compatibility inventory and migration notes for +`v2.0.0-beta.1` and exp-1 through exp-8, property/fuzz-style parser, +formatter, diagnostic, project-graph, and runtime API coverage where +feasible, source-reachable panic audit and disposition, benchmark fixtures or +documented benchmark smoke, and release review confirming no accidental +language or stable-ABI expansion. + +The normative exp-9 release contract is +`.llm/EXP_9_RELIABILITY_PERFORMANCE_ECOSYSTEM_HARDENING.md`. + +## 4.15 exp-10 Result-Based Host Errors Alpha + +Status: released experimental compiler-supported contract after matching +Slovo and Glagol exp-10 gates, 2026-05-18. exp-10 is not a beta maturity +claim. + +exp-10 promotes exactly one new concrete result family: + +```text +(result string i32) +``` + +This family mirrors the existing `(result i32 i32)` support as narrowly as +possible: immutable locals, parameters, returns, calls returning result +values, `is_ok`, `is_err`, `unwrap_ok`, `unwrap_err`, exhaustive `match` arms, +and formatter support. It does not promote generic result types. + +exp-10 promotes exactly four additive compiler-known host calls: + +```text +std.process.arg_result: (i32) -> (result string i32) +std.env.get_result: (string) -> (result string i32) +std.fs.read_text_result: (string) -> (result string i32) +std.fs.write_text_result: (string string) -> (result i32 i32) +``` + +The only ordinary host failure code promised by exp-10 is `err 1`. +`std.fs.write_text_result` returns `ok 0` on success and `err 1` on ordinary +host write failure. The string-returning calls return `ok` with an immutable +runtime-provided string on success and `err 1` on ordinary host failure. + +The exp-3 calls remain unchanged. `std.process.arg`, `std.env.get`, +`std.fs.read_text`, and `std.fs.write_text` keep their exp-3 trap, +empty-string, and integer-status behavior. + +exp-10 fixtures are: + +```text +examples/supported/host-io-result.slo +examples/formatter/host-io-result.slo +``` + +Artifact manifests record exp-10 standard-runtime usage only if existing +manifest patterns already record standard-runtime usage metadata. Otherwise no +exp-10-specific manifest field or schema version change is promoted. + +exp-10 explicitly defers `result string Error`, general host error ADTs, +platform-specific error codes, error messages, result equality, result +printing, mapping, nested results, result containers, new host API families, +stable ABI/layout/helper symbols, and beta maturity. + +The normative exp-10 release contract is +`.llm/EXP_10_RESULT_BASED_HOST_ERRORS_ALPHA.md`. + +## 4.16 exp-11 Basic Randomness Alpha + +Status: released experimental compiler-supported contract after matching +Glagol exp-11 gates passed. exp-11 is not a beta maturity claim. + +exp-11 promotes exactly one compiler-known standard-runtime operation: + +```text +std.random.i32: () -> i32 +``` + +Source call form: + +```slo +(std.random.i32) +``` + +`std.random.i32` returns a non-negative implementation-owned pseudo-random or +host-random `i32` suitable only for basic command-line program use. No seed +API, distribution, entropy source, sequence stability, cross-platform +reproducibility, or cryptographic/security suitability is promised. + +If the runtime cannot produce a value, it must trap with the exact message: + +```text +slovo runtime error: random i32 unavailable +``` + +The test runner must be deterministic enough for Glagol tests. It may return +an implementation-owned non-negative sample or sequence, and fixtures may +assert only structural properties such as non-negativity. + +exp-11 release fixtures are: + +```text +examples/supported/random.slo +examples/formatter/random.slo +``` + +Artifact manifests record exp-11 standard-runtime usage only if existing +manifest patterns already record standard-runtime usage metadata. Otherwise no +randomness-specific manifest field or schema version change is promoted. + +exp-11 explicitly defers seed APIs, crypto/security promises, bytes APIs, +ranges or bounds arguments, floats, random strings, UUIDs, broader +`std.random.*` names, randomness-specific manifest fields, stable +ABI/layout/helper symbols, and beta maturity. + +The normative exp-11 release contract is +`.llm/EXP_11_BASIC_RANDOMNESS_ALPHA.md`. + +## 4.17 exp-12 Standard Input Result Alpha + +Status: released experimental compiler-supported contract after matching +Glagol exp-12 gates passed, 2026-05-18. exp-12 is not a beta maturity claim. + +exp-12 promotes exactly one compiler-known standard-runtime operation: + +```text +std.io.read_stdin_result: () -> (result string i32) +``` + +Source call form: + +```slo +(std.io.read_stdin_result) +``` + +`std.io.read_stdin_result` reads the remaining standard input as text and +returns `(ok string i32 text)` on success. Ordinary EOF with no bytes is +success with the empty string. Ordinary host/input failure returns +`(err string i32 1)`. The only ordinary failure code promised by exp-12 is +`err 1`. + +The test runner must be deterministic enough for Glagol tests. It may return +an implementation-owned fixed input string as `ok`, including the empty +string. + +exp-12 release fixtures are: + +```text +examples/supported/stdin-result.slo +examples/formatter/stdin-result.slo +``` + +Existing exp-3 and exp-10 host calls stay unchanged. Artifact manifests record +exp-12 standard-runtime usage only if existing manifest patterns already +record standard-runtime usage metadata. Otherwise no stdin-specific manifest +field or schema version change is promoted. + +exp-12 explicitly defers trap-based `std.io.read_stdin`, line iteration, +prompt APIs, terminal mode, binary stdin, streaming, async, encoding or +Unicode promises beyond existing string bytes, stable ABI/layout/helper +symbols, manifest schema changes, and beta maturity. + +The normative exp-12 release contract is +`.llm/EXP_12_STDIN_RESULT_ALPHA.md`. + +## 4.18 exp-13 String Parse I32 Result Alpha + +Status: released experimental compiler-supported contract after matching +Glagol exp-13 gates passed, 2026-05-18. exp-13 is not a beta maturity claim. + +exp-13 promotes exactly one compiler-known standard-runtime operation: + +```text +std.string.parse_i32_result: (string) -> (result i32 i32) +``` + +Source call form: + +```slo +(std.string.parse_i32_result text) +``` + +`std.string.parse_i32_result` parses the entire input string as ASCII decimal +signed `i32`. Accepted input has an optional leading `-`, followed by at least +one ASCII digit `0` through `9`, with no other leading, trailing, embedded, or +non-ASCII digit bytes. The numeric value must be in the signed `i32` range +`-2147483648` through `2147483647`. + +Success returns `(ok i32 i32 value)`. The empty string, a lone `-`, non-digit +bytes, leading `+`, whitespace, trailing bytes, non-ASCII digits, and +out-of-range values return `(err i32 i32 1)`. The only ordinary parse error +code promised by exp-13 is `err 1`. + +exp-13 adds no new result family. It uses the existing `(result i32 i32)` +constructors, observers, unwraps, and `match` behavior. + +The intended exp-12 composition shape is structural: + +```slo +(let input (result string i32) (std.io.read_stdin_result)) +(match input + ((ok text) + (std.string.parse_i32_result text)) + ((err code) + (err i32 i32 code))) +``` + +Fixtures must not assume a particular stdin payload. A trailing newline read +from stdin is a trailing byte and therefore returns `(err i32 i32 1)` unless a +future contract adds trimming or line APIs. + +The later exp-111 staged `std/io.slo` stdin helpers follow the same rule: +their explicit-import fixture stays structural and payload-agnostic because +the runner may provide implementation-owned stdin text. + +exp-13 release fixtures are: + +```text +examples/supported/string-parse-i32-result.slo +examples/formatter/string-parse-i32-result.slo +``` + +Artifact manifests record exp-13 standard-runtime usage only if existing +manifest patterns already record standard-runtime usage metadata. Otherwise no +parse-specific manifest field or schema version change is promoted. + +exp-13 explicitly defers trap-based `std.string.parse_i32`, parsing floats, +bools, strings, or bytes, whitespace trimming, locale-aware parsing, base +prefixes, radix arguments, underscores, plus-sign acceptance, generic parse +APIs, parse error messages, richer parse error codes, parse error ADTs, +Unicode digit parsing, string indexing and slicing, tokenizer/scanner APIs, +stdin line APIs, stable ABI/layout/helper symbols, manifest schema changes, +and beta maturity. + +The normative exp-13 contract is +`.llm/EXP_13_STRING_PARSE_I32_RESULT_ALPHA.md`. + +## 4.19 exp-14 Standard Runtime Conformance Alignment + +Status: released experimental conformance alignment. exp-14 is not a beta +maturity claim. + +exp-14 seeds standard-runtime conformance/readiness over the released exp-13 +surface. It aligns documentation, fixture inventory, fixture byte checks, and +release gates around already promoted compiler-known `std.*` behavior. + +exp-14 adds the normative catalog: + +```text +STANDARD_RUNTIME.md +``` + +The catalog lists every promoted compiler-known `std.*` operation through +exp-13 with signature, release, fixture, behavior/trap/result note, manifest +note, and deferrals. It does not make legacy compatibility aliases such as +`print_i32`, `print_string`, `print_bool`, or `string_len` into `std.*` +operations. + +exp-14 adds byte-identical canonical Slovo fixtures for the already released +exp-8 host time/sleep names: + +```text +examples/supported/time-sleep.slo +examples/formatter/time-sleep.slo +``` + +The fixture uses only `std.time.sleep_ms 0` and structural/non-negative +`std.time.monotonic_ms` checks. It must not assert positive-duration timing, +elapsed-time precision, scheduler behavior, wall-clock correspondence, or +ordering across threads. + +exp-14 requires the current supported fixture inventory to stay explicit and +requires Slovo/Glagol supported and formatter fixtures to stay byte-aligned +where matching files exist. + +exp-14 also requires a fresh-project workflow to cover `new`, `check`, +`fmt --check`, `test`, `doc`, and `build` where the hosted toolchain exists. +That workflow, or an added conformance project, must exercise existing +features together: modules/imports, tests, strings, `std.string.concat`, +`std.string.parse_i32_result`, result `match`, `(vec i32)`, `(vec i64)`, +enum `match`, and `std.io.print_i32`. + +The exp-14 release gate includes Slovo and Glagol diff checks, +`cargo fmt --check`, `cargo test`, ignored promotion gate, binary smoke, LLVM +smoke, fixture byte-alignment checks, fresh-project workflow checks, +artifact-manifest no-schema-drift checks, and release review `PASS`. + +exp-14 adds no source syntax, type forms, runtime APIs, compiler-known +`std.*` names, standard library functions, manifest schema version, +ABI/layout promise, runtime headers/libraries, or beta maturity. + +The normative exp-14 release contract is +`.llm/EXP_14_STANDARD_RUNTIME_CONFORMANCE_ALIGNMENT.md`. + +## 4.20 exp-15 Result Helper Standard Names Alpha + +Status: released experimental alpha. exp-15 is not a beta maturity claim. + +exp-15 promotes standard source-level names for the existing result helper +surface: + +```text +std.result.is_ok +std.result.is_err +std.result.unwrap_ok +std.result.unwrap_err +``` + +These names are accepted only for the currently supported concrete result +families: + +```text +(result i32 i32) +(result string i32) +``` + +They are the preferred names for new source. Existing unqualified result +helper forms `is_ok`, `is_err`, `unwrap_ok`, and `unwrap_err` remain +compatibility syntax where they are already supported. + +exp-15 adds byte-identical canonical fixtures: + +```text +examples/supported/result-helpers.slo +examples/formatter/result-helpers.slo +``` + +The fixture covers ok/err observation and unwraps for both supported result +families. + +exp-15 adds no result mapping, chaining, `unwrap_or`, option helper standard +names, new payload families, generic result, user-defined error payloads, enum +payloads, manifest schema changes, runtime ABI/layout promises, stable helper +symbols, runtime headers/libraries, or beta maturity. + +The normative exp-15 release contract is +`.llm/EXP_15_RESULT_HELPER_STANDARD_NAMES_ALPHA.md`. + +## 5. Slice 1: Struct Value Flow + +Status: promoted as `examples/supported/struct-value-flow.slo`. + +### 5.1 Goal + +v0 structs can be declared, constructed, and immediately field-accessed: + +```slo +(. (Point (x 20) (y 22)) x) +``` + +v1 should make structs usable as ordinary values in the supported subset. +Later promoted slices may broaden the direct field-type family while reusing +the same constructor, local, parameter, return, call, and field-access flow. + +The first v1 struct slice promotes: + +- struct locals +- struct parameters +- struct returns +- calls returning structs +- field access through stored struct values + +Struct field mutation and whole-struct value mutation are intentionally +deferred beyond v1. They must not be claimed as supported until a later +promotion specifies syntax, typed-core meaning, lowering, formatting, +diagnostics, examples, and tests at the same standard. + +### 5.2 Surface Syntax + +Struct declaration syntax remains: + +```slo +(struct Point + (x i32) + (y i32)) +``` + +Constructor syntax remains: + +```slo +(Point (x 20) (y 22)) +``` + +Local storage uses the existing local declaration shape: + +```slo +(let p Point (Point (x 20) (y 22))) +(. p x) +``` + +Function parameters and returns use the nominal struct name: + +```slo +(fn point_x ((p Point)) -> i32 + (. p x)) + +(fn make_point ((x i32) (y i32)) -> Point + (Point (x x) (y y))) +``` + +### 5.3 Type Rules + +- Struct names are nominal types. +- Struct names must be declared before use. +- A struct local declared with `let` is immutable and supported. +- A struct local declared with `var` is deferred beyond v1 until field/value + mutation is explicitly promoted. +- A function parameter of struct type is immutable and supported. +- A function may return a struct value when the struct has only supported v1 + field types. +- A call may produce a struct value when the callee return type is a declared + supported struct. +- Field access through a value of known struct type returns the field type. +- The first struct-flow slice supports `i32` fields. exp-18 additionally + supports direct fields whose types are current user-defined enum names. + exp-19 additionally supports direct `bool` and immutable `string` fields. +- exp-29 additionally supports direct immutable `i64` and finite `f64` fields. +- exp-120 additionally supports direct fixed immutable array fields over + `i32`, `i64`, `f64`, `bool`, and `string` element families with positive + literal lengths. +- Nested struct fields remain deferred unless separately specified. + +### 5.4 Lowering Direction + +The typed core represents struct values as nominal typed expressions. The +promoted slice requires checked forms for: + +- struct constructor expressions +- field access expressions +- immutable struct local bindings +- struct-typed parameters +- struct-typed return values +- calls whose result type is a struct + +The explicit constructor and field-access forms are: + +```text +StructConstruct { + name, + fields, + result_type +} + +StructFieldAccess { + value, + field, + result_type +} +``` + +Glagol uses a compiler-owned LLVM aggregate representation for v1 struct +values, but v1 does not promise a stable external ABI or cross-module layout. +Source field declaration order currently drives the implementation aggregate +order inside a closed compilation unit; it is not a public ABI promise. + +The implementation prioritizes correctness and verification over layout +optimization. + +### 5.5 Formatter Rules + +Formatter behavior preserves v0 style: + +- top-level struct declarations are multi-line +- constructor expressions with inline `i32` fields may stay inline +- local declarations containing constructors follow existing local formatting +- field access remains `(. value field)` + +If constructor expressions become too complex for one line, v1 must define a +canonical multiline layout before support is claimed. + +### 5.6 Diagnostics + +The promoted struct-flow slice requires the existing structured diagnostic +format: machine-readable code, source span, line/column range, and any useful +expected/found or hint fields. + +The promoted slice is covered by Glagol diagnostics for: + +- constructor field type mismatch +- missing constructor field +- unknown constructor field +- constructor field order mismatch if declaration order remains required +- unsupported struct field type +- empty struct declaration +- unknown field access +- `MutableStructLocalUnsupported` when a struct local uses `var` + +Before v1 release, the diagnostic matrix must also cover: + +- unknown struct type in local declaration +- unknown struct type in function parameter +- unknown struct type in function return +- duplicate struct declarations +- duplicate struct fields +- duplicate constructor fields +- field access on non-struct value +- unsupported struct field/value mutation because mutation remains deferred +- unsupported nested struct field if nested structs remain deferred + +### 5.7 Fixtures + +The first v1 struct-flow promotion adds: + +```text +examples/supported/struct-value-flow.slo +examples/formatter/struct-value-flow.slo +``` + +Later promoted struct-field slices additionally use: + +```text +examples/supported/array-struct-fields.slo +examples/formatter/array-struct-fields.slo +``` + +Glagol mirrors these with compiler fixtures and a mutable-struct-local +diagnostic snapshot. monorepo promotion verification must run both the +default Glagol promotion gate and its ignored Slovo/Glagol alignment gate. + +## 6. Slice 2: Option/Result Value Flow And Payload Access + +Status: promoted as `examples/supported/option-result-flow.slo` and +`examples/supported/option-result-payload.slo`. + +### 6.1 Goal + +v0 option/result support allowed only direct constructor returns. The v1 +option/result flow slice makes first-pass `i32` option/result values usable as +ordinary immutable values. The payload-access extension promotes explicit +trap-based unary extraction for `i32` payloads. The v1.4 core-language +expansion later promotes source-level `match` for the same concrete +option/result families while keeping broader algebraic-data-type behavior +deferred. + +The promoted option/result contract supports: + +- immutable `(option i32)`, `(option i64)`, `(option f64)`, `(option bool)`, + `(option string)`, and `(result i32 i32)` locals +- `(option i32)`, `(option i64)`, `(option f64)`, `(option bool)`, + `(option string)`, and `(result i32 i32)` function parameters +- calls returning option/result values +- tag observation with `is_some`, `is_none`, `is_ok`, and `is_err` +- explicit payload extraction with `unwrap_some`, `unwrap_ok`, and + `unwrap_err` + +The exp-10 release additionally promotes only the concrete `(result string i32)` +family for result-based host errors. That family follows the same result +value-flow, observer, unwrap, and match rules where the `ok` payload is +`string` and the `err` payload is `i32`. + +Direct constructor-return functions remain part of the v0 baseline. Returning +option/result values from locals, call results, or value-producing `if` +expressions is not part of this promoted slice until fixtures and tests cover +those forms. + +### 6.2 Surface Syntax + +Constructors remain: + +```slo +(some i32 value) +(none i32) +(some i64 value) +(none i64) +(some f64 value) +(none f64) +(some bool value) +(none bool) +(some string value) +(none string) +(ok i32 i32 value) +(err i32 i32 value) +``` + +Local storage uses the existing local declaration shape: + +```slo +(let value (option i32) (some i32 42)) +(let wide (option i64) (some i64 42i64)) +(let fraction (option f64) (some f64 42.5)) +(let flag (option bool) (some bool true)) +(let text (option string) (some string "hello")) +(let status (result i32 i32) (ok i32 i32 42)) +``` + +Function parameters and returns use the existing type forms: + +```slo +(fn option_score ((value (option i32))) -> i32 + (if (is_some value) + 1 + 0)) + +(fn option_wide_score ((value (option i64))) -> i32 + (if (is_some value) + 1 + 0)) + +(fn option_fraction_score ((value (option f64))) -> i32 + (if (is_some value) + 1 + 0)) + +(fn option_flag_score ((value (option bool))) -> i32 + (if (is_some value) + 1 + 0)) + +(fn option_text_score ((value (option string))) -> i32 + (if (is_some value) + 1 + 0)) + +(fn result_failure_score ((value (result i32 i32))) -> i32 + (if (is_err value) + 1 + 0)) +``` + +The promoted tag observers are unary forms: + +```slo +(is_some option-value) +(is_none option-value) +(is_ok result-value) +(is_err result-value) +(std.result.is_ok result-value) +(std.result.is_err result-value) +``` + +The promoted payload extractors are unary forms: + +```slo +(unwrap_some option-value) +(unwrap_ok result-value) +(unwrap_err result-value) +(std.result.unwrap_ok result-value) +(std.result.unwrap_err result-value) +``` + +They are intentionally trap-based extraction forms. They are not pattern +matching and do not introduce user-catchable exceptions. + +For result values, exp-15 makes the `std.result.*` forms the preferred source +surface. The unqualified result helpers remain compatibility syntax where they +were already supported. Option helper standard names are not promoted. + +### 6.3 Type Rules + +- The promoted slice supports `(option i32)`, `(option i64)`, + `(option f64)`, `(option bool)`, `(option string)`, `(result i32 i32)`, + the exp-10 family + `(result string i32)`, and the + exp-25 family `(result i64 i32)`. +- Payloads and extraction beyond those concrete families remain unsupported; + `unwrap_ok` returns `string` for `(result string i32)` and `i64` for + `(result i64 i32)`. +- Option/result locals are supported only with immutable `let`. +- Option/result parameters are immutable. +- `is_some` and `is_none` require `(option i32)`, `(option i64)`, + `(option f64)`, `(option bool)`, or `(option string)` and return `bool`. +- `is_ok` and `is_err` require `(result i32 i32)` or the exp-10 + family `(result string i32)` and return `bool`. +- `std.result.is_ok` and `std.result.is_err` have the same result-family type + rules as the legacy unqualified compatibility observers. +- `unwrap_some` requires `(option i32)`, `(option i64)`, `(option f64)`, + `(option bool)`, or `(option string)` and returns the matching concrete + payload type. +- `unwrap_ok` requires `(result i32 i32)` and returns `i32`, or requires the + exp-10 family `(result string i32)` and returns `string`. +- `unwrap_err` requires `(result i32 i32)` or the exp-10 family + `(result string i32)` and returns `i32`. +- `std.result.unwrap_ok` and `std.result.unwrap_err` have the same result-family + type rules and trap behavior as the legacy unqualified compatibility + extractors. +- `unwrap_some` traps at runtime when its operand is `none`. +- `unwrap_ok` traps at runtime when its operand is `err`. +- `unwrap_err` traps at runtime when its operand is `ok`. +- Source-level option/result `match` for `(option i32)`, `(option i64)`, + `(option f64)`, `(option bool)`, `(option string)`, and `(result i32 i32)` + is promoted by v1.4. exp-10 promotes the same result `match` shape for + `(result string i32)`. Mapping remains deferred. +- Option/result equality remains unsupported and deferred beyond v1. +- Printing option/result values remains unsupported and deferred beyond v1. +- Nested option/result values, arrays of options/results, and structs + containing option/results remain deferred beyond v1. +- Additional option/result payload families beyond the current + `(option i32)`, `(option i64)`, `(option f64)`, `(option bool)`, + `(option string)`, and released concrete result slices remain deferred. +- Payload traps are not user-catchable exceptions in v1; user-catchable + exceptions remain deferred beyond v1. + +### 6.4 Lowering Direction + +The typed core represents current option/result values as tagged concrete +payload aggregates. The promoted slice requires checked forms for: + +- option/result constructors +- immutable option/result local bindings +- option/result typed parameters +- option/result typed return values +- calls whose result type is option/result +- tag observers +- payload extractors + +Glagol uses a compiler-owned tagged aggregate representation for current +option/result values. The tag indicates `some`/`ok` when true and `none`/`err` +when false. This is closed-module implementation behavior, not a stable +external ABI or layout promise. + +Payload extraction must lower through an explicit tag check before payload +access. On the non-matching tag, the generated runtime path traps. The exact +trap helper, block names, aggregate layout, and ABI are implementation-owned, +but the check-before-payload-access shape is part of the v1 contract so Glagol +tests can verify it. + +The v1.2 runtime error messages for payload traps are stable: + +```text +slovo runtime error: unwrap_some on none +slovo runtime error: unwrap_ok on err +slovo runtime error: unwrap_err on ok +``` + +The message is written to stderr with a trailing newline. The process then +exits with code `1`. These traps are not user-catchable Slovo exceptions. + +### 6.5 Formatter Rules + +Formatter behavior preserves the current compact style: + +- constructors stay inline when their payload expression is compact +- option/result local declarations use one-line local formatting +- option/result parameter and return types print as `(option i32)`, + `(option i64)`, `(option string)`, and `(result i32 i32)` +- exp-10 `(result string i32)` parameter and return types print in the same + canonical type-form style +- tag observers print as unary expression forms +- payload extractors print as unary expression forms +- exp-15 `std.result.*` observers and payload extractors print as unary + expression forms and remain inline when used as expressions + +v1.4 promotes option/result `match`; its canonical multiline layout is defined +in section 4.2 and `.llm/V1_4_CORE_LANGUAGE_EXPANSION.md`. + +### 6.6 Diagnostics + +The promoted slice keeps the existing structured diagnostic format. It is +covered by Glagol diagnostics for: + +- malformed option/result constructors +- non-`i32` option/result payload types +- constructor payload type mismatch +- option/result equality +- option/result printing +- option observers applied to non-option values +- result observers applied to non-result values +- `unwrap_some` applied to non-option values +- `unwrap_ok` or `unwrap_err` applied to non-result values +- `std.result.*` helpers applied to non-result values or unsupported result + families + +Before v1 release, the diagnostic matrix must also cover: + +- non-`i32` option/result payloads in function parameter position +- non-`i32` option/result payloads in function return position +- unsupported option/result mapping if such forms become source-reachable + without promotion + +### 6.7 Fixtures + +The option/result value-flow and payload-access promotion adds: + +```text +examples/supported/option-result-flow.slo +examples/formatter/option-result-flow.slo +examples/supported/option-result-payload.slo +examples/formatter/option-result-payload.slo +``` + +The exp-10 result-based host errors release adds: + +```text +examples/supported/host-io-result.slo +examples/formatter/host-io-result.slo +``` + +The exp-13 string parse i32 result release adds: + +```text +examples/supported/string-parse-i32-result.slo +examples/formatter/string-parse-i32-result.slo +``` + +The exp-15 result helper standard names release adds: + +```text +examples/supported/result-helpers.slo +examples/formatter/result-helpers.slo +``` + +Glagol mirrors these with compiler fixtures, formatter coverage, test-runner +coverage, LLVM shape checks, runtime trap checks, observer diagnostics, and +payload-extractor diagnostics. + +## 7. Slice 3: Array Value Flow And Dynamic Indexing + +Status: promoted as `examples/supported/array-value-flow.slo`, widened by +exp-117 through `examples/supported/array-direct-scalars.slo` and +`examples/supported/array-direct-scalars-value-flow.slo`, then broadened again +by exp-118 through `examples/supported/array-string.slo` and +`examples/supported/array-string-value-flow.slo`. + +### 7.1 Goal + +v0 arrays support fixed `(array i32 N)` constructors, immutable array locals +initialized from matching constructors, and literal indexing. The v1 array +slice keeps those v0 forms valid and broadens fixed-size direct scalar arrays +plus one concrete `string` array lane as ordinary values in the supported +subset, including immutable locals initialized from matching array-valued +expressions. + +The promoted slice supports: + +- fixed array types `(array T N)` with positive literal `N` when `T` is one of + `i32`, `i64`, `f64`, `bool`, or `string` +- fixed array constructors over those promoted element families +- immutable array locals initialized from any supported expression that checks + as the exact declared `(array T N)` type +- `(array T N)` function parameters +- `(array T N)` function returns +- calls returning `(array T N)` values +- dynamic `(index array-expr index-expr)` when `index-expr` checks as `i32` + +The slice intentionally does not promote array mutation, arrays of other +unsupported element types, zero-length arrays, slices, array equality, +printing arrays, nested arrays, unchecked indexing, or any stable ABI/layout +promise. + +### 7.2 Surface Syntax + +Fixed array type syntax remains: + +```slo +(array T N) +``` + +For this slice, `T` is exactly one of `i32`, `i64`, `f64`, `bool`, or +`string`. + +Constructor syntax remains: + +```slo +(array T value...) +``` + +Function parameters and returns may use fixed direct scalar or `string` array +types: + +```slo +(fn pick ((values (array i64 3)) (i i32)) -> i64 + (index values i)) + +(fn make_values () -> (array bool 3) + (array bool true false true)) + +(fn make_words () -> (array string 3) + (array string "sun" "moon" "star")) +``` + +A call may return an array value and feed another supported array expression +position: + +```slo +(fn pick_from_return ((i i32)) -> bool + (index (make_values) i)) +``` + +Immutable array locals may be initialized from any supported array-valued +expression with the exact declared type: + +```slo +(fn local_array_flow ((i i32)) -> f64 + (let values (array f64 3) (array f64 1.0 2.0 3.0)) + (index values i)) +``` + +Index syntax remains: + +```slo +(index array-expr index-expr) +``` + +For this slice, `index-expr` may be any supported expression that checks as +`i32`. Literal indexing remains valid. + +### 7.3 Type Rules + +- `N` in `(array T N)` is a positive source integer literal in type position. +- The promoted element types are exactly `i32`, `i64`, `f64`, `bool`, and + `string`. +- Array constructors produce `(array T count)` values. +- Constructor values are checked left to right and must each check as `T`. +- In an expected-type context, `(array T count)` is valid for `(array T N)` + only when `count == N`. +- Immutable array locals declared with `let` may be initialized by any + supported expression that checks as exactly the declared `(array T N)` type, + including direct constructors, parameters, and call results. +- This slice does not promote mutable array locals. +- Function parameters may have type `(array T N)`. +- Function returns may have type `(array T N)`. +- Calls may produce `(array T N)` when the callee return type is a promoted + fixed array type. +- Supported array values may be passed to parameters with the exact same + `(array T N)` type. +- Returning an array expression requires the exact declared `(array T N)` + return type. +- `index` requires an array expression that checks as `(array T N)`. + Promoted index bases are any supported expression with that type, including + immediate constructors, immutable array locals, array parameters, and calls + returning promoted fixed arrays. +- `index` requires an index expression that checks as `i32`. +- `index` returns `T`. +- Array equality remains unsupported. +- Printing arrays remains unsupported. +- Nested arrays and arrays of structs/options/results remain deferred. + +### 7.4 Bounds Behavior + +Literal out-of-bounds indexing remains a structured diagnostic. A literal index +must satisfy `0 <= index < N`; if it does not, checking fails before lowering. + +Dynamic indexing must be checked at runtime by Glagol. For every accepted +dynamic index expression, the lowered program must evaluate the array +expression once, evaluate the index expression once, then branch through an +explicit bounds check equivalent to: + +```text +0 <= index && index < N +``` + +If the check succeeds, the selected element is read and returned. If the check +fails, execution must enter an explicit runtime trap path before any +out-of-bounds element access occurs. The trap path may be compiler-owned and is +not a stable Slovo runtime ABI in v1, but it must be visible in lowering or +LLVM output well enough for Glagol tests to verify that dynamic array access is +not unchecked. + +Top-level tests that trigger the runtime trap should be reported as trapped +tests by Glagol's test runner once that runner covers trap results. This slice +does not require Slovo to standardize a user-catchable exception value. + +The v1.2 runtime error message for a failed dynamic bounds check is stable: + +```text +slovo runtime error: array index out of bounds +``` + +The message is written to stderr with a trailing newline. The process then +exits with code `1`. For `glagol test`, the trapped top-level test is reported +as a trapped test and the command exits with code `1`. For `glagol build`, the +produced executable exits with code `1`. + +### 7.5 Lowering Direction + +The typed core represents promoted arrays as fixed-length aggregate values with +compiler-owned layout. The promoted slice requires checked forms for: + +- fixed promoted array types +- fixed promoted array constructor expressions +- immutable array local bindings whose initializer checks as the exact declared + fixed promoted array type +- array-typed parameters +- array-typed return values +- calls whose result type is a fixed promoted array +- literal and dynamic array indexing + +The array index core form records whether bounds were proven at check time or +must be checked at runtime: + +```text +ArrayIndex { + array: TExpr, + index: TExpr, + bounds: LiteralInBounds | RuntimeChecked, + result_type: element_type, + span, + array_span, + index_span +} +``` + +Literal in-bounds indices may lower to direct aggregate extraction. Dynamic +indices lower to a runtime-checked element access with a trap edge. Glagol may +copy fixed arrays between local storage, parameters, returns, and call results +using any compiler-owned aggregate representation, but v1 does not promise a +stable LLVM, C, FFI, or cross-module ABI layout. + +### 7.6 Formatter Rules + +Formatter behavior preserves the current compact array style: + +- `(array T N)` types print inline +- constructors with inline promoted values print as `(array T value...)` +- array local declarations print with existing one-line local formatting when + their initializer is inline +- array parameter and return types print inline in function signatures +- index expressions print as `(index array-expr index-expr)` when both + operands are inline +- function and test body indentation follows the existing + one-body-form-per-line rule + +If later slices add multiline array constructors, slices, or pattern-style +array forms, those slices must define their own canonical layouts. + +### 7.7 Diagnostics + +The promoted slice keeps the existing structured diagnostic format. It is +covered by the v0 array diagnostics for malformed types, malformed +constructors, unsupported element types, zero-length arrays, empty +constructors, constructor length mismatch, element type mismatch, malformed +index syntax, indexing non-arrays, non-`i32` index expressions, literal +out-of-bounds indices, mutable array locals, array equality, array printing, +and array mutation. + +The v1 promotion changes the first-pass `UnsupportedArraySignatureType` +boundary: exact `(array T N)` parameters and returns are now supported for the +direct scalar and `string` element families. Unsupported array signature +diagnostics remain required for other unsupported element types, zero-length +arrays, nested arrays, arrays containing unsupported value types, and any +future array type form not promoted here. + +Dynamic indices are no longer rejected as `DynamicArrayIndexUnsupported` when +the index expression checks as `i32`. That diagnostic remains appropriate only +for compilers or modes that explicitly target the v0 baseline. + +The promoted array slice also requires diagnostics or lowering checks for these +source-reachable promoted boundaries: + +- array return expression type mismatch +- array argument type mismatch +- array local initializer type mismatch when an immutable array local + initializer does not check as the exact declared fixed promoted array type +- missing runtime trap lowering if a dynamic index reaches LLVM unchecked + +Future diagnostics for newly promoted array forms remain separate from this +slice. In particular, later slices that promote mutable arrays, array mutation, +zero-length arrays, other unsupported array element types, nested arrays, +slices, equality, printing, unchecked indexing, or stable layout must define +their own diagnostics before claiming support. + +### 7.8 Fixtures + +The current array fixtures for this slice are: + +```text +examples/supported/array-direct-scalars.slo +examples/supported/array-value-flow.slo +examples/supported/array-direct-scalars-value-flow.slo +examples/supported/array-string.slo +examples/supported/array-string-value-flow.slo +examples/formatter/array-direct-scalars.slo +examples/formatter/array-value-flow.slo +examples/formatter/array-direct-scalars-value-flow.slo +examples/formatter/array-string.slo +examples/formatter/array-string-value-flow.slo +``` + +Glagol mirrors these with compiler fixtures, formatter coverage, test-runner +coverage, LLVM checks that dynamic indexing branches to an explicit trap path, +and diagnostics for unsupported array forms. + +## 8. Slice 4: Runtime Strings And Practical Runtime Values + +Status: promoted as `examples/supported/string-print.slo`, extended in v1.2 +through `examples/supported/string-value-flow.slo` and +`examples/supported/print-bool.slo`, then given v1.5 standard-runtime names in +`examples/supported/standard-runtime.slo`. exp-1 adds +`examples/supported/owned-string-concat.slo` as the first +owned-runtime-string compiler-supported fixture. exp-13 adds +`examples/supported/string-parse-i32-result.slo` as the first string parse +result compiler-supported fixture. exp-34 adds +`examples/supported/string-parse-bool-result.slo` for the exact lowercase bool +parse result slice. + +### 8.1 Goal + +The first runtime string slice promoted enough string behavior to print +compiler-emitted immutable source literals from ordinary programs. Slovo v1.2 +extends that slice conservatively so immutable strings can flow through small +programs without introducing allocation or ownership. exp-1 adds one narrow +heap-created owned-string operation, `std.string.concat`, while preserving the +same source type and keeping memory management out of user code. + +The promoted slice supports: + +- source string literals as immutable runtime literals +- ASCII-plus-current-escape literal semantics, without a full Unicode promise +- legacy compiler/runtime compatibility alias `(print_string value)` +- direct string-literal printing in a normal `i32` function body +- immutable string locals with `let` +- string parameters and string returns +- calls returning strings +- string equality with `=` +- string byte length with legacy compatibility alias `(string_len value)` +- top-level tests whose final expression uses string equality or compares a + string length result +- bool printing with legacy compatibility alias `(print_bool value)` +- v1.5 source-level standard-runtime names for the same print and string + length behavior: `std.io.print_i32`, `std.io.print_string`, + `std.io.print_bool`, and `std.string.len` +- exp-1 `std.string.concat`, returning an immutable runtime-owned `string` + from two `string` operands +- exp-13 released `std.string.parse_i32_result`, returning an existing + `(result i32 i32)` value from an entire ASCII decimal signed `i32` string +- exp-34 released `std.string.parse_bool_result`, returning `(result bool i32)` + for exact lowercase `true`/`false` parsing with `err 1` ordinary failures + +Everything else in the string family remains deferred until separately +specified, implemented, diagnosed, formatted, and tested. + +### 8.2 Surface Syntax + +String literals continue to use double quotes: + +```slo +"hello" +``` + +The first promoted runtime string consumer is: + +```slo +(print_string "hello") +``` + +`print_string` has exactly one argument of type `string`, returns builtin +`unit`, and prints the string followed by a newline. Like `print_i32`, it is a +legacy compiler/runtime compatibility alias paired with runtime lowering. +v1.5 promotes the source-level standard-runtime name `std.io.print_string` for +the same behavior. Neither spelling is a user-defined function, import, +foreign function, package dependency, or stable runtime ABI. + +v1.2 promotes string value flow: + +```slo +(let value string "hello") + +(fn echo ((value string)) -> string + value) + +(fn call_return () -> string + (echo "hello")) +``` + +v1.2 promotes string equality and length through the legacy alias: + +```slo +(= "hello" (call_return)) +(string_len "hello") +``` + +`=` accepts two `string` operands and returns `bool`. String equality compares +the decoded byte sequence before the trailing NUL byte. It is not +locale-sensitive and makes no Unicode normalization promise. + +`string_len` has exactly one `string` argument and returns `i32`. It counts +decoded bytes before the trailing NUL byte. It is not a Unicode scalar count, +grapheme count, display width, or locale-sensitive length. + +v1.5 promotes `std.string.len` for the same behavior: + +```slo +(std.string.len "hello") +``` + +exp-1 promotes `std.string.concat` as the first heap-created string operation: + +```slo +(std.string.concat "hello, " "slovo") +``` + +`std.string.concat` has exactly two `string` arguments and returns `string`. +The result is immutable and runtime-owned by the compiler/runtime. It is usable +with existing string equality, `std.string.len`, `std.io.print_string`, string +locals, parameters, returns, and calls returning `string`. No import is +required, no legacy alias is introduced, and no user-visible allocation or +deallocation form is exposed. + +v1.2 promotes bool printing through the legacy alias: + +```slo +(print_bool (= "hello" "hello")) +``` + +`print_bool` has exactly one `bool` argument, returns builtin `unit`, and +prints `true` or `false` followed by a newline. v1.5 promotes +`std.io.print_bool` for the same behavior. v1.5 also promotes +`std.io.print_i32` for the same behavior as legacy `print_i32`. + +Slovo v1.5 does not define a broader standard-runtime printing contract. The +only supported printing forms are legacy compatibility aliases `print_i32`, +`print_string`, and `print_bool`, plus source-level standard-runtime names +`std.io.print_i32`, `std.io.print_string`, and `std.io.print_bool`. Unit +printing, composite value printing, overloaded print forms, file IO, imports, +packages, and stable runtime print ABI bindings remain deferred beyond v1.5. + +A supported ordinary program may use `print_string` or `std.io.print_string` +as a sequential body form before an `i32` final expression: + +```slo +(fn main () -> i32 + (print_string "hello") + 0) +``` + +`print_string` and `std.io.print_string` are not supported as the final +expression of an `i32` function or `bool` test because they return builtin +`unit`. + +`bool` remains a checked value type for promoted conditions, tests, +option/result observers, string equality, `print_bool`, and +`std.io.print_bool`. +`unit` remains an internal builtin result type for unit-producing forms such as +`set`, `while`, legacy print aliases, and `std.io.print_*` calls. +User-declared `unit` return types, `unit` parameters, `unit` locals, stored +`unit` values, `print_unit`, and using `unit` as a user-visible final result +remain unsupported in v1.2. + +### 8.3 Literal Semantics + +The promoted literal domain is intentionally narrow: + +- unescaped literal content is ASCII source text +- the required current escapes are newline `\n`, tab `\t`, quote `\"`, and + backslash `\\` +- decoded literal bytes are immutable +- embedded NUL bytes and arbitrary byte escapes are not part of the promoted + slice +- this slice makes no promise that arbitrary Unicode source text is accepted, + normalized, encoded, compared, or printed correctly + +For the v1.2/v1.5 slice, the source literal denotes a runtime literal, not an +owned heap string. Every promoted string value originates from a source string +literal or from propagating such a value through an immutable local, parameter, +return, or call result. + +exp-1 extends this by allowing one heap-created string source: +`std.string.concat`. The result is runtime-owned and immutable, but ownership +is not source-visible. No Slovo program can mutate, manually allocate, free, +retain, or otherwise manage the storage behind a promoted string value. + +### 8.4 Type Boundary + +The checker assigns source string literals the builtin type `string`. + +Promoted uses of `string` values: + +- direct argument to `print_string` or `std.io.print_string` +- immutable `let` locals with type `string` +- mutable `var` locals with type `string` plus same-type `set` reassignment +- function parameters with type `string` +- function returns with type `string` +- call results whose declared return type is `string` +- operands to `=` when both operands check as `string` +- argument to `string_len` or `std.string.len` +- exp-1 arguments to and result from `std.string.concat` + +Still deferred: + +- string ordering or comparison other than equality +- string values inside arrays or vectors +- slices or borrowed substring views +- concatenation beyond exp-1 `std.string.concat` +- indexing +- ownership or lifetime annotations +- user-defined runtime bindings involving `string` + +### 8.5 Runtime Representation And Lowering + +The runtime representation for this slice is still a borrowed immutable +NUL-terminated byte pointer to compiler-emitted static storage. v1.2 does not +add allocation. + +Required lowering shape: + +- each promoted string literal lowers to immutable static storage containing the + decoded ASCII-plus-escape bytes followed by one trailing NUL byte +- string locals, parameters, returns, and call results lower as copies of that + borrowed immutable pointer value +- `(print_string value)` lowers to a runtime call that receives a borrowed + pointer to that storage and writes the bytes followed by a newline +- `(print_bool value)` lowers to a runtime call that writes `true\n` or + `false\n` +- string equality lowers to a byte-sequence comparison before the trailing NUL +- `(string_len value)` lowers to a byte count before the trailing NUL +- v1.5 `std.io.print_string`, `std.io.print_bool`, and `std.string.len` lower + through the same implementation paths as their legacy aliases +- exp-1 `std.string.concat` lowers to a runtime-owned immutable string + allocation, byte copy, and trailing terminator write; allocation failure + traps with `slovo runtime error: string allocation failed` +- since embedded NUL is not promoted, printing observes the whole decoded + literal and equality/length observe the whole decoded value + +Typed-core shape: + +```text +StringLiteral { + bytes, + type: string, + span +} + +CallIntrinsic { + name: print_string, + args: [StringLiteral], + type: unit, + span +} + +StringLet { + name, + type: string, + value: TExpr, + span +} + +CallIntrinsic { + name: string_len, + args: [TExpr], + type: i32, + span +} + +StringEquals { + left: TExpr, + right: TExpr, + type: bool, + span +} + +CallIntrinsic { + name: print_bool, + args: [TExpr], + type: unit, + span +} + +StdStringConcat { + left: TExpr, + right: TExpr, + type: string, + span +} +``` + +This representation is not a stable Slovo ABI. Slovo v1 does not promise a C +signature, symbol name, pointer type spelling, address space, literal +deduplication behavior, static object layout, allocator interaction, or FFI +contract for strings. exp-1 also does not promise a stable runtime-owned string +layout, allocator, cleanup timing, helper symbol, or C ABI. + +### 8.6 Formatter Rules + +Formatter behavior preserves the current compact call style: + +```slo +(fn main () -> i32 + (print_string "hello") + 0) +``` + +String literals stay inline when used as the direct argument to `print_string`, +as immutable string local initializers, as string returns, as string equality +operands, and as `string_len` arguments. String parameter and return types print +as `string`. `string_len` and `print_bool` use the same compact call style as +other promoted intrinsics. v1.5 `std.io.print_*` and `std.string.len` calls use +the same compact call style. exp-1 `std.string.concat` uses the same compact +call style and remains inline when nested in `std.io.print_string`, +`std.string.len`, equality, local initializers, returns, and user calls. The +formatter must preserve the decoded literal meaning when re-emitting the +current supported escapes. + +### 8.7 Diagnostics + +The promoted slice keeps the existing structured diagnostic format. It is +acceptable to reuse general diagnostics when the expected/found data is clear. + +Required promoted-boundary diagnostics: + +- malformed or unterminated string literals must be reported as structured + lexer/parser diagnostics before checking +- `(print_string)` and `(print_string a b)` must report an arity diagnostic + with expected `1` +- `(print_string value)` where `value` is not `string` must report a type + diagnostic with expected `string` and found checked type +- using `print_string` as an `i32` function result or `bool` test result must + report the existing result-type diagnostic for found `unit` +- `(string_len)` and `(string_len a b)` must report an arity diagnostic with + expected `1` +- `(string_len value)` where `value` is not `string` must report a type + diagnostic with expected `string` and found checked type +- `(= left right)` where exactly one operand is `string` must report a type + diagnostic; both operands must be `string` for string equality +- `(print_bool)` and `(print_bool a b)` must report an arity diagnostic with + expected `1` +- `(print_bool value)` where `value` is not `bool` must report a type + diagnostic with expected `bool` and found checked type +- `(std.string.concat)`, `(std.string.concat a)`, and + `(std.string.concat a b c)` must report an arity diagnostic with expected `2` +- `(std.string.concat left right)` where either operand is not `string` must + report a type diagnostic with expected `string` and the found checked type +- using `std.string.concat` where a non-`string` result is required must report + the existing result-type diagnostic for found `string` +- declaring, exporting, importing, or binding a name that shadows + `std.string.concat` must produce a structured reserved-name diagnostic +- `print_unit` must report an unsupported intrinsic/form diagnostic, or a + structured unknown-call diagnostic if the implementation has no intrinsic + entry for it +- string ordering/comparison other than equality, string containers beyond the + current direct struct fields, current fixed string arrays, current direct + fixed-array struct fields, and current concrete option/result families, + slices, concatenation beyond exp-1 + `std.string.concat`, indexing, mutation, user-visible allocation, + user-visible deallocation, and user-defined runtime bindings must remain + unsupported with structured diagnostics or explicit backend-feature + diagnostics; they must not reach a backend panic + +Future diagnostics for broader string features remain separate from this +slice. + +### 8.8 Fixtures + +The runtime string promotions add: + +```text +examples/supported/string-print.slo +examples/supported/string-value-flow.slo +examples/supported/print-bool.slo +examples/formatter/string-print.slo +examples/formatter/string-value-flow.slo +examples/formatter/print-bool.slo +``` + +exp-1 adds: + +```text +examples/supported/owned-string-concat.slo +examples/formatter/owned-string-concat.slo +``` + +The exp-1 fixture files are current compiler-supported fixtures after the +matching Glagol exp-1 release gate. + +Glagol mirrors these with compiler fixtures, formatter coverage, test-runner +coverage, LLVM checks that literals lower to immutable static NUL-terminated +storage, executable/runtime checks that `print_string` prints the decoded +literal plus a newline, string equality and length checks, `print_bool` +stdout checks, and diagnostics for the deferred boundaries. + +### 8.9 v1.5 Standard Runtime Names + +v1.5 adds source-level standard-runtime names for stable v1.2 runtime behavior: + +```slo +(std.io.print_i32 value) +(std.io.print_string value) +(std.io.print_bool value) +(std.string.len value) +``` + +These names are compiler-known. They are not user modules, not importable +module members, not package dependencies, not foreign functions, and not stable +C ABI symbols. No import is required to call them. + +The promoted names have exactly the same type and behavior contracts as the +legacy compatibility aliases: + +- `std.io.print_i32`: one `i32`, returns builtin `unit` +- `std.io.print_string`: one `string`, returns builtin `unit` +- `std.io.print_bool`: one `bool`, returns builtin `unit` +- `std.string.len`: one `string`, returns `i32` + +Unknown or unpromoted `std.*` calls must produce a structured diagnostic; the +suggested code is `UnsupportedStandardLibraryCall`. Arity and type mismatches +for the promoted `std.*` calls use existing arity/type diagnostics where +applicable. The promoted names are reserved from user function/export +shadowing. + +v1.5 adds: + +```text +examples/supported/standard-runtime.slo +examples/formatter/standard-runtime.slo +``` + +### 8.10 exp-1 Owned Runtime String Name + +exp-1 adds one standard-runtime name: + +```slo +(std.string.concat left right) +``` + +The name is compiler-known and has the exact signature: + +```text +std.string.concat: (string, string) -> string +``` + +The result is an immutable runtime-owned string. Existing `string` operations +do not distinguish source-visible value kinds: literal-backed strings and +runtime-owned strings both have type `string`. + +`std.string.concat` is not an importable module member, not a user-defined +function, not a package dependency, not a foreign function, and not a stable C +ABI symbol. The implementation may lower through any runtime helper and +allocator strategy that preserves the source contract. + +No source-visible deallocation form is promoted. Runtime-owned string cleanup +is an implementation responsibility and is not observable except that +supported safe source must not require manual free and must not expose +use-after-free. + +Allocation failure traps with: + +```text +slovo runtime error: string allocation failed +``` + +The trap is process-terminating and exits with code `1`. + +### 8.11 exp-13 String Parse I32 Result Name + +exp-13 adds one standard-runtime name: + +```slo +(std.string.parse_i32_result text) +``` + +The name is compiler-known and has the exact signature: + +```text +std.string.parse_i32_result: (string) -> (result i32 i32) +``` + +The result uses the existing `(result i32 i32)` family. Success returns +`(ok i32 i32 value)`. Ordinary parse failure returns `(err i32 i32 1)`. + +The parser accepts the entire string only when it is ASCII decimal signed +`i32`: optional leading `-`, one or more ASCII digits, and an in-range value. +It rejects empty input, a lone `-`, `+`, whitespace, trailing bytes, +non-digits, non-ASCII digits, and out-of-range values with `err 1`. + +`std.string.parse_i32_result` is not an importable module member, not a +user-defined function, not a package dependency, not a foreign function, and +not a stable C ABI symbol. The implementation may lower through any runtime +helper strategy that preserves the source contract. + +### 8.12 exp-14 Standard Runtime Catalog + +exp-14 adds no new standard-runtime names. It adds `STANDARD_RUNTIME.md` as a +conformance catalog for the compiler-known `std.*` operations promoted through +exp-13. + +The catalog is documentation and release-gate inventory. It must match the +already promoted names, signatures, fixture references, behavior/trap/result +notes, manifest notes, and deferrals. It must not add imports, user modules, +package dependencies, runtime APIs, helper-symbol promises, ABI/layout +promises, manifest schema versions, runtime headers/libraries, or beta +maturity. + +### 8.13 exp-15 Result Helper Standard Names + +exp-15 adds four standard-runtime source names: + +```slo +(std.result.is_ok value) +(std.result.is_err value) +(std.result.unwrap_ok value) +(std.result.unwrap_err value) +``` + +The names are compiler-known and accepted only for `(result i32 i32)`, +`(result i64 i32)`, `(result string i32)`, exp-28 returned +`(result f64 i32)`, and exp-34 returned `(result bool i32)`. + +The exact signatures are: + +```text +std.result.is_ok: ((result i32 i32)) -> bool +std.result.is_ok: ((result i64 i32)) -> bool +std.result.is_ok: ((result string i32)) -> bool +std.result.is_ok: ((result f64 i32)) -> bool +std.result.is_ok: ((result bool i32)) -> bool +std.result.is_err: ((result i32 i32)) -> bool +std.result.is_err: ((result i64 i32)) -> bool +std.result.is_err: ((result string i32)) -> bool +std.result.is_err: ((result f64 i32)) -> bool +std.result.is_err: ((result bool i32)) -> bool +std.result.unwrap_ok: ((result i32 i32)) -> i32 +std.result.unwrap_ok: ((result i64 i32)) -> i64 +std.result.unwrap_ok: ((result string i32)) -> string +std.result.unwrap_ok: ((result f64 i32)) -> f64 +std.result.unwrap_ok: ((result bool i32)) -> bool +std.result.unwrap_err: ((result i32 i32)) -> i32 +std.result.unwrap_err: ((result i64 i32)) -> i32 +std.result.unwrap_err: ((result string i32)) -> i32 +std.result.unwrap_err: ((result f64 i32)) -> i32 +std.result.unwrap_err: ((result bool i32)) -> i32 +``` + +These forms lower to the same typed-core observer and payload-extraction +operations as the legacy unqualified compatibility forms. The existing +`unwrap_ok on err` and `unwrap_err on ok` trap behavior applies. + +`std.result.*` helper names are not importable module members, not +user-defined functions, not package dependencies, not foreign functions, and +not stable C ABI symbols. The implementation may lower through any helper +strategy that preserves the source contract. + +## 9. Slice 5: Tooling Contracts + +Status: promoted as a tooling contract only. This slice does not add source +syntax, runtime behavior, type-system behavior, or a native executable output +requirement. + +### 9.1 Machine Diagnostic Schema + +Slovo v1 keeps the v0 rule that diagnostics have both human-readable and +machine-readable forms generated from one compiler diagnostic object. Human +rendering may choose layout and surrounding prose, but the machine form is a +stable tool API. + +The v1 machine diagnostic schema is `slovo.diagnostic` version `1`. + +Every machine diagnostic emitted for a v1 compiler boundary must include: + +- schema marker: `slovo.diagnostic` +- version marker: `1` +- severity +- stable PascalCase diagnostic code +- concise message +- source file path +- primary zero-based, half-open byte span +- primary one-based line/column range + +Optional fields: + +- expected value, type, arity, or form +- found value, type, arity, or form +- safe repair hint +- related spans, each represented as a repeated `(related (span ...))` form + with its own file path, byte span, line/column range, and optional message + +Machine-readable diagnostic example: + +```slo +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code TypeMismatch) + (message "expected i32, found string") + (file "main.slo") + (span + (bytes 42 49) + (range 12 8 12 15)) + (expected i32) + (found string) + (hint "Use an integer value or convert explicitly.")) +``` + +Related span example: + +```slo +(related + (span + (file "main.slo") + (bytes 10 14) + (range 2 8 2 12) + (message "original declaration"))) +``` + +Each related span is emitted as its own repeated `(related ...)` form. The +related message belongs to that related span object; it is not a top-level +diagnostic message. + +The primary and related byte spans are zero-based and half-open. Line values +are one-based source line numbers. Column values are one-based byte columns +within the original UTF-8 source line; a tab counts as one input byte. The end +column is the first byte column after the highlighted range on the end line. +Diagnostic locations are derived from original source text, not from formatter +output. + +The schema version belongs in every machine diagnostic and every golden +diagnostic fixture. Future machine-shape changes require a documented schema +migration and updated Glagol snapshots. + +### 9.2 Artifact Manifest Schema + +Slovo v1 defines a narrow textual artifact manifest for compiler outputs and +test reports. The manifest records what a tool invocation consumed, what mode +it ran in, whether it succeeded, which diagnostic schema applies, and where +generated textual or structured artifacts can be found. + +The v1 artifact manifest schema is `slovo.artifact-manifest` version `1`. + +Every manifest must include: + +- schema marker: `slovo.artifact-manifest` +- version marker: `1` +- source path +- command string +- mode +- success marker +- diagnostics schema version +- primary output, either as a kind/path pair, stdout text, or explicit + no-output marker +- generated artifact references, which may be empty + +Optional field: + +- test report summary. When present in v1, it contains `total`, `passed`, + `failed`, and `skipped` counts. + +Compiler-output manifest example: + +```slo +(artifact-manifest + (schema slovo.artifact-manifest) + (version 1) + (source "examples/supported/add.slo") + (command "glagol compile examples/supported/add.slo --emit llvm") + (mode emit-llvm) + (success true) + (diagnostics-schema-version 1) + (primary-output + (kind llvm-ir) + (path "build/add.ll")) + (artifacts + (artifact + (kind llvm-ir) + (path "build/add.ll")))) +``` + +Test-report manifest example: + +```slo +(artifact-manifest + (schema slovo.artifact-manifest) + (version 1) + (source "examples/supported/top-level-test.slo") + (command "glagol test examples/supported/top-level-test.slo") + (mode test) + (success true) + (diagnostics-schema-version 1) + (primary-output + (kind stdout) + (stdout "1 passed\n")) + (artifacts) + (test-report + (total 1) + (passed 1) + (failed 0) + (skipped 0))) +``` + +Successful modes that intentionally produce no primary text may use: + +```slo +(primary-output + (kind no-output)) +``` + +Recommended v1 modes are `check`, `format`, `emit-llvm`, +`inspect-lowering`, and `test`. Recommended output and artifact kinds are +`diagnostics`, `formatted-source`, `llvm-ir`, `lowering-inspector`, `stdout`, +`stderr`, `no-output`, and `test-report`. + +A failed invocation still writes a manifest when the tool mode supports +manifests. In that case `(success false)` is paired with a diagnostics artifact +or stdout/stderr output whose diagnostics use schema version `1`. + +This contract does not require native executable output. LLVM IR, formatted +source, inspector output, diagnostics, stdout/stderr capture, and test reports +are sufficient v1 artifacts. + +### 9.3 LLVM Source-Map And Debug-Info Direction + +Slovo v1 requires compiler data to preserve diagnostic source spans through +parse, check, typed core, lowering, and LLVM IR emission. This is a tooling +requirement so diagnostics, lowering inspectors, trap checks, and artifact +manifests can point back to original source. + +Stable LLVM debug metadata, DWARF emission, and source-map files are deferred. +Future promotion should map Slovo source spans to generated LLVM functions, +basic blocks, instructions, runtime intrinsic calls, and explicit trap paths. +Array bounds traps and option/result payload traps should carry the source span +of the Slovo operation that can trap. + +Lowering-inspector golden fixtures are textual compiler-tree artifacts. They +show accepted source as compiler-owned tree output, such as surface and checked +forms, and are not a substitute for or a promise of stable LLVM debug metadata, +DWARF, or standalone source-map files. + +### 9.4 Formatter Stability Contract + +Formatter stability is a promoted v1 tooling contract. Every promoted fixture +under `examples/formatter/` defines canonical output. Formatting a promoted +fixture must produce the same source bytes again, including line comments, +indentation, blank-line placement, and a final newline. Reformatting formatter +output must be idempotent. + +The v1 formatter is not a width-based pretty printer. It may normalize spacing +and indentation for supported forms, but it does not promise terminal-width +measurement, column reflow, or wrapping of long inline calls/forms. Long inline +forms stay inline unless their form has an explicit canonical multiline shape. + +Line comments are supported only as full-line comments in these positions: + +- before top-level forms +- inside `fn` and `test` bodies before body expressions +- after the final `fn` or `test` body expression and before the closing paren +- after the last top-level form + +All other comment positions are unsupported in v1 and must be rejected with +structured diagnostics, not silently dropped or moved. In particular, v1 rejects +comments inside: + +- `(module ...)` forms +- `(struct ...)` forms +- `fn` and `test` headers before their body begins +- type forms such as `(array i32 N)`, `(option i32)`, `(result i32 i32)`, + exp-2 `(vec i32)`, exp-94 `(vec i64)`, exp-103 `(vec f64)`, and exp-4 + enum type names +- inline expression forms, including calls, field access, array constructors, + struct constructors, option/result constructors, observers, unwraps, match + patterns, comparisons, arithmetic, `string_len`, `std.string.len`, + `print_i32`, `print_string`, `print_bool`, `std.io.print_i32`, + `std.io.print_string`, `std.io.print_bool`, `std.io.print_f64`, exp-2 + `std.vec.i32.*` calls, exp-94 `std.vec.i64.*` calls, exp-103 + `std.vec.f64.*` calls, exp-99 `std.vec.string.*` calls, exp-3 host calls, exp-4 qualified enum + constructors, and exp-13 + `std.string.parse_i32_result` calls, and exp-15 `std.result.*` helper calls + +Nested promoted forms have these stable shapes: + +- `if`, `while`, `unsafe`, and `match` use their existing multiline + indentation and do not collapse to a single line because their contents are + short +- calls, field access, arrays, struct constructors, option/result constructors, + option/result observers, option/result unwraps, `string_len`, + `std.string.len`, legacy print aliases, `std.io.print_*` calls, and exp-2 + `std.vec.i32.*` calls, exp-94 `std.vec.i64.*` calls, exp-103 + `std.vec.f64.*` calls, exp-99 `std.vec.string.*` calls, exp-3 host calls, + and exp-4 qualified enum + constructors, exp-13 `std.string.parse_i32_result` calls, exp-15 + `std.result.*` helper calls, exp-20 `std.io.print_f64` calls, exp-21 + `std.io.print_i64` calls, exp-22 `std.num.*` conversion calls, exp-23 + `std.num.i64_to_i32_result` calls, exp-24 integer-to-string calls, and + exp-25 `std.string.parse_i64_result` calls, and exp-26 + `std.num.f64_to_string` calls, and exp-27 + `std.num.f64_to_i32_result` calls, and exp-28 + `std.string.parse_f64_result` calls, and exp-31 + `std.num.f64_to_i64_result` calls, and exp-34 + `std.string.parse_bool_result` calls remain inline when used as expressions + +Unsupported formatter positions must produce diagnostics using the v1 +`slovo.diagnostic` schema. A formatter diagnostic must identify the unsupported +position with a machine-readable code, source span, line/column range, and a +message or hint that names the accepted v1 comment/layout positions. + +### 9.5 Glagol Promotion Obligations + +Before a promoted v1 boundary is treated as complete, Glagol must provide and +keep an explicit coverage inventory. This is a Slovo-to-Glagol contract +obligation; it does not by itself claim that the current Glagol implementation +has already completed every listed fixture. + +- golden machine diagnostic fixtures using `slovo.diagnostic` version `1` + for every promoted v1 boundary and every current explicitly rejected + source-reachable v1 boundary +- a diagnostic fixture inventory that maps each explicitly rejected v1 + boundary to the golden fixture and diagnostic code that prove it +- tests that assert the diagnostic schema and version markers are present +- textual lowering-inspector golden fixtures for every promoted v1 feature + family: + - add/canonical + - top-level tests + - locals + - if + - while + - struct + - struct value flow + - array + - array value flow + - option/result + - option/result flow + - option/result payload + - option/result match + - string print + - string value flow + - print bool + - standard runtime alpha + - unsafe + - formatter comments/stability, only where inspector mode applies to source + accepted by lowering +- artifact manifest examples or tests for successful compiler output, + diagnostic failure, and test-report output +- source-span preservation through the compiler data needed by diagnostics and + LLVM emission +- no source-reachable panic for supported v1 source or for explicitly + unsupported source at promoted boundaries + +Future feature families must extend both inventories before promotion: accepted +source needs textual lowering-inspector golden fixtures, and every new +explicitly rejected source-reachable boundary needs golden machine-diagnostic +coverage. + +### 9.6 v1.7 Tooling Extensions + +v1.7 hardens developer experience tooling while preserving all v1.6 language +semantics. The promoted tooling extensions are: + +- project scaffolding with `glagol new [--name ]` +- formatter check mode with `glagol fmt --check ` +- formatter write mode with `glagol fmt --write ` +- deterministic Markdown documentation generation with + `glagol doc -o ` +- a local release-gate script contract + +Project scaffolding emits the existing v1.3 project shape: `slovo.toml` plus +flat source modules under `src`. The generated main module must be testable +using only already-promoted language forms. Scaffolding must reject non-empty +target directories without overwriting existing content. + +Formatter check/write modes use the canonical formatter rules from section +9.4. Project formatting applies only to immediate `.slo` modules under the +manifest source root and uses deterministic v1.3 module ordering. Plain +`glagol fmt ` continues to write formatted source to stdout. + +Documentation generation records modules, imports/exports, structs, functions, +and tests as deterministic Markdown. It is generated documentation, not +runtime reflection, typed-core reflection, debug metadata, DWARF, source maps, +or ABI/layout information. + +LSP, watch mode, daemon protocols, SARIF, debug adapters, stable debug +metadata, DWARF emission, and standalone source-map files remain deferred. + +## 10. Slice 6: Unsafe, Memory, And FFI + +Status: v1.6 reservation and gating contract. Raw-memory execution remains +deferred. + +Slovo v1 keeps v0 lexical `unsafe` as the only accepted unsafe boundary. +Lexical `unsafe` permits otherwise supported safe forms in an unsafe block; it +does not promote raw memory, pointer, allocation, unchecked indexing, or FFI +operations. + +v1.6 reserves the following compiler-known unsafe operation heads: + +- `alloc` +- `dealloc` +- `load` +- `store` +- `ptr_add` +- `unchecked_index` +- `reinterpret` +- `ffi_call` + +These names are recognized before ordinary user-call lookup. Safe code that +uses any reserved unsafe head must be rejected with `UnsafeRequired`. + +Code inside lexical `(unsafe ...)` that uses any reserved unsafe head must be +rejected with `UnsupportedUnsafeOperation` until a future release defines that +operation's syntax, type rules, lowering, runtime behavior, diagnostics, and +tests. + +User functions, imports, exports, and parameters must not shadow these +compiler-known unsafe heads. Such declarations must be rejected with a +structured duplicate/reserved-name diagnostic. + +The v1.6 memory direction is staged: safe values remain the default; future +memory work is expected to define affine ownership plus explicit unsafe +regions before raw memory can execute. v1.6 makes no stable ABI, layout, FFI, +allocator, ownership, or raw-memory execution promise. + +A future raw-memory or FFI contract must specify: + +- pointer types +- allocation and deallocation +- load and store +- pointer arithmetic +- unchecked indexing +- reinterpretation +- FFI call shape +- ownership of allocation failures and cleanup behavior +- ownership, affine/resource, and lifetime rules +- diagnostics beyond the v1.6 `UnsafeRequired` and + `UnsupportedUnsafeOperation` gates + +No stable ABI or layout promise is made in v1. Stable ABI and stable layout +promises remain deferred until a future spec explicitly promotes them. + +## 11. Deferred Until Future Beta Unless Promoted Explicitly + +- broad conformance suite, stable beta compatibility policy, semantic + versioning, and deprecation policy +- struct field mutation and whole-struct value mutation +- option/result mapping, equality, printing, payload families beyond the + current exp-10 `(result string i32)` slice and the exp-36/exp-75/exp-95/ + exp-100/exp-102/exp-109 concrete `(option i32)` / `(option i64)` / + `(option f64)` / `(option bool)` / `(option string)` source helper slices, + nested + option/result values, arrays or structs containing option/results, generic + payloads, enum payloads beyond exp-16 unary `i32` variants, exp-116 unary + direct scalar/string variants, and exp-121 unary current known + non-recursive struct variants, enum import behavior beyond exp-17 explicit + local export/import lists, enum values in containers or nested structs + beyond exp-18 direct fields, general payload ADTs, and user-catchable + exceptions +- unit printing and user-declared or stored `unit` values +- broader standard-runtime printing beyond v1.5 `std.io.print_i32`, + `std.io.print_string`, and `std.io.print_bool` +- `std.io.print_unit`, host IO beyond the exp-3 slice, exp-10 `*_result` + slice, and exp-12 stdin-result slice, networking, async IO, binary file + APIs, directory traversal, terminal control, platform abstraction, general + host error ADTs, stdin APIs beyond exp-12, parsing APIs beyond exp-13 + `std.string.parse_i32_result`, exp-25 `std.string.parse_i64_result`, exp-28 + `std.string.parse_f64_result`, and exp-34 exact lowercase + `std.string.parse_bool_result`, + result helper payload families beyond the explicitly listed + `(result i32 i32)`, `(result i64 i32)`, `(result string i32)`, exp-28 + returned `(result f64 i32)`, and exp-34 returned `(result bool i32)` + families, generic option helpers beyond exp-36/exp-75/exp-95/exp-100/ + exp-102/exp-109 + concrete option source helpers, + randomness beyond the exp-11 target, time beyond + the exp-8 host time/sleep target, vectors/collections beyond the exp-2 + `(vec i32)`, exp-94 `(vec i64)`, exp-99 `(vec string)`, and exp-103 + `(vec f64)` targets, + user-defined standard modules, overloading, and generic standard-library APIs +- numeric primitives and conversions beyond exp-20 direct `f64`, exp-21 + direct `i64`, exp-22 explicit `std.num.i32_to_i64`, + `std.num.i32_to_f64`, and `std.num.i64_to_f64` calls, exp-23 + `std.num.i64_to_i32_result`, exp-24 `std.num.i32_to_string` and + `std.num.i64_to_string`, exp-25 `std.string.parse_i64_result`, exp-27 + `std.num.f64_to_i32_result`, exp-28 `std.string.parse_f64_result`, exp-31 + `std.num.f64_to_i64_result`, and + existing `i32`/`bool`, including `f32`, + unsigned integers, narrower integer widths, `char`, `bytes`, `decimal`, + numeric casts or conversions beyond exp-22 explicit widening calls, the + exp-23 checked `i64 -> i32` result call, the exp-27 checked + `f64 -> i32` result call, and the exp-31 checked `f64 -> i64` result call, + implicit promotion, mixed `i32`/`i64`/`f64` + arithmetic, generic numeric operators, numeric container families beyond the + current fixed direct-scalar arrays, current concrete vec families, current + concrete option/result families, and current direct numeric enum payloads, + numeric struct fields beyond exp-29 direct `i64` and finite `f64` fields, + random floats or random + `i64`, f64 formatting beyond exp-26 finite decimal text, + locale/base/radix/grouping/padding formatting controls, generic + format/display traits, and stable numeric formatting or ABI/layout promises +- standard math source helpers beyond the exp-39 `std/math.slo` helper slice + and exp-44 explicit `std.math` import path, including trigonometry, `sqrt`, + `pow`, exponentials, logarithms, rounding APIs, generic math, overloads, + traits, automatic std imports, workspace dependency syntax for std, + package std dependency declarations, and mixed numeric helper families +- standard result source helpers beyond the released exp-33, exp-35, + exp-74, and exp-109 `std/result.slo` concrete helper slices, exp-45 + explicit `std.result` project import path, and exp-46 workspace + source-search model, including generic `std.result.map`, generic + `std.result.unwrap_or`, `std.result.and_then`, broader + result/option bridge or transpose/flatten helpers, generic result helpers, + and new result payload families +- standard option source helpers beyond the released exp-36/exp-75/exp-95/ + exp-100/exp-102/exp-109 `std/option.slo` concrete helper slices, exp-45 + explicit `std.option` project import path, and exp-46 workspace + source-search model, including generic option helpers, + option mapping/chaining, broader option/result bridge helpers, and option + payload families beyond `i32`, `i64`, `f64`, `bool`, and `string` +- standard time source helpers beyond the released exp-37 `std/time.slo` + facade wrappers, including wall-clock/calendar/timezone APIs, + high-resolution timers, async timers, cancellation, and scheduling + guarantees +- generic vectors, vector element types other than `i32`, vector mutation, + vector literals beyond empty/append construction, `push`, nested vectors, + vectors in arrays/structs/options/results, iterators, slices, maps, sets, + user-visible vector deallocation, and stable vector ABI/layout/helper symbols +- string concatenation beyond exp-1 `std.string.concat`, parsing beyond + exp-13 `std.string.parse_i32_result`, exp-25 + `std.string.parse_i64_result`, and exp-28 + `std.string.parse_f64_result`, plus exp-34 exact lowercase + `std.string.parse_bool_result`, indexing, slicing, string containers beyond + the current direct struct fields, current fixed string arrays, current + direct fixed-array struct fields, and current concrete option/result + families, user-visible string allocation or deallocation, Unicode length or + digit semantics, and stable string ABI/layout +- pointer types, allocation, deallocation, load, store, pointer arithmetic, + reinterpretation, unchecked indexing, raw memory operations, and FFI +- stable ABI and stable layout promises +- macros +- generics +- package management +- ownership and affine resources +- concurrency +- direct machine-code backend +- stable cross-language ABI diff --git a/docs/language/STANDARD_RUNTIME.md b/docs/language/STANDARD_RUNTIME.md new file mode 100644 index 0000000..949b742 --- /dev/null +++ b/docs/language/STANDARD_RUNTIME.md @@ -0,0 +1,143 @@ +# Slovo Standard Runtime Catalog + +Status: standard-runtime catalog through exp-101 over the released exp-14 +conformance baseline. The latest Slovo experimental alpha release is +`exp-101`, Standard Vec String Option And Transform Helpers Alpha. exp-29, exp-30, exp-32, +exp-33, and exp-35 through exp-93 add no compiler-known `std.*` names; +exp-31 adds exactly one checked numeric conversion name; exp-34 adds exactly +one strict bool parse name; exp-94 adds exactly four concrete `(vec i64)` +vector names; exp-95 through exp-98 add no compiler-known `std.*` names; and +exp-99 adds exactly four concrete `(vec string)` vector names. exp-100 and +exp-101 add no new compiler-known `std.*` names. + +This document catalogs promoted compiler-known `std.*` operations only. It +does not add source syntax, runtime APIs, standard library functions, manifest +schema versions, ABI/layout promises, runtime headers/libraries, or beta +maturity. + +The catalog is closed to names promoted through exp-101. exp-29, exp-30, +exp-32, exp-33, and exp-35 through exp-93 add no new standard-runtime +operation names. exp-32/exp-39/exp-56/exp-57 `std/math.slo` helpers, +exp-33/exp-35 `std/result.slo` helpers, exp-36 `std/option.slo` helpers, +exp-37 `std/time.slo` wrappers, exp-38 `std/random.slo`, `std/env.slo`, and +`std/fs.slo` wrappers, exp-48 `std/string.slo` and `std/num.slo` facades, +exp-49 `std/io.slo` facades, exp-52 `std/process.slo` facades, and exp-53/55 +`std/cli.slo` facades are source-authored functions, not compiler-known +`std.*` operations. exp-44 through exp-55 add explicit source-search and +discovery gates for existing source modules; exp-56 through exp-93 add +language/source-helper slices. exp-94 adds `std.vec.i64.empty`, +`std.vec.i64.append`, `std.vec.i64.len`, and `std.vec.i64.index`; exp-95 +broadens option language/source-helper support only and adds no new catalog +entries; exp-99 adds `std.vec.string.empty`, `std.vec.string.append`, +`std.vec.string.len`, and `std.vec.string.index`; exp-100 broadens option +language/source-helper support only and adds no new catalog entries; exp-101 +broadens vec-string source-helper support only and adds no new catalog +entries. +Legacy compatibility +aliases such as `print_i32`, `print_string`, `print_bool`, and `string_len` +are not `std.*` names and are therefore excluded from this catalog. Legacy +unqualified result helper names remain compatibility syntax; the preferred +source-level result helper names are the `std.result.*` names cataloged below. + +## Catalog + +| Operation | Signature | Release | Fixture | Behavior/trap/result note | Manifest note | Deferrals | +| --- | --- | --- | --- | --- | --- | --- | +| `std.io.print_i32` | `(i32) -> unit` | v1.5 | `examples/supported/standard-runtime.slo` | Prints the `i32` using the legacy stdout behavior and returns builtin `unit`. | Uses existing standard-runtime usage recording if present; no schema change. | Unit/composite printing, formatting controls, stable helper ABI/layout. | +| `std.io.print_string` | `(string) -> unit` | v1.5 | `examples/supported/standard-runtime.slo` | Prints the string using the legacy stdout behavior and returns builtin `unit`. | Uses existing standard-runtime usage recording if present; no schema change. | String formatting controls, Unicode policy beyond existing string bytes, stable helper ABI/layout. | +| `std.io.print_bool` | `(bool) -> unit` | v1.5 | `examples/supported/standard-runtime.slo` | Prints `true` or `false` using the legacy stdout behavior and returns builtin `unit`. | Uses existing standard-runtime usage recording if present; no schema change. | Broader value printing and formatting controls. | +| `std.io.print_f64` | `(f64) -> unit` | exp-20 | `examples/supported/f64-numeric-primitive.slo` | Prints one implementation-owned finite `f64` textual representation plus a newline and returns builtin `unit`. | Uses existing standard-runtime usage recording if present; no schema change. | Formatting controls, stable printed digits, NaN/infinity policy, stable helper ABI/layout. | +| `std.io.print_i64` | `(i64) -> unit` | exp-21 | `examples/supported/i64-numeric-primitive.slo` | Prints one signed decimal `i64` textual representation plus a newline and returns builtin `unit`. | Uses existing standard-runtime usage recording if present; no schema change. | Formatting controls, unsigned/narrower integer printing, stable helper ABI/layout. | +| `std.num.i32_to_i64` | `(i32) -> i64` | exp-22 | `examples/supported/numeric-widening-conversions.slo` | Explicitly widens a signed `i32` value to the corresponding signed `i64` value. | Uses existing standard-runtime usage recording if present; no schema change. | Implicit promotion, narrowing conversions beyond exp-23 checked i64-to-i32 result, cast syntax, stable helper ABI/layout. | +| `std.num.i32_to_f64` | `(i32) -> f64` | exp-22 | `examples/supported/numeric-widening-conversions.slo` | Explicitly converts a signed `i32` value to a finite `f64` value representing that integer. | Uses existing standard-runtime usage recording if present; no schema change. | Implicit promotion, rounding-mode controls, generic casts, stable helper ABI/layout. | +| `std.num.i64_to_f64` | `(i64) -> f64` | exp-22 | `examples/supported/numeric-widening-conversions.slo` | Explicitly converts a signed `i64` value to a finite `f64`; exact preservation is not promised for every `i64`. | Uses existing standard-runtime usage recording if present; no schema change. | Implicit promotion, checked lossy f64 conversion APIs, rounding-mode controls, stable helper ABI/layout. | +| `std.num.i64_to_i32_result` | `(i64) -> (result i32 i32)` | exp-23 | `examples/supported/checked-i64-to-i32-conversion.slo` | Returns `ok value` when the signed `i64` input is in the signed `i32` range, otherwise returns `err 1`; range failure does not trap. | Uses existing standard-runtime usage recording if present; no schema change. | Other narrowing conversions beyond exp-27 checked f64-to-i32 result, cast syntax, `std.num.cast`, checked cast generics, stable helper ABI/layout. | +| `std.num.i32_to_string` | `(i32) -> string` | exp-24 | `examples/supported/integer-to-string.slo` | Returns the decimal signed ASCII string for the input `i32`; negative values include `-`, non-negative values have no leading `+`. | Uses existing standard-runtime usage recording if present; no schema change. | f64 formatting beyond exp-26 finite decimal text, parse APIs beyond the released i32/i64/f64/bool result calls, locale/base/radix/grouping/padding controls, generic format/display, implicit conversion, stable helper ABI/layout/ownership. | +| `std.num.i64_to_string` | `(i64) -> string` | exp-24 | `examples/supported/integer-to-string.slo` | Returns the decimal signed ASCII string for the input `i64`; negative values include `-`, non-negative values have no leading `+`. | Uses existing standard-runtime usage recording if present; no schema change. | f64 formatting beyond exp-26 finite decimal text, parse APIs beyond the released i32/i64/f64/bool result calls, locale/base/radix/grouping/padding controls, generic format/display, implicit conversion, stable helper ABI/layout/ownership. | +| `std.num.f64_to_string` | `(f64) -> string` | exp-26 | `examples/supported/f64-to-string.slo` | Returns finite decimal ASCII string text for promoted finite `f64` inputs; fixture values are `0.0`, `3.5`, `-1.5`, and `10.0`. | Uses existing standard-runtime usage recording if present; no schema change. | f32, f64 parse, generic parse/format/display/interpolation, locale/base/radix/grouping/padding/precision controls, stable NaN/infinity text, implicit conversion, stable helper ABI/layout/ownership. | +| `std.num.f64_to_i32_result` | `(f64) -> (result i32 i32)` | exp-27 | `examples/supported/f64-to-i32-result.slo` | Returns `ok value` only when the `f64` input is finite, exactly integral, and in the signed `i32` range; returns `err 1` for non-finite, fractional, or out-of-range input without trapping. | Uses existing standard-runtime usage recording if present; no schema change. | Unchecked f64-to-i32, casts/cast syntax, generic `cast_checked`, f32, unsigned/narrower integer families, f64 parse, mixed numeric arithmetic, numeric containers, stable helper ABI/layout/ownership. | +| `std.num.f64_to_i64_result` | `(f64) -> (result i64 i32)` | exp-31 | `examples/supported/f64-to-i64-result.slo` | Returns `ok value` only when the `f64` input is finite, exactly integral, and in the signed `i64` range; returns `err 1` for non-finite, fractional, or out-of-range input without trapping. Conservative fixture values avoid pinning every `f64`/`i64` edge. | Uses existing standard-runtime usage recording if present; no schema change. | Unchecked casts, unchecked f64-to-i64, cast syntax, generic `cast_checked`, f32, unsigned/narrower integer families, mixed numeric arithmetic, broad math, stable helper ABI/layout/ownership. | +| `std.string.len` | `(string) -> i32` | v1.5 | `examples/supported/standard-runtime.slo` | Returns the existing decoded byte-count length used by legacy `string_len`. | Uses existing standard-runtime usage recording if present; no schema change. | Unicode scalar/grapheme length, slicing, indexing, stable string ABI/layout. | +| `std.string.concat` | `(string, string) -> string` | exp-1 | `examples/supported/owned-string-concat.slo` | Returns an immutable runtime-owned string; allocation failure traps as `slovo runtime error: string allocation failed`. | Uses existing standard-runtime usage recording if present; no concat-specific schema field. | Mutable strings, string containers, user-visible allocation/deallocation, stable string ABI/layout. | +| `std.vec.i32.empty` | `() -> (vec i32)` | exp-2 | `examples/supported/vec-i32.slo` | Returns an empty immutable runtime-owned `(vec i32)`. | Uses existing standard-runtime usage recording if present; no vector-specific schema field. | Generic vectors, element families beyond `i32`, `i64`, and `string`, vector mutation, stable vector ABI/layout. | +| `std.vec.i32.append` | `((vec i32), i32) -> (vec i32)` | exp-2 | `examples/supported/vec-i32.slo` | Returns a new immutable vector containing the input elements and appended value; allocation failure traps with the exp-2 vector allocation message. | Uses existing standard-runtime usage recording if present; no vector-specific schema field. | Mutation, `push`, capacity APIs, user deallocation, stable vector ABI/layout. | +| `std.vec.i32.len` | `((vec i32)) -> i32` | exp-2 | `examples/supported/vec-i32.slo` | Returns vector length as `i32`. | Uses existing standard-runtime usage recording if present; no vector-specific schema field. | Generic length APIs and stable vector ABI/layout. | +| `std.vec.i32.index` | `((vec i32), i32) -> i32` | exp-2 | `examples/supported/vec-i32.slo` | Returns the indexed value; out-of-bounds access traps with the exp-2 vector bounds message. | Uses existing standard-runtime usage recording if present; no vector-specific schema field. | Slices/views, iterators, mutation, stable vector ABI/layout. | +| `std.vec.i64.empty` | `() -> (vec i64)` | exp-94 | `examples/supported/vec-i64.slo` | Returns an empty immutable runtime-owned `(vec i64)`. | Uses existing standard-runtime usage recording if present; no vector-specific schema field. | Generic vectors, element families beyond `i32`, `i64`, and `string`, vector mutation, stable vector ABI/layout. | +| `std.vec.i64.append` | `((vec i64), i64) -> (vec i64)` | exp-94 | `examples/supported/vec-i64.slo` | Returns a new immutable vector containing the input elements and appended value; allocation failure traps with the existing vector allocation message. | Uses existing standard-runtime usage recording if present; no vector-specific schema field. | Mutation, `push`, capacity APIs, user deallocation, stable vector ABI/layout. | +| `std.vec.i64.len` | `((vec i64)) -> i32` | exp-94 | `examples/supported/vec-i64.slo` | Returns vector length as `i32`. | Uses existing standard-runtime usage recording if present; no vector-specific schema field. | Generic length APIs and stable vector ABI/layout. | +| `std.vec.i64.index` | `((vec i64), i32) -> i64` | exp-94 | `examples/supported/vec-i64.slo` | Returns the indexed value; out-of-bounds access traps with the existing vector bounds message. | Uses existing standard-runtime usage recording if present; no vector-specific schema field. | Slices/views, iterators, mutation, stable vector ABI/layout. | +| `std.vec.string.empty` | `() -> (vec string)` | exp-99 | `examples/supported/vec-string.slo` | Returns an empty immutable runtime-owned `(vec string)`. | Uses existing standard-runtime usage recording if present; no vector-specific schema field. | Generic vectors, element families beyond `i32`, `i64`, and `string`, vector mutation, stable vector ABI/layout. | +| `std.vec.string.append` | `((vec string), string) -> (vec string)` | exp-99 | `examples/supported/vec-string.slo` | Returns a new immutable vector containing the input elements and appended string; allocation failure traps with the existing vector allocation message. | Uses existing standard-runtime usage recording if present; no vector-specific schema field. | Mutation, `push`, capacity APIs, user deallocation, stable vector ABI/layout. | +| `std.vec.string.len` | `((vec string)) -> i32` | exp-99 | `examples/supported/vec-string.slo` | Returns vector length as `i32`. | Uses existing standard-runtime usage recording if present; no vector-specific schema field. | Generic length APIs and stable vector ABI/layout. | +| `std.vec.string.index` | `((vec string), i32) -> string` | exp-99 | `examples/supported/vec-string.slo` | Returns the indexed value; out-of-bounds access traps with the existing vector bounds message. | Uses existing standard-runtime usage recording if present; no vector-specific schema field. | Slices/views, iterators, mutation, stable vector ABI/layout. | +| `std.io.eprint` | `(string) -> unit` | exp-3 | `examples/supported/host-io.slo` | Writes the string to stderr without appending a newline and returns builtin `unit`. | Uses existing standard-runtime usage recording if present; no host-IO schema bump. | Formatting controls, terminal APIs, async IO, stable helper ABI/layout. | +| `std.process.argc` | `() -> i32` | exp-3 | `examples/supported/host-io.slo` | Returns process argument count as `i32`. | Uses existing standard-runtime usage recording if present; no process-specific schema field. | Rich process APIs, argv encoding guarantees, stable helper ABI/layout. | +| `std.process.arg` | `(i32) -> string` | exp-3 | `examples/supported/host-io.slo` | Returns zero-based process argument; out-of-range access traps as `slovo runtime error: process argument index out of bounds`. | Uses existing standard-runtime usage recording if present; no process-specific schema field. | Result-based errors are separate exp-10 names; rich process APIs remain deferred. | +| `std.env.get` | `(string) -> string` | exp-3 | `examples/supported/host-io.slo` | Returns environment value, or the empty string when missing. | Uses existing standard-runtime usage recording if present; no env-specific schema field. | Result-based errors are separate exp-10 names; env mutation and enumeration remain deferred. | +| `std.fs.read_text` | `(string) -> string` | exp-3 | `examples/supported/host-io.slo` | Returns file text; host read failure traps with the exp-3 file read message. | Uses existing standard-runtime usage recording if present; no fs-specific schema field. | Result-based errors are separate exp-10 names; binary, directory, streaming, async APIs remain deferred. | +| `std.fs.write_text` | `(string, string) -> i32` | exp-3 | `examples/supported/host-io.slo` | Writes text, returning `0` on success and `1` on host failure. | Uses existing standard-runtime usage recording if present; no fs-specific schema field. | Result-based errors are separate exp-10 names; binary, directory, streaming, async APIs remain deferred. | +| `std.time.monotonic_ms` | `() -> i32` | exp-8 | `examples/supported/time-sleep.slo` | Returns non-negative host monotonic elapsed milliseconds with implementation-owned epoch; host unavailability may trap as `slovo runtime error: monotonic time unavailable`. | Uses existing standard-runtime usage recording if present; no time-specific schema field. | Wall-clock/calendar/timezone APIs, high-resolution timers, scheduling guarantees, stable clock ABI/layout. | +| `std.time.sleep_ms` | `(i32) -> unit` | exp-8 | `examples/supported/time-sleep.slo` | Sleeps for a non-negative millisecond duration; `0` is valid. Negative runtime values trap as `slovo runtime error: sleep_ms negative duration`; host sleep failure may trap as `slovo runtime error: sleep failed`. | Uses existing standard-runtime usage recording if present; no time-specific schema field. | Positive-duration conformance timing assertions, async timers, cancellation, scheduling guarantees. | +| `std.process.arg_result` | `(i32) -> (result string i32)` | exp-10 | `examples/supported/host-io-result.slo` | Returns `ok` with argument text when in range, or `err 1` when out of range. | Uses existing standard-runtime usage recording if present; no exp-10-specific schema field. | Rich host error ADTs, platform codes, process API breadth, stable helper ABI/layout. | +| `std.env.get_result` | `(string) -> (result string i32)` | exp-10 | `examples/supported/host-io-result.slo` | Returns `ok` with environment value when present, or `err 1` when missing. | Uses existing standard-runtime usage recording if present; no exp-10-specific schema field. | Rich host error ADTs, env mutation/enumeration, platform codes. | +| `std.fs.read_text_result` | `(string) -> (result string i32)` | exp-10 | `examples/supported/host-io-result.slo` | Returns `ok` with file contents on success, or `err 1` on ordinary host read failure. | Uses existing standard-runtime usage recording if present; no exp-10-specific schema field. | Binary, directory, streaming, async APIs, rich host error ADTs. | +| `std.fs.write_text_result` | `(string, string) -> (result i32 i32)` | exp-10 | `examples/supported/host-io-result.slo` | Returns `ok 0` on success, or `err 1` on ordinary host write failure. | Uses existing standard-runtime usage recording if present; no exp-10-specific schema field. | Binary, directory, streaming, async APIs, rich host error ADTs. | +| `std.random.i32` | `() -> i32` | exp-11 | `examples/supported/random.slo` | Returns a non-negative implementation-owned random `i32`; unavailable randomness traps as `slovo runtime error: random i32 unavailable`. | Uses existing standard-runtime usage recording if present; no randomness-specific schema field. | Seed APIs, crypto/security promises, ranges, bytes, floats, UUIDs, stable RNG ABI/layout. | +| `std.io.read_stdin_result` | `() -> (result string i32)` | exp-12 | `examples/supported/stdin-result.slo` | Reads remaining stdin as text; success returns `ok` text, ordinary EOF with no bytes returns `ok ""`, ordinary host/input failure returns `err 1`. | Uses existing standard-runtime usage recording if present; no stdin-specific schema field. | Trap stdin, line APIs, prompts, terminal mode, binary/streaming/async stdin. | +| `std.string.parse_i32_result` | `(string) -> (result i32 i32)` | exp-13 | `examples/supported/string-parse-i32-result.slo` | Parses an entire ASCII decimal signed `i32`; success returns `ok` value, ordinary parse failure returns `err 1`. | Uses existing standard-runtime usage recording if present; no parse-specific schema field. | Trap parse, floats/bools/bytes, trimming, locale/radix/underscore/plus parsing, rich parse errors, Unicode digits, slicing/indexing. | +| `std.string.parse_i64_result` | `(string) -> (result i64 i32)` | exp-25 | `examples/supported/string-parse-i64-result.slo` | Parses an entire non-empty ASCII decimal signed `i64` with optional leading `-`; success returns `ok` value, parse failure or out-of-range returns `err 1` without trapping. | Uses existing standard-runtime usage recording if present; no parse-specific schema field. | Generic parse, leading plus, trimming, locale/radix/base-prefix/underscore parsing, rich parse errors, Unicode digits, stable helper ABI/layout/ownership. | +| `std.string.parse_f64_result` | `(string) -> (result f64 i32)` | exp-28 | `examples/supported/string-parse-f64-result.slo` | Parses complete finite ASCII decimal `f64` text in the narrow `-?digits.digits` shape; success returns `ok` value, ordinary parse failure, non-finite text, unsupported leading/trailing characters, or out-of-domain input returns `err 1` without trapping. | Uses existing standard-runtime usage recording if present; no parse-specific schema field. | Generic parse, trap-based f64 parse, leading `+`, exponent notation, `.5`, `5.`, bool parse beyond exp-34 exact lowercase true/false, string/bytes parse, locale parsing, Unicode digits, underscores, rich parse errors, stable helper ABI/layout/ownership, result genericity, f64 containers. | +| `std.string.parse_bool_result` | `(string) -> (result bool i32)` | exp-34 | `examples/supported/string-parse-bool-result.slo` | Parses exactly complete ASCII lowercase `true` or `false`; success returns `ok true` or `ok false`, and ordinary parse failure returns `err 1` without trapping. | Uses existing standard-runtime usage recording if present; no parse-specific schema field. | Generic parse, trap-based bool parse, bool parsing beyond exact lowercase true/false, string/bytes parse, trimming, case-insensitive parsing, locale/Unicode parsing, numeric boolean parsing, rich parse errors, stable helper ABI/layout/ownership. | +| `std.result.is_ok` | `((result i32 i32)) -> bool`; `((result i64 i32)) -> bool`; `((result string i32)) -> bool`; `((result f64 i32)) -> bool`; `((result bool i32)) -> bool` | exp-15, extended exp-25, exp-28, and exp-34 | `examples/supported/result-helpers.slo`; `examples/supported/string-parse-i64-result.slo`; `examples/supported/string-parse-f64-result.slo`; `examples/supported/string-parse-bool-result.slo` | Preferred source-level ok-tag observer for the supported concrete result families. | No result-helper-specific manifest field; schema version remains `1`. | Generic results, result payload families beyond the listed concrete families, option helpers, mapping/chaining, stable helper ABI/layout. | +| `std.result.is_err` | `((result i32 i32)) -> bool`; `((result i64 i32)) -> bool`; `((result string i32)) -> bool`; `((result f64 i32)) -> bool`; `((result bool i32)) -> bool` | exp-15, extended exp-25, exp-28, and exp-34 | `examples/supported/result-helpers.slo`; `examples/supported/string-parse-i64-result.slo`; `examples/supported/string-parse-f64-result.slo`; `examples/supported/string-parse-bool-result.slo` | Preferred source-level err-tag observer for the supported concrete result families. | No result-helper-specific manifest field; schema version remains `1`. | Generic results, result payload families beyond the listed concrete families, option helpers, mapping/chaining, stable helper ABI/layout. | +| `std.result.unwrap_ok` | `((result i32 i32)) -> i32`; `((result i64 i32)) -> i64`; `((result string i32)) -> string`; `((result f64 i32)) -> f64`; `((result bool i32)) -> bool` | exp-15, extended exp-25, exp-28, and exp-34 | `examples/supported/result-helpers.slo`; `examples/supported/string-parse-i64-result.slo`; `examples/supported/string-parse-f64-result.slo`; `examples/supported/string-parse-bool-result.slo` | Preferred source-level ok-payload extraction; traps with the existing `unwrap_ok on err` behavior when applied to `err`. | No result-helper-specific manifest field; schema version remains `1`. | Generic results, result payload families beyond the listed concrete families, generic `std.result.unwrap_or`, mapping/chaining, stable helper ABI/layout. | +| `std.result.unwrap_err` | `((result i32 i32)) -> i32`; `((result i64 i32)) -> i32`; `((result string i32)) -> i32`; `((result f64 i32)) -> i32`; `((result bool i32)) -> i32` | exp-15, extended exp-25, exp-28, and exp-34 | `examples/supported/result-helpers.slo`; `examples/supported/string-parse-i64-result.slo`; `examples/supported/string-parse-f64-result.slo`; `examples/supported/string-parse-bool-result.slo` | Preferred source-level err-payload extraction; traps with the existing `unwrap_err on ok` behavior when applied to `ok`. | No result-helper-specific manifest field; schema version remains `1`. | Generic results, result payload families beyond the listed concrete families, generic `std.result.unwrap_or`, mapping/chaining, stable helper ABI/layout. | + +## Manifest Rule + +The artifact manifest remains `slovo.artifact-manifest` schema version `1`. +If the existing implementation records standard-runtime usage, it records the +names above through the same additive structure. If it does not, this catalog +does not introduce one-off metadata for any operation or release. + +## Deferrals + +This catalog does not define broad standard-library modules, importable +standard packages, stable runtime helper names, C ABI symbols, headers, +libraries, semantic versioning, deprecation policy, or beta compatibility. +Those remain future beta work unless a later explicit contract promotes them. + +For the current exp-37 language baseline, it also does not define automatic +standard-library imports, compiler-loaded `std/` source, source replacement of +cataloged compiler-known calls, `f32`, generic parse or format APIs, +`f64` formatting beyond +`std.num.f64_to_string : (f64) -> string` finite decimal text, locale/base/ +radix/grouping/padding/precision controls, generic format/display/ +interpolation traits, implicit conversions, stable NaN/infinity text, mixed +numeric arithmetic/comparison, unchecked f64-to-i32, unchecked f64-to-i64, +unchecked casts, casts/cast syntax, generic `cast_checked`, narrowing or +checked numeric conversions beyond `std.num.i64_to_i32_result`, +`std.num.f64_to_i32_result`, and `std.num.f64_to_i64_result`, unsigned or +narrower integer families, broad math beyond the source-authored exp-32 +`std/math.slo` helper slice, trigonometry, `sqrt`, `pow`, +generic result helpers beyond the source-authored exp-35 `std/result.slo` +concrete helper slice, source constructors or general source `match` support +for `(result bool i32)` beyond compiler-supported fixture flow, +generic `std.result.unwrap_or`, `std.result.map`, +`std.result.and_then`, generic option helpers beyond the source-authored +exp-36/exp-75/exp-95/exp-100 `std/option.slo` concrete helper slices, option +payload families beyond `i32`, `i64`, and `string`, broad time APIs beyond +the exp-37 `std/time.slo` wrappers, +wall-clock/calendar/timezone APIs, high-resolution timers, async timers, +cancellation, scheduling guarantees, new result payload families, +numeric parse/format APIs beyond the two exp-24 +integer-to-string calls, the exp-26 f64-to-string call, and the released +`std.string.parse_i32_result` / +`std.string.parse_i64_result` / +`std.string.parse_f64_result` / +`std.string.parse_bool_result` result calls, bool parsing beyond exact +lowercase `true`/`false`, string/bytes parse, +underscores, rich parse errors, Unicode digit parsing, f64 containers, stable +standard-library APIs or implementation replacement, stable +ABI/layout/ownership, manifest schema changes, or beta maturity. diff --git a/docs/language/examples/README.md b/docs/language/examples/README.md new file mode 100644 index 0000000..bc244d2 --- /dev/null +++ b/docs/language/examples/README.md @@ -0,0 +1,639 @@ +# Slovo Examples + +Examples are split by current compiler support. + +## Compiler-Supported Targets + +Entries in this section are current compiler support under the matching +release notes. The current compiler-supported baseline is `1.0.0-beta`, +which absorbs the final exp-125 unsigned precursor scope alongside the +already promoted project/package, stdlib-source, collection, composite-data, +formatter, and diagnostics surface. + +`supported/add.slo` is the current executable promotion fixture. Glagol can parse it, +lower it, type-check it, emit LLVM for it, and cover that output with an automated +test. + +`supported/top-level-test.slo` is the current test-runner promotion fixture. +Glagol can parse it, lower it, type-check it, format it, inspect it, check tests, +run tests, keep normal LLVM emission free of test metadata, and cover those +behaviors with automated tests. + +`supported/local-variables.slo` is the current local-binding promotion fixture. +Glagol can parse it, lower it, type-check it, format it, emit LLVM for local +storage, run local-using top-level tests, and cover local diagnostics with +automated tests. exp-112 later broadens that same fixture with direct +immutable `bool` locals over the already promoted bool-expression surface. + +`supported/if.slo` is the current value-producing conditional promotion +fixture. Glagol can parse it, lower it, type-check it, format it, emit LLVM +branch/merge blocks, run if-using top-level tests, and cover conditional +diagnostics with automated tests. + +`supported/while.slo` is the current first-pass loop promotion fixture. Glagol +can parse it, lower it, type-check it, format it, emit LLVM loop blocks, run +loop-using top-level tests, and cover loop diagnostics with automated tests. + +`supported/struct.slo` is the current first-pass struct promotion fixture. +Glagol can parse it, lower it, type-check it, format it, emit LLVM for +immediate constructor field access, run struct-using top-level tests, and cover +struct diagnostics with automated tests. + +`supported/struct-value-flow.slo` is the first v1 struct value-flow promotion +fixture. Glagol can parse it, lower it, type-check it, format it, emit LLVM for +immutable struct locals, struct parameters, struct returns, calls returning +structs, and field access through stored struct values. + +`supported/array.slo` is the current first-pass array/indexing promotion +fixture. Glagol can parse it, lower it, type-check it, format it, emit LLVM for +immediate constructor indexing and immutable array local indexing, run +array-using top-level tests, and cover array diagnostics with automated tests. + +`supported/array-value-flow.slo` is the first v1 array value-flow and +dynamic-indexing promotion fixture. Glagol can parse it, lower it, type-check +it, format it, emit LLVM for immutable array locals initialized from matching +array-valued expressions, fixed `i32` array parameters, returns, calls +returning arrays, and runtime-checked dynamic indexing, and run array +value-flow top-level tests. + +`supported/array-direct-scalars.slo` broadens the fixed-array fixture surface +from `i32` only to direct scalar element families `i32`, `i64`, `f64`, and +`bool`. It keeps positive lengths only, immutable locals only, and checked +literal indexing only. + +`supported/array-direct-scalars-value-flow.slo` broadens the array value-flow +fixture surface to the same direct scalar array family. It keeps immutable +array locals, parameters, returns, calls returning arrays, and runtime-checked +dynamic indexing, while keeping index expressions `i32` only. + +`supported/array-string.slo` broadens the fixed-array fixture surface with one +concrete `string` element lane. It keeps positive lengths only, immutable +locals only, and checked literal indexing only. + +`supported/array-string-value-flow.slo` broadens the array value-flow fixture +surface to the same `string` array lane. It keeps immutable array locals, +parameters, returns, calls returning arrays, and runtime-checked dynamic +indexing, while keeping index expressions `i32` only. + +`supported/option-result.slo` is the current first-pass option/result +constructor promotion fixture. Glagol can parse it, lower it, type-check it, +format it, emit LLVM for direct constructor-return functions, and cover +option/result diagnostics with automated tests. + +`supported/option-result-flow.slo` is the first v1 option/result value-flow +promotion fixture. Glagol can parse it, lower it, type-check it, format it, +emit LLVM for immutable option/result locals, option/result parameters, calls +returning option/result values, and tag observation with `is_some`, `is_none`, +`is_ok`, and `is_err`. + +`supported/option-result-payload.slo` is the first v1 option/result +payload-access promotion fixture. Glagol can parse it, lower it, type-check it, +format it, emit LLVM for `unwrap_some`, `unwrap_ok`, and `unwrap_err`, runtime +trap on mismatched tags, and verify that tag checks happen before payload +access. + +`supported/option-result-match.slo` is the v1.4 option/result match promotion +fixture. Glagol can parse it, lower it, type-check it, format it, emit LLVM for +source-level `match` over `(option i32)` and `(result i32 i32)`, enforce +exhaustive arms, bind immutable arm payloads, type-check common arm result +types, and cover match diagnostics with automated tests. + +`supported/string-print.slo` is the first runtime string promotion fixture. +Glagol can parse it, lower it, type-check it, format it, emit LLVM for +immutable compiler-emitted NUL-terminated string literal storage, call the +legacy `print_string` runtime compatibility alias, and verify that the decoded +literal is printed with a trailing newline. + +`supported/string-value-flow.slo` is the v1.2 immutable string value-flow +promotion fixture. Glagol can parse it, lower it, type-check it, format it, +emit LLVM for string locals, parameters, returns, calls returning strings, +string equality, and string byte length with `string_len`, and run top-level +tests using string equality and length comparisons. + +`supported/print-bool.slo` is the v1.2 bool-printing promotion fixture. Glagol +can parse it, lower it, type-check it, format it, emit LLVM for the legacy +`print_bool` runtime compatibility alias, and verify `true` / `false` output +with trailing newlines. + +`supported/standard-runtime.slo` is the v1.5 standard-runtime alpha promotion +fixture. Glagol can parse it, lower it, type-check it, format it, emit LLVM for +compiler-known `std.io.print_i32`, `std.io.print_string`, `std.io.print_bool`, +and `std.string.len` calls, preserve legacy print and string-length aliases, +and reject unknown or unpromoted `std.*` calls with structured diagnostics. + +`supported/owned-string-concat.slo` is the exp-1 owned-runtime-string +compiler-supported fixture. The surface is compiler-known `std.string.concat` +with signature `(string, string) -> string`, producing an immutable +runtime-owned `string` usable by existing string equality, `std.string.len`, +`std.io.print_string`, string locals, parameters, returns, and calls. + +`supported/vec-i32.slo` is the exp-2 collections alpha compiler-supported +fixture. It covers one concrete `(vec i32)` type, +`std.vec.i32.empty`, `std.vec.i32.append`, `std.vec.i32.len`, +`std.vec.i32.index`, vector equality, immutable locals, parameters, returns, +calls, and top-level tests. + +`supported/vec-i64.slo` is the exp-94 concrete `(vec i64)` baseline +compiler-supported fixture. It covers `std.vec.i64.empty`, +`std.vec.i64.append`, `std.vec.i64.len`, `std.vec.i64.index`, vector +equality, append immutability, immutable locals, parameters, returns, calls, +and top-level tests. + +`supported/host-io.slo` is the exp-3 standard IO and host environment +compiler-supported fixture. It covers `std.io.eprint`, `std.process.argc`, +`std.process.arg`, +`std.env.get`, `std.fs.read_text`, and `std.fs.write_text` using the +stage-specific trap/status behavior. + +`supported/time-sleep.slo` is the exp-8 host time and sleep alpha conformance +fixture added by the exp-14 alignment release. It covers +`std.time.monotonic_ms: () -> i32`, `std.time.sleep_ms: (i32) -> unit`, +non-negative/structural monotonic-time checks, and deterministic `sleep_ms 0` +behavior. It makes no positive-duration timing assertion. + +`supported/enum-basic.slo` is the exp-4 user enum fixture. It covers a +payloadless enum declaration, zero-argument qualified constructors, +same-enum equality, immutable local/parameter/return/call flow, exhaustive +payloadless enum `match`, and top-level tests. + +`supported/enum-payload-i32.slo` is the exp-16 unary i32 enum payload fixture. +It covers an enum declaration mixing payloadless and unary `i32` payload +variants, qualified payload constructors, payload `match` arms with immutable +arm-local bindings, immutable local/parameter/return/call flow, same-enum +tag-plus-payload equality, top-level tests, and `main`. + +`supported/enum-struct-fields.slo` is the exp-18 enum struct field fixture. It +covers struct field declarations using current enum type names directly, +struct construction with payloadless and unary `i32` enum field values, field +access returning enum values, same-enum equality on field access, exhaustive +enum `match` on field access, immutable local/parameter/return/call flow, +top-level tests, and `main`. + +`supported/primitive-struct-fields.slo` is the exp-19 primitive struct field +fixture. It covers direct `i32`, `bool`, and immutable `string` struct fields, +construction, field access, immutable local/parameter/return/call flow, a bool +field in a predicate and test, string field equality, `std.string.len`, +top-level tests, and `main`. + +`supported/local-variables.slo` is also the exp-112 immutable bool locals +fixture. It now covers direct immutable `bool` locals declared with `let`, +initialized from already promoted bool literals, parameters, and calls +returning bool, with local references feeding existing predicate, return, +call, top-level test, and `main` positions. + +`supported/f64-numeric-primitive.slo` is the exp-20 f64 numeric primitive +fixture. It covers direct `f64` parameters, returns, immutable locals, calls, +decimal literals, same-type `f64` arithmetic/comparison, `std.io.print_f64`, +top-level tests, and `main` returning `i32`. + +`supported/i64-numeric-primitive.slo` is the exp-21 i64 numeric primitive +fixture. It covers direct `i64` parameters, returns, immutable locals, calls, +explicit signed decimal `i64` literal atoms, same-type `i64` +arithmetic/comparison, `std.io.print_i64`, top-level tests, and `main` +returning `i32`. + +`supported/numeric-widening-conversions.slo` is the exp-22 numeric widening +conversions alpha fixture. It covers exactly three explicit standard-runtime +calls: `std.num.i32_to_i64 : (i32) -> i64`, +`std.num.i32_to_f64 : (i32) -> f64`, and +`std.num.i64_to_f64 : (i64) -> f64`, with converted values feeding same-type +arithmetic/comparison, top-level tests, and `main` returning `i32`. + +`supported/checked-i64-to-i32-conversion.slo` is the exp-23 checked +i64-to-i32 conversion alpha fixture. It covers exactly one checked narrowing +standard-runtime call, +`std.num.i64_to_i32_result : (i64) -> (result i32 i32)`, with low/high +signed `i32` bounds and an in-range negative value returning `ok`, out-of-range +values returning `err 1`, existing `std.result.*` helpers, top-level tests, +and `main` returning `i32`. + +`supported/integer-to-string.slo` is the exp-24 integer to string alpha +fixture. It covers exactly two integer formatting standard-runtime calls, +`std.num.i32_to_string : (i32) -> string` and +`std.num.i64_to_string : (i64) -> string`, with decimal signed ASCII output +for `i32` zero/negative/high values and `i64` low/high/beyond-`i32` values, +existing string equality, `std.string.len`, `std.io.print_string`, top-level +tests, and `main` returning `i32`. + +`supported/string-parse-i64-result.slo` is the exp-25 string parse i64 result +alpha fixture. It covers exactly one string parsing standard-runtime call, +`std.string.parse_i64_result : (string) -> (result i64 i32)`, with complete +non-empty ASCII signed decimal input, `ok` results for zero, a negative value, +and signed `i64` low/high bounds, `err 1` for empty, leading-plus, and +out-of-range input, existing result observation/extraction forms, +`std.num.i64_to_string` roundtrips, top-level tests, and `main` returning +`i32`. + +`supported/f64-to-string.slo` is the exp-26 f64 to string alpha fixture. It +promotes exactly `std.num.f64_to_string : (f64) -> string`, with finite +decimal ASCII text for promoted finite `f64` fixture values. + +`supported/f64-to-i32-result.slo` is the exp-27 checked f64 to i32 result +alpha fixture. It promotes exactly +`std.num.f64_to_i32_result : (f64) -> (result i32 i32)`, returning `ok value` +only for finite, exactly integral inputs inside the signed `i32` range and +`err 1` for non-finite, fractional, or out-of-range input. + +`supported/string-parse-f64-result.slo` is the exp-28 string parse f64 result +alpha fixture. It promotes exactly +`std.string.parse_f64_result : (string) -> (result f64 i32)`, returning +`ok value` for complete finite ASCII decimal `f64` text and `err 1` for +ordinary parse failure, non-finite text, trailing or leading unsupported +characters, or out-of-domain input. + +`supported/numeric-struct-fields.slo` is the exp-29 numeric struct fields +alpha fixture. It promotes direct immutable struct field declarations whose +field types are exactly `i64` or finite `f64`, alongside already supported +direct `i32`, `bool`, immutable `string`, and current enum fields. It covers +constructors, immutable local/parameter/return/call flow, field access, +same-type arithmetic/comparison after field access, existing numeric +print/format helpers, top-level tests, and `main`. + +`formatter/std-source-layout-alpha.slo` is the exp-30 standard library source +layout alpha formatter fixture. It demonstrates the current helper shape while +the source layout itself lives under `std/`; it does not claim repo-root `std` +import/search support. + +`supported/f64-to-i64-result.slo` is the exp-31 checked f64 to i64 result +alpha fixture. It promotes exactly +`std.num.f64_to_i64_result : (f64) -> (result i64 i32)`, returning `ok value` +only for finite, exactly integral inputs inside the signed `i64` range and +`err 1` for non-finite, fractional, or out-of-range input. It uses a +formatter-stable conservative out-of-range fixture value, +`9223372036854776000.0`; the accepted exact `9223372036854775808.0` spelling +formats to that value. Detailed edge behavior near the `f64`/`i64` limits is +implementation-owned by Glagol tests. + +## Current Slovo-Side Beta Baseline + +The current Slovo-side beta baseline includes the final exp-125 unsigned +numeric and stdlib breadth scope. These files are now current +compiler-supported targets with matching Glagol coverage: + +- `supported/u32-numeric-primitive.slo` +- `supported/u64-numeric-primitive.slo` +- `supported/unsigned-integer-to-string.slo` +- `supported/string-parse-u32-result.slo` +- `supported/string-parse-u64-result.slo` +- widened explicit import projects under `projects/std-import-string/`, + `projects/std-import-num/`, `projects/std-import-io/`, + `projects/std-import-env/`, `projects/std-import-fs/`, + `projects/std-import-process/`, `projects/std-import-cli/`, + `projects/std-import-result/`, and `projects/std-import-option/` + +`supported/unsafe.slo` is the current lexical unsafe promotion fixture. Glagol +can parse it, lower it, type-check it, format it, emit LLVM for safe forms +inside the block, and cover unsafe-required diagnostics for raw unsafe +operation heads with automated tests. + +`supported/random.slo` is the exp-11 basic randomness alpha release fixture. +It covers only `std.random.i32: () -> i32` and a non-negative result check. + +`supported/stdin-result.slo` is the exp-12 standard input result alpha release +fixture. It covers only +`std.io.read_stdin_result: () -> (result string i32)`, existing +`(result string i32)` observers, unwrap, and `match`, with deterministic +test-runner `ok` behavior. + +`supported/string-parse-i32-result.slo` is the exp-13 string parse i32 result +alpha release fixture. It covers only +`std.string.parse_i32_result: (string) -> (result i32 i32)`, existing +`(result i32 i32)` observers, unwrap, and `match`, and structural composition +with `std.io.read_stdin_result` without assuming a particular stdin payload. + +`supported/result-helpers.slo` is the exp-15 result helper standard names alpha +fixture. It covers `std.result.is_ok`, `std.result.is_err`, +`std.result.unwrap_ok`, and `std.result.unwrap_err` for only +`(result i32 i32)` and `(result string i32)`. + +Supported examples are part of the current compiler contract. The supported +subset is only the syntax and behavior exercised by the released fixture set: +module, +top-level functions, top-level tests, top-level `i32` structs, `i32` parameters +and returns, direct `(option i32)`, `(option i64)`, and `(result i32 i32)` +constructor returns, +integer literals, parameter references, binary `+`, equality +comparison `=`, function calls, legacy compiler/runtime compatibility alias +`print_i32`, local `i32` bindings with `let` and `var`, immutable +`(array T N)` locals with direct array constructors when `T` is `i32`, `i64`, +`f64`, or `bool`, assignment to mutable locals with `set`, ordering comparison +`<` as a bool-producing condition, value-producing `if`, +first-pass `while`, first-pass struct construction and immediate field access, +first v1 struct value flow through immutable locals, parameters, returns, and +stored field access, +fixed direct scalar array constructors and literal indexing, +fixed direct scalar array value flow through immutable array locals initialized +from matching array-valued expressions, fixed array parameters, returns, calls +returning arrays, and runtime-checked dynamic indexing with `i32` index +expressions, +first-pass `(some i32 value)`, `(none i32)`, `(some i64 value)`, +`(none i64)`, `(ok i32 i32 value)`, and `(err i32 i32 value)` constructors as +direct function returns, +first v1 option/result value flow through immutable locals, parameters, calls, +and tag observation, +first v1 trap-based concrete option/result payload extraction with +`unwrap_some`, `unwrap_ok`, and `unwrap_err`, +v1.4 source-level `match` for `(option i32)`, `(option i64)`, and +`(result i32 i32)` values, with exhaustive arms, immutable arm-scoped payload +bindings, one-or-more expression arm bodies, and common arm result types, +first runtime string slice through immutable source string literals passed +directly to legacy compiler/runtime compatibility alias `print_string`, +v1.2 immutable string value flow through `let` locals, parameters, returns, +calls returning strings, string equality, string byte length through +`string_len`, top-level tests using string equality, and legacy +compiler/runtime compatibility alias `print_bool`, +v1.5 compiler-known standard-runtime names `std.io.print_i32`, +`std.io.print_string`, `std.io.print_bool`, and `std.string.len`, with legacy +`print_i32`, `print_string`, `print_bool`, and `string_len` retained as +compatibility aliases, +exp-1 `std.string.concat` fixture for immutable runtime-owned strings, +exp-2 `(vec i32)` values with empty/append/len/index/equality, +exp-3 host IO calls, +exp-4 payloadless enum forms, +exp-5 local package/workspace graph hygiene, +exp-6 C FFI scalar imports behind lexical `unsafe`, +exp-8 host time/sleep names with only `sleep_ms 0` and structural/ +non-negative `monotonic_ms` checks, +the exp-11 `std.random.i32` release, +the exp-12 `std.io.read_stdin_result` release, +the exp-13 `std.string.parse_i32_result` release, +the exp-15 preferred `std.result.*` helper names for the already supported +`(result i32 i32)` and `(result string i32)` families, with legacy +unqualified result helpers retained as compatibility syntax, +the exp-16 unary `i32` enum payload release, +the exp-18 direct enum struct field release, +the exp-19 direct primitive struct field release, +the exp-112 immutable bool locals release, +the exp-20 direct f64 numeric primitive release, +the exp-21 direct i64 numeric primitive release, +the exp-22 numeric widening conversion release, +the exp-23 checked i64-to-i32 conversion release, +the exp-24 integer to string release, +the exp-25 string parse i64 result release, +the exp-26 f64 to string release, +the exp-27 checked f64-to-i32 result release, +the exp-28 string parse f64 result release, +the exp-31 checked f64-to-i64 result release, +lexical `unsafe` expression blocks containing only otherwise supported safe v0 forms, +and final-expression returns. + +## Compatibility + +Frozen v0 compatibility fixtures live under `compat/v0/`. +`compat/v0/supported/` preserves the v0 supported program fixtures, and +`compat/v0/formatter/` preserves the v0 formatter fixtures. They are not the +place for new v1 promotion work. + +Compatibility fixtures change only through the migration process in +`../MIGRATION_POLICY.md`. Ordinary v1 additions belong in `supported/` and +`formatter/`. + +## Projects + +`projects/basic/` is the minimal v1.3 project-mode fixture. It contains a +`slovo.toml` manifest, a flat `src` source root, one exported local module, one +explicit import, top-level tests, and an entry `main` function. + +`projects/enum-imports/` is the exp-17 project enum import fixture. It exports +an enum from one module, imports that enum type into another module, and covers +payloadless and unary `i32` payload constructors, immutable locals, +signatures, calls/returns, same-enum equality, exhaustive enum `match`, +top-level tests, and `main`. Matching Glagol exp-17 gates are required before +broader compiler-support claims. + +Project fixtures are local module fixtures only. They are not package-manager, +dependency, workspace, registry, generated-code, ABI, or incremental-build +fixtures. + +## Workspaces + +`workspaces/exp-5-local/` is the minimal exp-5 local workspace fixture. +It contains one workspace manifest, two local package manifests, one local path +dependency, one package-qualified import, and one exported cross-package +function. + +This fixture is experimental compiler-supported after matching Glagol exp-5 +gates passed. It is not a remote registry, version solving, lockfile, +build-script, generated-code, published-package, semver, ABI, macro, +package-generic, or package re-export fixture. + +## FFI + +`ffi/exp-6-c-add/` is the minimal exp-6 C FFI scalar imports alpha fixture. It +contains one top-level imported C function declaration, a local C companion +implementing `c_add`, and a safe Slovo wrapper whose imported C call is visibly +inside lexical `(unsafe ...)`. + +This fixture is current experimental compiler-supported coverage after +matching Slovo and Glagol exp-6 gates. It is not a pointer, allocation, +deallocation, raw memory, ownership/lifetime, C export, generated-header, +generated-library, broad linker, stable ABI, or stable layout fixture. + +## Formatter + +Files in `formatter/` define canonical layout for the current strict supported +syntax. They do not add language features and they are not executable +promotion fixtures unless they are also present under `supported/`. + +Formatter examples may use only the supported forms listed above, plus line +comments. Unsupported design-target forms must stay out of formatter fixtures +until their formatter behavior and full compiler contract are implemented. +Top-level `test` is covered by `formatter/top-level-test.slo`. +Value-producing `if` is covered by `formatter/if.slo`. +Local declarations and assignment are covered by +`formatter/local-variables.slo`. First-pass `while` is covered by +`formatter/while.slo`. First-pass structs are covered by +`formatter/struct.slo`. First v1 struct value flow is covered by +`formatter/struct-value-flow.slo`. First-pass array constructors, immutable +array locals, and literal indexing are covered by `formatter/array.slo` and +the widened direct scalar fixture `formatter/array-direct-scalars.slo`. First +v1 array value flow and dynamic indexing are covered by +`formatter/array-value-flow.slo` and the widened direct scalar fixture +`formatter/array-direct-scalars-value-flow.slo`. The widened fixed string +array constructor and literal-indexing lane is covered by +`formatter/array-string.slo`. The widened fixed string array value-flow and +dynamic-indexing lane is covered by `formatter/array-string-value-flow.slo`. +First-pass option/result constructors are covered by +`formatter/option-result.slo`. First v1 option/result value flow is covered by +`formatter/option-result-flow.slo`. First v1 option/result payload access is +covered by `formatter/option-result-payload.slo`. v1.4 option/result match is +covered by `formatter/option-result-match.slo`. Lexical unsafe blocks are +covered by `formatter/unsafe.slo`. First runtime string printing is covered by +`formatter/string-print.slo`. v1.2 string value flow is covered by +`formatter/string-value-flow.slo`. v1.2 bool printing is covered by +`formatter/print-bool.slo`. v1.5 standard-runtime names are covered by +`formatter/standard-runtime.slo`. exp-1 owned string concatenation is covered +by `formatter/owned-string-concat.slo`. exp-2 vectors are covered by +`formatter/vec-i32.slo`. exp-3 host IO formatting is covered by +`formatter/host-io.slo`. exp-4 enum formatting is covered by +`formatter/enum-basic.slo`. exp-16 unary `i32` enum payload formatting is +covered by `formatter/enum-payload-i32.slo`. exp-18 direct enum struct field +formatting is covered by `formatter/enum-struct-fields.slo`. exp-19 direct +primitive struct field formatting is covered by +`formatter/primitive-struct-fields.slo`. exp-112 immutable bool locals +formatting is covered by `formatter/local-variables.slo`. exp-20 f64 numeric +primitive formatting is covered by `formatter/f64-numeric-primitive.slo`. exp-21 i64 +numeric primitive formatting is covered by +`formatter/i64-numeric-primitive.slo`. exp-22 numeric widening conversion +formatting is covered by `formatter/numeric-widening-conversions.slo`. exp-23 +checked i64-to-i32 conversion formatting is covered by +`formatter/checked-i64-to-i32-conversion.slo`. exp-24 integer to string +formatting is covered by `formatter/integer-to-string.slo`. exp-17 does not +add a project +formatter fixture because this directory contains single-file formatter +fixtures, not whole project directories. The exp-12 standard input result +alpha is covered by `formatter/stdin-result.slo`. The exp-13 string +parse i32 result alpha is covered by +`formatter/string-parse-i32-result.slo`. The exp-25 string parse i64 result +alpha is covered by `formatter/string-parse-i64-result.slo`. The exp-26 f64 +to string alpha is covered by `formatter/f64-to-string.slo`. The exp-27 +checked f64-to-i32 result alpha is covered by +`formatter/f64-to-i32-result.slo`. The exp-28 string parse f64 result alpha +is covered by `formatter/string-parse-f64-result.slo`. The exp-29 numeric +struct fields alpha is covered by `formatter/numeric-struct-fields.slo`. The +exp-30 standard library source layout alpha is covered conservatively by +`formatter/std-source-layout-alpha.slo`. The exp-31 checked f64-to-i64 result +alpha is covered by `formatter/f64-to-i64-result.slo`. The +exp-15 result helper standard names +alpha is covered by +`formatter/result-helpers.slo`. + +## Design/Speculative + +Files in `speculative/` are language-design targets. They document intended Slovo +forms, but they are not current Glagol executable fixtures until each form has +parser/lowerer support, checker behavior, diagnostics, formatter behavior, LLVM +behavior or explicit unsupported diagnostics, and automated tests. + +Speculative examples may use forms that Glagol partially recognizes. Partial +recognition is not support; these files stay outside the compiler contract until +the strict support rule is satisfied. + +`speculative/point.slo` tracks follow-up struct behavior beyond v1 struct value +flow, including mutation and broader layout/ABI questions. The first-pass +struct contract is promoted through `supported/struct.slo`; the first v1 +struct value-flow contract is promoted through `supported/struct-value-flow.slo`. + +`speculative/array-index.slo` is retained as a non-contract array sketch. The +promoted v1 array value-flow forms live in `supported/array-value-flow.slo` +and the widened direct scalar fixtures `supported/array-direct-scalars.slo` +and `supported/array-direct-scalars-value-flow.slo`, plus the fixed string +fixtures `supported/array-string.slo` and +`supported/array-string-value-flow.slo`; mutation, slices, other unsupported +element types, nested arrays, equality, printing, unchecked indexing, and +layout/ABI questions remain follow-up work. + +`speculative/option-result.slo` tracks follow-up option/result behavior beyond +the current promoted constructor, value-flow, tag-observation, and explicit +`i32` payload-access and v1.4 match fixtures, including mapping, equality, +printing, string payloads, non-`i32` payloads, generic payloads, nested +option/result values, arrays or structs containing option/results, +payload ADTs, and user-catchable exceptions. + +Runtime string printing is promoted through `supported/string-print.slo`. +Immutable string value flow, string equality, and string byte length are +promoted through `supported/string-value-flow.slo`. Mutable string locals, +string assignment, string ordering, strings inside arrays, options, or +results, string containers beyond exp-19 direct struct fields, string slices, +concatenation beyond exp-1 `std.string.concat`, +indexing, ownership annotations, and user-defined string runtime bindings +remain follow-up design work. `print_unit` remains +unsupported because `unit` is still an internal builtin result type rather than +a user-visible value. v1.5 standard-runtime names are promoted only for +existing print and string-length behavior. `std.io.print_unit`, host IO beyond +the exp-3 slice and exp-12 stdin-result slice, networking, async IO, binary +file APIs, directory +traversal, terminal control, platform abstraction, general host error ADTs, +`result string Error`, package interaction, stdin APIs beyond the exp-12 +result slice, parsing beyond exp-13 `std.string.parse_i32_result`, exp-25 +`std.string.parse_i64_result`, and exp-28 +`std.string.parse_f64_result`, result helper payload families beyond the +explicitly listed `(result i32 i32)`, `(result i64 i32)`, +`(result string i32)`, and exp-28 returned `(result f64 i32)` families, +tokenizer/scanner APIs, parsing bools/strings/bytes, +whitespace/locale/base-prefix/underscore/plus-sign parsing, Unicode digit +parsing, parse error messages or richer codes, randomness, time, generic +vectors, vector element types other than `i32`, +vector mutation, vector `var` or `set`, vector literals beyond empty/append +construction, `push`, nested vectors, vectors in arrays, structs, options, or +results, iterators, slices, maps, sets, user-visible vector deallocation, +stable vector ABI/layout/helper symbols, allocation, user-defined standard +modules, imports/packages, overloading, generic APIs, Unicode length +semantics, and ABI/layout promises remain follow-up design work. exp-2 adds +only `(vec i32)` with +`std.vec.i32.empty`, `std.vec.i32.append`, `std.vec.i32.len`, +`std.vec.i32.index`, and vector equality. exp-3 adds only `std.io.eprint`, +`std.process.argc`, `std.process.arg`, `std.env.get`, `std.fs.read_text`, and +`std.fs.write_text`. exp-4 adds only payloadless user-defined enums with +qualified zero-argument constructors, same-enum equality, immutable value flow, +and exhaustive payloadless enum `match`. exp-16 adds only unary `i32` enum +payload variants, exp-17 adds only explicit project/workspace import/export +of current enum type names, and exp-18 adds only direct enum-typed struct +fields for the current enum surface. exp-19 adds only direct `bool` and +immutable `string` struct fields alongside direct `i32` and exp-18 enum +fields. exp-112 adds only direct immutable `bool` locals over already +promoted bool expressions. exp-20 adds only direct `f64` value flow, +same-type `f64` arithmetic/comparison, and `std.io.print_f64`. `f32`, +wider/common integer +types beyond exp-21 direct `i64`, char/bytes/decimal, numeric casts, mixed +numeric arithmetic, f64/i64 collections, f64/i64 enum payloads, f64/i64 struct +fields, parse_f64, random floats or random `i64`, stable +ABI/layout, and beta maturity remain deferred. exp-21 adds only direct `i64` +value flow, explicit signed decimal `i64` literal atoms, same-type `i64` +arithmetic/comparison, and `std.io.print_i64`. exp-22 adds only explicit +standard-runtime widening calls `std.num.i32_to_i64 : (i32) -> i64`, +`std.num.i32_to_f64 : (i32) -> f64`, and +`std.num.i64_to_f64 : (i64) -> f64`. exp-23 adds only +`std.num.i64_to_i32_result : (i64) -> (result i32 i32)`; implicit promotion, +mixed numeric operators, other narrowing or checked conversions, cast syntax, +`std.num.cast`, `f64` conversions, `f32`, unsigned/narrower integer families, +numeric parse/format APIs beyond exp-24 integer-to-string, exp-25 +`std.string.parse_i64_result`, exp-26 finite `std.num.f64_to_string`, and +exp-27 checked `std.num.f64_to_i32_result`, +stable ABI/layout, and beta maturity remain deferred. exp-24 adds only +`std.num.i32_to_string : (i32) -> string` and +`std.num.i64_to_string : (i64) -> string`; `f64` formatting, parse APIs, +locale/base/radix/grouping/padding controls, generic format/display traits, +implicit conversions, stable standard-library implementation source, stable +ABI/layout/ownership, and beta maturity remain deferred. exp-25 adds only +`std.string.parse_i64_result : (string) -> (result i64 i32)`; `f64` parse, +generic parse, whitespace/plus/underscore/radix/base/locale/Unicode parsing, +rich parse errors, stable ABI/layout/ownership, and beta maturity remain +deferred. exp-26 adds only `std.num.f64_to_string : (f64) -> string`; `f32`, +f64 parse, generic format/display/interpolation, precision controls, stable +NaN/infinity text, stable ABI/layout/ownership, and beta maturity remain +deferred. exp-27 adds only +`std.num.f64_to_i32_result : (f64) -> (result i32 i32)`; unchecked `f64` to +`i32`, casts or cast syntax, generic `cast_checked`, `f32`, +unsigned/narrower integer families, f64 parse, mixed numeric arithmetic, +numeric containers, stable ABI/layout/ownership, and beta maturity remain +deferred. exp-28 adds only +`std.string.parse_f64_result : (string) -> (result f64 i32)`; generic parse, +bool/string/bytes parse, locale parsing, Unicode digit parsing, underscores, +rich parse errors, stable helper ABI/layout/ownership, result genericity, f64 +containers, mixed numeric arithmetic, and beta maturity remain deferred. +exp-29 adds only direct immutable `i64` and finite `f64` struct fields; +struct field mutation, nested structs, arrays/vectors/options/results as +fields, enum payload widening, numeric containers, `f32`, unsigned or +narrower integer families, mixed numeric arithmetic, implicit conversions, +new parse/format APIs, generic structs, methods, traits, stable ABI/layout/ +ownership, manifest schema changes, FFI layout claims, and beta maturity +remain deferred. exp-31 adds only +`std.num.f64_to_i64_result : (f64) -> (result i64 i32)`; unchecked casts, +cast syntax, generic `cast_checked`, `f32`, unsigned/narrower integer +families, mixed numeric arithmetic, broad math, stable ABI/layout, manifest +schema changes, and beta maturity remain deferred. +Payloads beyond unary +`i32`, +generic enums, +generic functions, type aliases, traits/interfaces/protocols, methods, derives, +wildcard/rest patterns, guards, nested patterns, enum values in +arrays/options/results/vectors, enum values in nested structs, enum mutation, enum +printing/ordering/hash, reflection, explicit discriminants, unqualified +variant constructors, enum import behavior beyond exp-17 explicit local lists, +stable ABI/layout, and moving option/result to ordinary standard-library types +remain deferred. + +`speculative/unsafe-memory.slo` tracks follow-up raw-memory behavior beyond the +current lexical unsafe block, including pointer locals, allocation, +deallocation, pointer load/store, and other unsafe operations. diff --git a/docs/language/examples/compat/v0/README.md b/docs/language/examples/compat/v0/README.md new file mode 100644 index 0000000..31cec2f --- /dev/null +++ b/docs/language/examples/compat/v0/README.md @@ -0,0 +1,31 @@ +# Slovo v0 Compatibility Fixtures + +This directory freezes the Slovo v0 fixture set after the v0 release. + +`supported/` contains the v0 compiler-supported examples: + +- `add.slo` +- `top-level-test.slo` +- `local-variables.slo` +- `if.slo` +- `while.slo` +- `struct.slo` +- `array.slo` +- `option-result.slo` +- `unsafe.slo` + +`formatter/` contains the matching v0 canonical formatter fixtures: + +- `canonical.slo` +- `top-level-test.slo` +- `local-variables.slo` +- `if.slo` +- `while.slo` +- `struct.slo` +- `array.slo` +- `option-result.slo` +- `unsafe.slo` + +Do not add v1 fixtures here. New promoted slices belong in +`examples/supported/` and `examples/formatter/`. These files move only through +an explicit migration recorded in `MIGRATION_POLICY.md` and release notes. diff --git a/docs/language/examples/compat/v0/formatter/array.slo b/docs/language/examples/compat/v0/formatter/array.slo new file mode 100644 index 0000000..9ca3674 --- /dev/null +++ b/docs/language/examples/compat/v0/formatter/array.slo @@ -0,0 +1,18 @@ +(module main) + +(fn immediate_second () -> i32 + (index (array i32 10 20 30) 1)) + +(fn local_sum () -> i32 + (let values (array i32 3) (array i32 4 5 6)) + (+ (index values 0) (index values 2))) + +(test "immediate array index" + (= (immediate_second) 20)) + +(test "array local index" + (let values (array i32 3) (array i32 7 8 9)) + (= (index values 2) 9)) + +(fn main () -> i32 + (+ (immediate_second) (local_sum))) diff --git a/docs/language/examples/compat/v0/formatter/canonical.slo b/docs/language/examples/compat/v0/formatter/canonical.slo new file mode 100644 index 0000000..def0173 --- /dev/null +++ b/docs/language/examples/compat/v0/formatter/canonical.slo @@ -0,0 +1,11 @@ +; status: formatter-canonical +; Scope: current strict supported syntax only. + +(module main) + +(fn add ((a i32) (b i32)) -> i32 + (+ a b)) + +(fn main () -> i32 + (print_i32 (add 20 22)) + 0) diff --git a/docs/language/examples/compat/v0/formatter/if.slo b/docs/language/examples/compat/v0/formatter/if.slo new file mode 100644 index 0000000..b94c047 --- /dev/null +++ b/docs/language/examples/compat/v0/formatter/if.slo @@ -0,0 +1,20 @@ +(module main) + +(fn choose ((value i32)) -> i32 + (if (< value 3) + 10 + 20)) + +(test "if chooses then" + (= (choose 2) 10)) + +(test "if chooses else" + (= (choose 4) 20)) + +(test "if returns bool" + (if (< 1 2) + true + false)) + +(fn main () -> i32 + (choose 2)) diff --git a/docs/language/examples/compat/v0/formatter/local-variables.slo b/docs/language/examples/compat/v0/formatter/local-variables.slo new file mode 100644 index 0000000..f97c8e1 --- /dev/null +++ b/docs/language/examples/compat/v0/formatter/local-variables.slo @@ -0,0 +1,16 @@ +(module main) + +(fn add_local ((a i32)) -> i32 + (let one i32 1) + (var total i32 (+ a one)) + (set total (+ total 1)) + total) + +(test "locals work" + (let base i32 2) + (var value i32 (add_local base)) + (set value (+ value 1)) + (= value 5)) + +(fn main () -> i32 + (add_local 2)) diff --git a/docs/language/examples/compat/v0/formatter/option-result.slo b/docs/language/examples/compat/v0/formatter/option-result.slo new file mode 100644 index 0000000..e0803f1 --- /dev/null +++ b/docs/language/examples/compat/v0/formatter/option-result.slo @@ -0,0 +1,16 @@ +(module main) + +(fn maybe_value () -> (option i32) + (some i32 42)) + +(fn maybe_empty () -> (option i32) + (none i32)) + +(fn result_ok () -> (result i32 i32) + (ok i32 i32 42)) + +(fn result_err () -> (result i32 i32) + (err i32 i32 7)) + +(fn main () -> i32 + 0) diff --git a/docs/language/examples/compat/v0/formatter/struct.slo b/docs/language/examples/compat/v0/formatter/struct.slo new file mode 100644 index 0000000..1cecdcb --- /dev/null +++ b/docs/language/examples/compat/v0/formatter/struct.slo @@ -0,0 +1,17 @@ +(module main) + +(struct Point + (x i32) + (y i32)) + +(fn point_sum () -> i32 + (+ (. (Point (x 20) (y 22)) x) (. (Point (x 20) (y 22)) y))) + +(test "struct field access" + (= (point_sum) 42)) + +(test "struct field compares" + (= (. (Point (x 7) (y 9)) y) 9)) + +(fn main () -> i32 + (point_sum)) diff --git a/docs/language/examples/compat/v0/formatter/top-level-test.slo b/docs/language/examples/compat/v0/formatter/top-level-test.slo new file mode 100644 index 0000000..9004f81 --- /dev/null +++ b/docs/language/examples/compat/v0/formatter/top-level-test.slo @@ -0,0 +1,13 @@ +; status: formatter-canonical +; Scope: promoted top-level test formatter contract. + +(module tests) + +(fn add ((a i32) (b i32)) -> i32 + (+ a b)) + +(test "add works" + (= (add 2 3) 5)) + +(fn main () -> i32 + 0) diff --git a/docs/language/examples/compat/v0/formatter/unsafe.slo b/docs/language/examples/compat/v0/formatter/unsafe.slo new file mode 100644 index 0000000..ab8651c --- /dev/null +++ b/docs/language/examples/compat/v0/formatter/unsafe.slo @@ -0,0 +1,16 @@ +(module main) + +(fn add_one_in_unsafe ((value i32)) -> i32 + (unsafe + (let one i32 1) + (+ value one))) + +(test "unsafe block returns final value" + (= (add_one_in_unsafe 4) 5)) + +(test "unsafe block can return bool" + (unsafe + (= (add_one_in_unsafe 1) 2))) + +(fn main () -> i32 + (add_one_in_unsafe 41)) diff --git a/docs/language/examples/compat/v0/formatter/while.slo b/docs/language/examples/compat/v0/formatter/while.slo new file mode 100644 index 0000000..6c60d5b --- /dev/null +++ b/docs/language/examples/compat/v0/formatter/while.slo @@ -0,0 +1,22 @@ +(module main) + +(fn count_to ((limit i32)) -> i32 + (var i i32 0) + (while (< i limit) + (set i (+ i 1))) + i) + +(test "while counts" + (var i i32 0) + (while (< i 3) + (set i (+ i 1))) + (= i 3)) + +(test "while false skips" + (var i i32 0) + (while false + (set i (+ i 1))) + (= i 0)) + +(fn main () -> i32 + (count_to 4)) diff --git a/docs/language/examples/compat/v0/supported/add.slo b/docs/language/examples/compat/v0/supported/add.slo new file mode 100644 index 0000000..7550b90 --- /dev/null +++ b/docs/language/examples/compat/v0/supported/add.slo @@ -0,0 +1,11 @@ +; status: compiler-supported +; Strict-manifest Glagol executable fixture. + +(module main) + +(fn add ((a i32) (b i32)) -> i32 + (+ a b)) + +(fn main () -> i32 + (print_i32 (add 20 22)) + 0) diff --git a/docs/language/examples/compat/v0/supported/array.slo b/docs/language/examples/compat/v0/supported/array.slo new file mode 100644 index 0000000..9ca3674 --- /dev/null +++ b/docs/language/examples/compat/v0/supported/array.slo @@ -0,0 +1,18 @@ +(module main) + +(fn immediate_second () -> i32 + (index (array i32 10 20 30) 1)) + +(fn local_sum () -> i32 + (let values (array i32 3) (array i32 4 5 6)) + (+ (index values 0) (index values 2))) + +(test "immediate array index" + (= (immediate_second) 20)) + +(test "array local index" + (let values (array i32 3) (array i32 7 8 9)) + (= (index values 2) 9)) + +(fn main () -> i32 + (+ (immediate_second) (local_sum))) diff --git a/docs/language/examples/compat/v0/supported/if.slo b/docs/language/examples/compat/v0/supported/if.slo new file mode 100644 index 0000000..b94c047 --- /dev/null +++ b/docs/language/examples/compat/v0/supported/if.slo @@ -0,0 +1,20 @@ +(module main) + +(fn choose ((value i32)) -> i32 + (if (< value 3) + 10 + 20)) + +(test "if chooses then" + (= (choose 2) 10)) + +(test "if chooses else" + (= (choose 4) 20)) + +(test "if returns bool" + (if (< 1 2) + true + false)) + +(fn main () -> i32 + (choose 2)) diff --git a/docs/language/examples/compat/v0/supported/local-variables.slo b/docs/language/examples/compat/v0/supported/local-variables.slo new file mode 100644 index 0000000..f97c8e1 --- /dev/null +++ b/docs/language/examples/compat/v0/supported/local-variables.slo @@ -0,0 +1,16 @@ +(module main) + +(fn add_local ((a i32)) -> i32 + (let one i32 1) + (var total i32 (+ a one)) + (set total (+ total 1)) + total) + +(test "locals work" + (let base i32 2) + (var value i32 (add_local base)) + (set value (+ value 1)) + (= value 5)) + +(fn main () -> i32 + (add_local 2)) diff --git a/docs/language/examples/compat/v0/supported/option-result.slo b/docs/language/examples/compat/v0/supported/option-result.slo new file mode 100644 index 0000000..e0803f1 --- /dev/null +++ b/docs/language/examples/compat/v0/supported/option-result.slo @@ -0,0 +1,16 @@ +(module main) + +(fn maybe_value () -> (option i32) + (some i32 42)) + +(fn maybe_empty () -> (option i32) + (none i32)) + +(fn result_ok () -> (result i32 i32) + (ok i32 i32 42)) + +(fn result_err () -> (result i32 i32) + (err i32 i32 7)) + +(fn main () -> i32 + 0) diff --git a/docs/language/examples/compat/v0/supported/struct.slo b/docs/language/examples/compat/v0/supported/struct.slo new file mode 100644 index 0000000..1cecdcb --- /dev/null +++ b/docs/language/examples/compat/v0/supported/struct.slo @@ -0,0 +1,17 @@ +(module main) + +(struct Point + (x i32) + (y i32)) + +(fn point_sum () -> i32 + (+ (. (Point (x 20) (y 22)) x) (. (Point (x 20) (y 22)) y))) + +(test "struct field access" + (= (point_sum) 42)) + +(test "struct field compares" + (= (. (Point (x 7) (y 9)) y) 9)) + +(fn main () -> i32 + (point_sum)) diff --git a/docs/language/examples/compat/v0/supported/top-level-test.slo b/docs/language/examples/compat/v0/supported/top-level-test.slo new file mode 100644 index 0000000..284e49e --- /dev/null +++ b/docs/language/examples/compat/v0/supported/top-level-test.slo @@ -0,0 +1,10 @@ +(module tests) + +(fn add ((a i32) (b i32)) -> i32 + (+ a b)) + +(test "add works" + (= (add 2 3) 5)) + +(fn main () -> i32 + 0) diff --git a/docs/language/examples/compat/v0/supported/unsafe.slo b/docs/language/examples/compat/v0/supported/unsafe.slo new file mode 100644 index 0000000..ab8651c --- /dev/null +++ b/docs/language/examples/compat/v0/supported/unsafe.slo @@ -0,0 +1,16 @@ +(module main) + +(fn add_one_in_unsafe ((value i32)) -> i32 + (unsafe + (let one i32 1) + (+ value one))) + +(test "unsafe block returns final value" + (= (add_one_in_unsafe 4) 5)) + +(test "unsafe block can return bool" + (unsafe + (= (add_one_in_unsafe 1) 2))) + +(fn main () -> i32 + (add_one_in_unsafe 41)) diff --git a/docs/language/examples/compat/v0/supported/while.slo b/docs/language/examples/compat/v0/supported/while.slo new file mode 100644 index 0000000..6c60d5b --- /dev/null +++ b/docs/language/examples/compat/v0/supported/while.slo @@ -0,0 +1,22 @@ +(module main) + +(fn count_to ((limit i32)) -> i32 + (var i i32 0) + (while (< i limit) + (set i (+ i 1))) + i) + +(test "while counts" + (var i i32 0) + (while (< i 3) + (set i (+ i 1))) + (= i 3)) + +(test "while false skips" + (var i i32 0) + (while false + (set i (+ i 1))) + (= i 0)) + +(fn main () -> i32 + (count_to 4)) diff --git a/docs/language/examples/ffi/exp-6-c-add/README.md b/docs/language/examples/ffi/exp-6-c-add/README.md new file mode 100644 index 0000000..b12a5de --- /dev/null +++ b/docs/language/examples/ffi/exp-6-c-add/README.md @@ -0,0 +1,29 @@ +# exp-6 C FFI Scalar Imports Alpha Fixture + +This fixture is experimental compiler-supported coverage for +`.llm/EXP_6_C_FFI_SCALAR_IMPORTS_ALPHA.md`. + +It is current compiler support after matching Slovo and Glagol exp-6 gates. + +The fixture covers one imported C function: + +```slo +(import_c c_add ((lhs i32) (rhs i32)) -> i32) +``` + +The Slovo wrapper calls it only inside lexical `unsafe`: + +```slo +(unsafe + (c_add 40 2)) +``` + +`slovo.toml` makes the fixture usable through project-mode native build gates. +The top-level test intentionally exercises the exp-6 test-runner boundary: the +interpreter-style test runner must report that C imports are unsupported for +direct test execution, while hosted native build uses the local `c_add.c` +companion as an explicit link input. + +No pointer types, allocation, deallocation, raw-memory operations, ownership or +lifetime model, generated header, library output, C exports, or stable ABI +promise is part of this fixture. diff --git a/docs/language/examples/ffi/exp-6-c-add/c_add.c b/docs/language/examples/ffi/exp-6-c-add/c_add.c new file mode 100644 index 0000000..eb71321 --- /dev/null +++ b/docs/language/examples/ffi/exp-6-c-add/c_add.c @@ -0,0 +1,5 @@ +#include + +int32_t c_add(int32_t lhs, int32_t rhs) { + return lhs + rhs; +} diff --git a/docs/language/examples/ffi/exp-6-c-add/main.slo b/docs/language/examples/ffi/exp-6-c-add/main.slo new file mode 100644 index 0000000..7a54fc2 --- /dev/null +++ b/docs/language/examples/ffi/exp-6-c-add/main.slo @@ -0,0 +1,15 @@ +(module main) + +(import_c c_add ((lhs i32) (rhs i32)) -> i32) + +(fn add_42 () -> i32 + (unsafe + (c_add 40 2))) + +(fn main () -> i32 + (add_42)) + +(test "C import test runner diagnostic" + (= (unsafe + (c_add 1 2)) + 3)) diff --git a/docs/language/examples/ffi/exp-6-c-add/slovo.toml b/docs/language/examples/ffi/exp-6-c-add/slovo.toml new file mode 100644 index 0000000..b5e71fa --- /dev/null +++ b/docs/language/examples/ffi/exp-6-c-add/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "exp-6-c-add" +source_root = "." +entry = "main" diff --git a/docs/language/examples/formatter/README.md b/docs/language/examples/formatter/README.md new file mode 100644 index 0000000..abfa994 --- /dev/null +++ b/docs/language/examples/formatter/README.md @@ -0,0 +1,319 @@ +# Slovo Formatter Fixtures + +Formatter fixtures define canonical layout for the current strict supported +syntax. They are not additional compiler-supported programs unless mirrored +under `examples/supported/`. + +The formatter fixture set now includes the final exp-125 unsigned fixtures +absorbed into `1.0.0-beta`. + +Promoted formatter fixtures may use only: + +- `(module name)` +- top-level `(fn ...)` +- explicit `i32` parameters and `i32` returns, plus promoted `string` + parameters and returns +- direct `(option i32)`, `(option i64)`, and `(result i32 i32)` returns only + for first-pass option/result constructor fixtures +- integer literals +- parameter references +- binary `+` +- user-defined function calls +- legacy compiler/runtime compatibility alias `(print_i32 value)` +- local `i32` bindings with `(let name i32 value)` and `(var name i32 value)` +- immutable array locals with `(let name (array T N) array-expression)` when + `T` is `i32`, `i64`, `f64`, or `bool` and the initializer has the exact + declared fixed array type +- assignment to mutable local `i32` bindings with `(set name value)` +- value-producing `(if condition then-expression else-expression)` +- first-pass `(while condition body...)` as a non-final sequential body form +- top-level `(struct Name (field i32)...)` +- first-pass struct construction and immediate field access +- first v1 struct value flow through immutable locals, parameters, returns, + calls returning structs, and stored field access +- fixed direct scalar array constructors and literal `(index array index)` +- fixed direct scalar array value flow through immutable array locals + initialized from matching array-valued expressions, fixed array parameters, + returns, calls returning arrays, and dynamic `(index array index)` with `i32` + index expressions +- first-pass `(some i32 value)`, `(none i32)`, `(some i64 value)`, + `(none i64)`, `(ok i32 i32 value)`, and `(err i32 i32 value)` constructors + as direct function returns +- first v1 option/result value flow through immutable locals, parameters, calls + returning option/result values, and `is_some` / `is_none` / `is_ok` / + `is_err` tag observation +- first v1 option/result payload access with `unwrap_some`, `unwrap_ok`, and + `unwrap_err` +- v1.4 source-level `match` for `(option i32)`, `(option i64)`, and + `(result i32 i32)` values, with exhaustive arms, immutable arm-scoped + payload bindings, one-or-more expression arm bodies, and common arm result + types +- source string literals used directly as `print_string` or + `std.io.print_string` arguments +- legacy compiler/runtime compatibility alias `(print_string value)` +- immutable string locals with `(let name string value)` +- string parameters and string returns +- calls returning strings +- string equality with `=` +- string byte length with `(string_len value)` +- legacy compiler/runtime compatibility alias `(print_bool value)` +- compiler-known standard-runtime calls `(std.io.print_i32 value)`, + `(std.io.print_string value)`, `(std.io.print_bool value)`, and + `(std.string.len value)` +- exp-112 direct immutable `bool` locals with `(let name bool value)` over the + already promoted bool-expression surface +- exp-20 direct `f64` parameters, returns, immutable locals, decimal literals, + same-type `f64` arithmetic/comparison, and `(std.io.print_f64 value)` +- exp-21 direct `i64` parameters, returns, immutable locals, explicit signed + decimal `i64` literal atoms, same-type `i64` arithmetic/comparison, and + `(std.io.print_i64 value)` +- exp-22 explicit numeric widening calls + `std.num.i32_to_i64 : (i32) -> i64`, + `std.num.i32_to_f64 : (i32) -> f64`, and + `std.num.i64_to_f64 : (i64) -> f64` +- exp-23 checked numeric narrowing call + `std.num.i64_to_i32_result : (i64) -> (result i32 i32)` +- exp-24 integer to string calls + `std.num.i32_to_string : (i32) -> string` and + `std.num.i64_to_string : (i64) -> string` +- exp-25 string parse i64 result call + `std.string.parse_i64_result : (string) -> (result i64 i32)` +- exp-26 f64 to string call `std.num.f64_to_string : (f64) -> string` +- exp-27 checked f64 to i32 result call + `std.num.f64_to_i32_result : (f64) -> (result i32 i32)` +- exp-28 string parse f64 result call + `std.string.parse_f64_result : (string) -> (result f64 i32)` +- exp-29 direct immutable `i64` and finite `f64` struct fields, using + existing struct construction, immutable struct flow, field access, + same-type numeric arithmetic/comparison, and numeric print/format helpers +- exp-30 conservative source-layout helper shape in + `std-source-layout-alpha.slo`, without repo-root `std` import/search + support or source replacement of compiler-known `std.*` calls +- exp-31 checked f64 to i64 result call + `std.num.f64_to_i64_result : (f64) -> (result i64 i32)` +- exp-1 compiler-known call `(std.string.concat left right)` +- exp-2 `(vec i32)` type forms and compiler-known calls + `(std.vec.i32.empty)`, `(std.vec.i32.append values value)`, + `(std.vec.i32.len values)`, and `(std.vec.i32.index values index)` +- exp-3 compiler-known calls `(std.io.eprint value)`, + `(std.process.argc)`, `(std.process.arg index)`, `(std.env.get name)`, + `(std.fs.read_text path)`, and `(std.fs.write_text path text)` +- exp-4 payloadless enum declarations `(enum Name VariantA VariantB)`, + zero-argument qualified constructors `(Name.VariantA)`, enum locals, + parameters, returns, calls, equality, and exhaustive enum `match` +- exp-16 unary `i32` enum payload variants such as `(Value i32)`, + qualified unary constructors such as `(Reading.Value value)`, immutable + arm-local payload bindings in enum `match`, and same-enum tag-plus-payload + equality +- exp-18 direct enum struct fields and exp-19 direct `bool` and immutable + `string` struct fields, using existing struct construction, immutable + struct flow, field access, equality, enum `match`, bool predicate, and + `std.string.len` forms +- exp-8 compiler-known calls `(std.time.monotonic_ms)` and + `(std.time.sleep_ms 0)` for structural/non-negative conformance checks only +- exp-11 compiler-known call `(std.random.i32)` and non-negative + structural checks using existing `<` and `if` forms +- exp-12 compiler-known call `(std.io.read_stdin_result)` using + existing `(result string i32)` observers, unwrap, and `match` +- exp-13 compiler-known call `(std.string.parse_i32_result text)` using + existing `(result i32 i32)` observers, unwrap, and `match` +- exp-15 compiler-known result helper calls `(std.result.is_ok value)`, + `(std.result.is_err value)`, `(std.result.unwrap_ok value)`, and + `(std.result.unwrap_err value)` for only `(result i32 i32)`, + `(result i64 i32)`, and `(result string i32)` +- lexical `(unsafe body... final-expression)` expression blocks containing only + otherwise supported safe v0 forms +- final-expression returns +- top-level `(test "name" body... final-expression)` +- equality comparison `=` when used as a bool-producing test expression +- ordering comparison `<` when used as a bool-producing condition +- line comments + +Promoted formatter fixtures are canonical output. +Formatting one of these fixtures must reproduce the same source bytes, +including accepted full-line comments and final newline, and formatting +formatter output must be idempotent. The v1 formatter is not a width-based +pretty printer and does not promise column reflow for long inline calls or +forms. + +Line comments are stable only as full-line comments before top-level forms, +inside function/test bodies before body expressions, after the final +function/test body expression before the closing paren, and after the last +top-level form. Comments inside module forms, struct forms, function/test +headers, type forms, or inline expression forms are unsupported formatter +positions and must produce structured diagnostics instead of being silently +dropped. + +Nested `if`, `while`, `unsafe`, and `match` forms keep their canonical +multiline shapes. Calls, field access, arrays, struct constructors, +option/result constructors, option/result observers, option/result unwraps, +legacy print aliases, `std.io.print_*` calls, exp-1 `std.string.concat` calls, +exp-2 `std.vec.i32.*` calls, exp-3 host calls, exp-4 qualified enum +constructors, exp-16 unary enum payload constructors, exp-8 `std.time.*` +calls, and exp-13 +`std.string.parse_i32_result` calls, and exp-15 `std.result.*` helper calls +and exp-20 `std.io.print_f64` calls remain inline when used as expressions. +exp-21 `std.io.print_i64`, exp-22 `std.num.*` conversion calls, exp-23 +`std.num.i64_to_i32_result`, exp-24 integer-to-string calls, and exp-25 +`std.string.parse_i64_result` calls, and exp-26 `std.num.f64_to_string` calls +and exp-27 `std.num.f64_to_i32_result` calls remain inline when used as +expressions. exp-28 `std.string.parse_f64_result` calls and exp-31 +`std.num.f64_to_i64_result` calls remain inline when used as expressions. +`string_len` and `std.string.len` remain inline when used as expressions. + +Current Slovo-side target formatter fixtures: + +- `u32-numeric-primitive.slo` +- `u64-numeric-primitive.slo` +- `unsigned-integer-to-string.slo` +- `string-parse-u32-result.slo` +- `string-parse-u64-result.slo` + +These formatter fixtures describe the final exp-125 unsigned contract shape +now absorbed into `1.0.0-beta`. Execution claims still come from the paired +supported/project fixtures and Glagol gates, not from formatter fixtures +alone. + +Design-target forms such as mutable string locals, string assignment, string +ordering, strings inside arrays, options, or results, string containers beyond +exp-19 direct struct fields, string slices, concatenation beyond exp-1 +`std.string.concat`, indexing, ownership +annotations, user-defined string runtime bindings, +`print_unit`, `std.io.print_unit`, host IO beyond the exp-3 slice and exp-12 +stdin-result slice, networking, async IO, binary file APIs, directory +traversal, terminal control, platform abstraction, general host error ADTs +beyond exp-10, +`result string Error`, package interaction, stdin APIs beyond the exp-12 +result slice, parsing beyond exp-13 `std.string.parse_i32_result`, exp-25 +`std.string.parse_i64_result`, and exp-28 +`std.string.parse_f64_result`, +result helper payload families beyond the explicitly listed `(result i32 i32)`, +`(result i64 i32)`, `(result string i32)`, and exp-28 returned +`(result f64 i32)` families, +tokenizer/scanner APIs, parsing bools/strings/bytes, numeric casts or +conversions beyond exp-22 explicit widening calls and the exp-23 checked +`i64 -> i32` result call, exp-27 checked `f64 -> i32` result call, and exp-31 +checked `f64 -> i64` result call, f64 +formatting beyond exp-26 finite decimal text, +locale/base/radix/grouping/padding formatting controls, generic format/display +traits, stable integer-formatting ABI/layout/ownership, implicit promotion, +mixed numeric arithmetic, `f32`, +unsigned integers, narrower integer widths beyond the existing `i32`, +char/bytes/decimal, +whitespace/locale/base-prefix/underscore/plus-sign parsing beyond the exact +exp-13, exp-25, and exp-28 parse calls, Unicode digit parsing, parse error +messages or richer codes, randomness beyond the exp-11 +slice, time beyond the exp-8 host time/sleep slice, generic vectors, +vector element types other than `i32`, +vector mutation, vector `var` or `set`, vector literals beyond empty/append +construction, `push`, nested vectors, vectors in arrays, structs, options, or +results, iterators, slices, maps, sets, user-visible vector deallocation, +stable vector ABI/layout/helper symbols, allocation, user-defined standard +modules, imports/packages, overloading, generic APIs, Unicode length +semantics, general slices, pointer types, raw memory operations, array +mutation, array element types beyond direct scalar `i32`, `i64`, `f64`, +`bool`, and `string`, zero-length arrays, nested arrays, array equality, +printing arrays, option/result mapping, equality, printing, +non-`i32` payload extraction, nested option/result values, arrays or structs +containing option/results, generic payloads, enum payloads beyond exp-16 unary +`i32` variants, generic enums, generic functions, type aliases, +traits/interfaces/protocols, methods, derives, wildcard/rest enum patterns, +enum guards, nested enum patterns, enum values in +arrays/structs/options/results/vectors, enum mutation, +enum printing/ordering/hash, enum reflection, explicit enum discriminants, +enum constructors with multiple arguments, unqualified variant constructors, +stable enum ABI/layout, moving option/result to ordinary standard-library +types, user-catchable exceptions, struct mutation, unsafe abstractions, and +user-declared `unit` returns must stay out of this directory until they meet +the strict support rule. + +`canonical.slo` is the canonical formatted shape for the current supported +function fixture surface. `top-level-test.slo` is the canonical formatted shape +for the promoted top-level test fixture surface. `local-variables.slo` is the +canonical formatted shape for promoted local `let` / `var` / `set` forms, +including the later exp-112 immutable bool-local broadening. +`if.slo` is the canonical formatted shape for promoted value-producing +conditionals. `while.slo` is the canonical formatted shape for promoted +first-pass loops. `struct.slo` is the canonical formatted shape for promoted +first-pass structs. `struct-value-flow.slo` is the canonical formatted shape +for the first v1 struct value-flow slice. `array.slo` is the canonical +formatted shape for promoted first-pass array constructors, immutable array +locals, and literal indexing. `array-direct-scalars.slo` is the canonical +formatted shape for the widened direct scalar fixed-array constructor and +literal-indexing slice. `array-value-flow.slo` is the canonical formatted +shape for the first v1 array value-flow and dynamic-indexing slice. +`array-direct-scalars-value-flow.slo` is the canonical formatted shape for the +widened direct scalar fixed-array value-flow and dynamic-indexing slice. +`array-string.slo` is the canonical formatted shape for the widened fixed +string-array constructor and literal-indexing slice. +`array-string-value-flow.slo` is the canonical formatted shape for the widened +fixed string-array value-flow and dynamic-indexing slice. +`option-result.slo` is the canonical formatted shape for promoted first-pass +option/result constructors. +`option-result-flow.slo` is the canonical formatted shape for the first v1 +option/result value-flow slice. `option-result-payload.slo` is the canonical +formatted shape for the first v1 option/result payload-access slice. +`option-result-match.slo` is the canonical formatted shape for the v1.4 +option/result match slice. +`string-print.slo` is the canonical formatted shape for the first runtime +string-printing slice. +`string-value-flow.slo` is the canonical formatted shape for the v1.2 +immutable string value-flow, equality, and length slice. +`print-bool.slo` is the canonical formatted shape for the v1.2 bool-printing +slice. +`standard-runtime.slo` is the canonical formatted shape for the v1.5 +standard-runtime alpha names. +`owned-string-concat.slo` is the canonical formatted shape for the exp-1 +owned-runtime-string concat fixture. +`vec-i32.slo` is the canonical formatted shape for the exp-2 `(vec i32)` +fixture. +`vec-i64.slo` is the canonical formatted shape for the exp-94 `(vec i64)` +fixture. +`host-io.slo` is the canonical formatted shape for the exp-3 standard IO and +host environment fixture. +`time-sleep.slo` is the canonical formatted shape for the exp-8 host time and +sleep fixture aligned by the exp-14 release. +`random.slo` is the canonical formatted shape for the exp-11 basic randomness +alpha release fixture. +`stdin-result.slo` is the canonical formatted shape for the exp-12 standard +input result alpha release fixture. +`string-parse-i32-result.slo` is the canonical formatted shape for the exp-13 +string parse i32 result alpha fixture. +`result-helpers.slo` is the canonical formatted shape for the exp-15 result +helper standard names alpha fixture. +`enum-basic.slo` is the canonical formatted shape for the exp-4 payloadless +user enum fixture. +`enum-payload-i32.slo` is the canonical formatted shape for the exp-16 unary +`i32` enum payload fixture. +`primitive-struct-fields.slo` is the canonical formatted shape for the exp-19 +primitive struct field fixture. +`local-variables.slo` is the canonical formatted shape for the exp-112 +immutable bool locals fixture. +`f64-numeric-primitive.slo` is the canonical formatted shape for the exp-20 +f64 numeric primitive fixture. +`i64-numeric-primitive.slo` is the canonical formatted shape for the exp-21 +i64 numeric primitive fixture. +`numeric-widening-conversions.slo` is the canonical formatted shape for the +exp-22 numeric widening conversions fixture. +`checked-i64-to-i32-conversion.slo` is the canonical formatted shape for the +exp-23 checked i64-to-i32 conversion fixture. +`integer-to-string.slo` is the canonical formatted shape for the exp-24 +integer to string fixture. +`string-parse-i64-result.slo` is the canonical formatted shape for the exp-25 +string parse i64 result fixture. +`f64-to-string.slo` is the canonical formatted shape for the exp-26 f64 to +string fixture. +`f64-to-i32-result.slo` is the canonical formatted shape for the exp-27 +checked f64-to-i32 result fixture. +`string-parse-f64-result.slo` is the canonical formatted shape for the exp-28 +string parse f64 result fixture. +`numeric-struct-fields.slo` is the canonical formatted shape for the exp-29 +numeric struct fields fixture. +`std-source-layout-alpha.slo` is the canonical formatted shape for the exp-30 +standard library source layout alpha fixture. +`f64-to-i64-result.slo` is the canonical formatted shape for the exp-31 +checked f64-to-i64 result fixture. +`unsafe.slo` is the canonical formatted shape for promoted lexical unsafe +blocks. +`comments.slo` is the canonical formatted shape for the promoted v1 full-line +comment stability positions. diff --git a/docs/language/examples/formatter/array-direct-scalars-value-flow.slo b/docs/language/examples/formatter/array-direct-scalars-value-flow.slo new file mode 100644 index 0000000..fcfae37 --- /dev/null +++ b/docs/language/examples/formatter/array-direct-scalars-value-flow.slo @@ -0,0 +1,59 @@ +(module main) + +(fn make_i32_values ((base i32)) -> (array i32 3) + (array i32 base (+ base 1) (+ base 2))) + +(fn make_i64_values ((base i64)) -> (array i64 3) + (array i64 base (+ base 1i64) (+ base 2i64))) + +(fn make_f64_values ((base f64)) -> (array f64 3) + (array f64 base (+ base 1.0) (+ base 2.0))) + +(fn make_flags ((first bool)) -> (array bool 3) + (array bool first false true)) + +(fn i32_at ((values (array i32 3)) (i i32)) -> i32 + (index values i)) + +(fn i64_at ((values (array i64 3)) (i i32)) -> i64 + (index values i)) + +(fn f64_at ((values (array f64 3)) (i i32)) -> f64 + (index values i)) + +(fn bool_at ((values (array bool 3)) (i i32)) -> bool + (index values i)) + +(fn i64_local_array_flow ((i i32)) -> i64 + (let values (array i64 3) (make_i64_values 40i64)) + (i64_at values i)) + +(fn f64_parameter_local_copy ((values (array f64 3)) (i i32)) -> f64 + (let copy (array f64 3) values) + (index copy i)) + +(fn echo_flags ((values (array bool 3))) -> (array bool 3) + values) + +(fn bool_call_return_index ((i i32)) -> bool + (index (make_flags true) i)) + +(test "i32 array parameter value flow" + (= (i32_at (make_i32_values 7) 0) 7)) + +(test "i64 array local call value flow" + (= (i64_local_array_flow 2) 42i64)) + +(test "f64 array parameter local copy" + (= (f64_parameter_local_copy (make_f64_values 4.0) 1) 5.0)) + +(test "bool array dynamic index" + (bool_at (make_flags true) 2)) + +(test "bool array return call value flow" + (index (echo_flags (make_flags false)) 2)) + +(fn main () -> i32 + (if (bool_call_return_index 0) + 0 + 1)) diff --git a/docs/language/examples/formatter/array-direct-scalars.slo b/docs/language/examples/formatter/array-direct-scalars.slo new file mode 100644 index 0000000..cb2a494 --- /dev/null +++ b/docs/language/examples/formatter/array-direct-scalars.slo @@ -0,0 +1,32 @@ +(module main) + +(fn i32_second () -> i32 + (index (array i32 10 20 30) 1)) + +(fn i64_local_pick () -> i64 + (let values (array i64 3) (array i64 4i64 5i64 6i64)) + (index values 2)) + +(fn f64_third () -> f64 + (index (array f64 1.5 2.5 3.5) 2)) + +(fn bool_local_pick () -> bool + (let flags (array bool 3) (array bool false true false)) + (index flags 1)) + +(test "i32 direct scalar array index" + (= (i32_second) 20)) + +(test "i64 local direct scalar array index" + (= (i64_local_pick) 6i64)) + +(test "f64 direct scalar array index" + (= (f64_third) 3.5)) + +(test "bool local direct scalar array index" + (bool_local_pick)) + +(fn main () -> i32 + (if (bool_local_pick) + (i32_second) + 0)) diff --git a/docs/language/examples/formatter/array-enum.slo b/docs/language/examples/formatter/array-enum.slo new file mode 100644 index 0000000..3444aa6 --- /dev/null +++ b/docs/language/examples/formatter/array-enum.slo @@ -0,0 +1,34 @@ +(module main) + +(enum Color Red Blue Green) + +(fn make_palette () -> (array Color 3) + (array Color (Color.Red) (Color.Blue) (Color.Green))) + +(fn echo_palette ((colors (array Color 3))) -> (array Color 3) + colors) + +(fn at ((colors (array Color 3)) (i i32)) -> Color + (index colors i)) + +(fn local_pick () -> Color + (let colors (array Color 3) (make_palette)) + (index colors 1)) + +(test "enum array immediate index" + (= (index (array Color (Color.Red) (Color.Blue) (Color.Green)) 2) (Color.Green))) + +(test "enum array local index" + (= (local_pick) (Color.Blue))) + +(test "enum array param return dynamic index" + (= (at (echo_palette (make_palette)) 0) (Color.Red))) + +(fn main () -> i32 + (match (at (make_palette) 1) + ((Color.Blue) + 0) + ((Color.Red) + 1) + ((Color.Green) + 1))) diff --git a/docs/language/examples/formatter/array-string-value-flow.slo b/docs/language/examples/formatter/array-string-value-flow.slo new file mode 100644 index 0000000..75ea05c --- /dev/null +++ b/docs/language/examples/formatter/array-string-value-flow.slo @@ -0,0 +1,41 @@ +(module main) + +(fn make_words ((head string)) -> (array string 3) + (array string head "middle" "tail")) + +(fn at ((values (array string 3)) (i i32)) -> string + (index values i)) + +(fn echo ((values (array string 3))) -> (array string 3) + values) + +(fn call_return_index ((i i32)) -> string + (index (make_words "call") i)) + +(fn local_array_flow ((i i32)) -> string + (let words (array string 3) (make_words "local")) + (at words i)) + +(fn parameter_local_copy ((values (array string 3)) (i i32)) -> string + (let copy (array string 3) values) + (index copy i)) + +(test "string array parameter value flow" + (= (at (make_words "alpha") 0) "alpha")) + +(test "string array dynamic index" + (= (at (make_words "alpha") 2) "tail")) + +(test "string array local call value flow" + (= (local_array_flow 1) "middle")) + +(test "string array parameter local copy" + (= (parameter_local_copy (make_words "omega") 0) "omega")) + +(test "string array return call value flow" + (= (index (echo (make_words "zeta")) 2) "tail")) + +(fn main () -> i32 + (if (= (call_return_index 1) "middle") + 0 + 1)) diff --git a/docs/language/examples/formatter/array-string.slo b/docs/language/examples/formatter/array-string.slo new file mode 100644 index 0000000..cbefcb3 --- /dev/null +++ b/docs/language/examples/formatter/array-string.slo @@ -0,0 +1,19 @@ +(module main) + +(fn immediate_second () -> string + (index (array string "sun" "moon" "star") 1)) + +(fn local_pick () -> string + (let words (array string 3) (array string "red" "green" "blue")) + (index words 2)) + +(test "string immediate array index" + (= (immediate_second) "moon")) + +(test "string local array index" + (= (local_pick) "blue")) + +(fn main () -> i32 + (if (= (local_pick) "blue") + 0 + 1)) diff --git a/docs/language/examples/formatter/array-struct-elements.slo b/docs/language/examples/formatter/array-struct-elements.slo new file mode 100644 index 0000000..2f20f49 --- /dev/null +++ b/docs/language/examples/formatter/array-struct-elements.slo @@ -0,0 +1,41 @@ +(module main) + +(enum Color Red Blue Green) + +(struct Pixel + (x i32) + (label string) + (color Color)) + +(fn make_pixels ((head string)) -> (array Pixel 3) + (array Pixel (Pixel (x 1) (label head) (color (Color.Red))) (Pixel (x 2) (label "mid") (color (Color.Blue))) (Pixel (x 3) (label "tail") (color (Color.Green))))) + +(fn echo_pixels ((pixels (array Pixel 3))) -> (array Pixel 3) + pixels) + +(fn at ((pixels (array Pixel 3)) (i i32)) -> Pixel + (index pixels i)) + +(fn first_label ((head string)) -> string + (. (at (make_pixels head) 0) label)) + +(fn last_color ((head string)) -> Color + (. (index (echo_pixels (make_pixels head)) 2) color)) + +(test "struct array string field access" + (= (first_label "head") "head")) + +(test "struct array nested enum field access" + (= (last_color "head") (Color.Green))) + +(test "struct array local param return call flow" + (= (. (at (echo_pixels (make_pixels "sun")) 1) x) 2)) + +(fn main () -> i32 + (match (last_color "head") + ((Color.Green) + 0) + ((Color.Red) + 1) + ((Color.Blue) + 1))) diff --git a/docs/language/examples/formatter/array-struct-fields.slo b/docs/language/examples/formatter/array-struct-fields.slo new file mode 100644 index 0000000..b275d6d --- /dev/null +++ b/docs/language/examples/formatter/array-struct-fields.slo @@ -0,0 +1,56 @@ +(module main) + +(struct ArrayRecord + (ints (array i32 3)) + (wides (array i64 2)) + (ratios (array f64 3)) + (flags (array bool 3)) + (words (array string 3))) + +(fn make_record ((base i32) (head string)) -> ArrayRecord + (ArrayRecord (ints (array i32 base (+ base 1) (+ base 2))) (wides (array i64 40i64 41i64)) (ratios (array f64 1.5 2.5 3.5)) (flags (array bool false true true)) (words (array string head "moon" "star")))) + +(fn echo_record ((record ArrayRecord)) -> ArrayRecord + record) + +(fn local_record ((head string)) -> ArrayRecord + (let record ArrayRecord (make_record 7 head)) + (echo_record record)) + +(fn int_at ((record ArrayRecord) (i i32)) -> i32 + (index (. record ints) i)) + +(fn wide_at ((record ArrayRecord) (i i32)) -> i64 + (index (. record wides) i)) + +(fn ratio_at ((record ArrayRecord) (i i32)) -> f64 + (index (. record ratios) i)) + +(fn flag_at ((record ArrayRecord) (i i32)) -> bool + (index (. record flags) i)) + +(fn word_at ((record ArrayRecord) (i i32)) -> string + (index (. record words) i)) + +(test "struct array i32 field access" + (= (int_at (make_record 7 "sun") 2) 9)) + +(test "struct array i64 field access" + (= (wide_at (make_record 7 "sun") 1) 41i64)) + +(test "struct array f64 field access" + (= (ratio_at (make_record 7 "sun") 2) 3.5)) + +(test "struct array bool field access" + (flag_at (make_record 7 "sun") 1)) + +(test "struct array string field access" + (= (word_at (make_record 7 "sun") 1) "moon")) + +(test "struct array local param return call flow" + (= (word_at (local_record "sun") 0) "sun")) + +(fn main () -> i32 + (if (flag_at (local_record "sun") 2) + 0 + 1)) diff --git a/docs/language/examples/formatter/array-value-flow.slo b/docs/language/examples/formatter/array-value-flow.slo new file mode 100644 index 0000000..7774902 --- /dev/null +++ b/docs/language/examples/formatter/array-value-flow.slo @@ -0,0 +1,42 @@ +(module main) + +(fn make_values ((base i32)) -> (array i32 3) + (array i32 base (+ base 1) (+ base 2))) + +(fn first ((values (array i32 3))) -> i32 + (index values 0)) + +(fn at ((values (array i32 3)) (i i32)) -> i32 + (index values i)) + +(fn echo ((values (array i32 3))) -> (array i32 3) + values) + +(fn call_return_index ((i i32)) -> i32 + (index (make_values 10) i)) + +(fn local_array_flow ((i i32)) -> i32 + (let values (array i32 3) (make_values 20)) + (at values i)) + +(fn parameter_local_copy ((values (array i32 3)) (i i32)) -> i32 + (let copy (array i32 3) values) + (index copy i)) + +(test "array parameter value flow" + (= (first (make_values 7)) 7)) + +(test "array dynamic index" + (= (at (make_values 4) 2) 6)) + +(test "array local call value flow" + (= (local_array_flow 1) 21)) + +(test "array parameter local copy" + (= (parameter_local_copy (make_values 40) 2) 42)) + +(test "array return call value flow" + (= (index (echo (make_values 30)) 2) 32)) + +(fn main () -> i32 + (call_return_index 1)) diff --git a/docs/language/examples/formatter/array.slo b/docs/language/examples/formatter/array.slo new file mode 100644 index 0000000..9ca3674 --- /dev/null +++ b/docs/language/examples/formatter/array.slo @@ -0,0 +1,18 @@ +(module main) + +(fn immediate_second () -> i32 + (index (array i32 10 20 30) 1)) + +(fn local_sum () -> i32 + (let values (array i32 3) (array i32 4 5 6)) + (+ (index values 0) (index values 2))) + +(test "immediate array index" + (= (immediate_second) 20)) + +(test "array local index" + (let values (array i32 3) (array i32 7 8 9)) + (= (index values 2) 9)) + +(fn main () -> i32 + (+ (immediate_second) (local_sum))) diff --git a/docs/language/examples/formatter/boolean-logic.slo b/docs/language/examples/formatter/boolean-logic.slo new file mode 100644 index 0000000..c9dac75 --- /dev/null +++ b/docs/language/examples/formatter/boolean-logic.slo @@ -0,0 +1,58 @@ +(module main) + +(fn and_true () -> bool + (and true true)) + +(fn and_false () -> bool + (and true false)) + +(fn or_true () -> bool + (or false true)) + +(fn not_false () -> bool + (not false)) + +(fn and_short_circuits () -> bool + (not (and false (= (/ 1 0) 0)))) + +(fn or_short_circuits () -> bool + (or true (= (/ 1 0) 0))) + +(fn logic_ok () -> bool + (if (and_true) + (if (not (and_false)) + (if (or_true) + (if (not_false) + (if (and_short_circuits) + (or_short_circuits) + false) + false) + false) + false) + false)) + +(fn main () -> i32 + (if (logic_ok) + 42 + 1)) + +(test "and true" + (and_true)) + +(test "and false" + (not (and_false))) + +(test "or true" + (or_true)) + +(test "not false" + (not_false)) + +(test "and short circuit" + (and_short_circuits)) + +(test "or short circuit" + (or_short_circuits)) + +(test "boolean logic summary" + (logic_ok)) diff --git a/docs/language/examples/formatter/canonical.slo b/docs/language/examples/formatter/canonical.slo new file mode 100644 index 0000000..def0173 --- /dev/null +++ b/docs/language/examples/formatter/canonical.slo @@ -0,0 +1,11 @@ +; status: formatter-canonical +; Scope: current strict supported syntax only. + +(module main) + +(fn add ((a i32) (b i32)) -> i32 + (+ a b)) + +(fn main () -> i32 + (print_i32 (add 20 22)) + 0) diff --git a/docs/language/examples/formatter/checked-i64-to-i32-conversion.slo b/docs/language/examples/formatter/checked-i64-to-i32-conversion.slo new file mode 100644 index 0000000..e0905f7 --- /dev/null +++ b/docs/language/examples/formatter/checked-i64-to-i32-conversion.slo @@ -0,0 +1,57 @@ +(module main) + +(fn narrow ((value i64)) -> (result i32 i32) + (std.num.i64_to_i32_result value)) + +(fn low_ok () -> (result i32 i32) + (narrow -2147483648i64)) + +(fn high_ok () -> (result i32 i32) + (narrow 2147483647i64)) + +(fn negative_ok () -> (result i32 i32) + (narrow -7i64)) + +(fn below_low_err () -> (result i32 i32) + (narrow -2147483649i64)) + +(fn above_high_err () -> (result i32 i32) + (narrow 2147483648i64)) + +(test "i64 low bound narrows to i32" + (let value (result i32 i32) (low_ok)) + (if (std.result.is_ok value) + (= (std.result.unwrap_ok value) -2147483648) + false)) + +(test "i64 high bound narrows to i32" + (let value (result i32 i32) (high_ok)) + (if (std.result.is_ok value) + (= (std.result.unwrap_ok value) 2147483647) + false)) + +(test "negative i64 narrows to i32" + (let value (result i32 i32) (negative_ok)) + (if (std.result.is_ok value) + (= (std.result.unwrap_ok value) -7) + false)) + +(test "below i32 range returns err" + (let value (result i32 i32) (below_low_err)) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "above i32 range returns err" + (let value (result i32 i32) (above_high_err)) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(fn main () -> i32 + (let value (result i32 i32) (negative_ok)) + (if (std.result.is_ok value) + (if (= (std.result.unwrap_ok value) -7) + 0 + 1) + (std.result.unwrap_err value))) diff --git a/docs/language/examples/formatter/comments.slo b/docs/language/examples/formatter/comments.slo new file mode 100644 index 0000000..27596b3 --- /dev/null +++ b/docs/language/examples/formatter/comments.slo @@ -0,0 +1,35 @@ +; status: formatter-canonical +; Scope: v1 full-line comment stability positions. +; comment before module form + +(module main) + +; comment before helper function + +(fn add_one ((value i32)) -> i32 + ; comment before first function body expression + (let one i32 1) + ; comment before final function body expression + (+ value one) + ; comment after final function body expression +) + +; comment before top-level test + +(test "comments stay attached" + ; comment before non-final test body expression + (let observed i32 (add_one 4)) + ; comment before final test body expression + (= observed 5) + ; comment after final test body expression +) + +; comment before main function + +(fn main () -> i32 + ; comment before only function body expression + (add_one 41) + ; comment after final function body expression +) + +; comment after last top-level form diff --git a/docs/language/examples/formatter/composite-locals.slo b/docs/language/examples/formatter/composite-locals.slo new file mode 100644 index 0000000..41f00c3 --- /dev/null +++ b/docs/language/examples/formatter/composite-locals.slo @@ -0,0 +1,294 @@ +(module main) + +(struct Point + (x i32) + (y i32)) + +(struct PrimitiveRecord + (id i32) + (active bool) + (label string)) + +(struct NumericRecord + (wide i64) + (ratio f64)) + +(enum Signal Red Yellow Green) + +(enum Reading + Missing + (Value i32)) + +(struct TaggedReading + (status Signal) + (reading Reading)) + +(fn make_point ((x i32) (y i32)) -> Point + (Point (x x) (y y))) + +(fn make_primitive_record ((id i32) (label string)) -> PrimitiveRecord + (PrimitiveRecord (id id) (active (= id 7)) (label label))) + +(fn make_numeric_record ((wide i64) (ratio f64)) -> NumericRecord + (NumericRecord (wide wide) (ratio ratio))) + +(fn make_tagged ((status Signal) (reading Reading)) -> TaggedReading + (TaggedReading (status status) (reading reading))) + +(fn vec_i32_pair ((base i32)) -> (vec i32) + (let values (vec i32) (std.vec.i32.empty)) + (let first (vec i32) (std.vec.i32.append values base)) + (std.vec.i32.append first (+ base 1))) + +(fn vec_i64_pair ((base i64)) -> (vec i64) + (let values (vec i64) (std.vec.i64.empty)) + (let first (vec i64) (std.vec.i64.append values base)) + (std.vec.i64.append first (+ base 1i64))) + +(fn vec_f64_pair ((base f64)) -> (vec f64) + (let values (vec f64) (std.vec.f64.empty)) + (let first (vec f64) (std.vec.f64.append values base)) + (std.vec.f64.append first (+ base 1.0))) + +(fn vec_bool_pair ((first bool) (second bool)) -> (vec bool) + (let values (vec bool) (std.vec.bool.empty)) + (let left (vec bool) (std.vec.bool.append values first)) + (std.vec.bool.append left second)) + +(fn vec_string_pair ((first string) (second string)) -> (vec string) + (let values (vec string) (std.vec.string.empty)) + (let left (vec string) (std.vec.string.append values first)) + (std.vec.string.append left second)) + +(fn swap_string ((first string) (second string)) -> string + (var current string first) + (set current second) + current) + +(fn vec_i32_local_value () -> i32 + (var current (vec i32) (vec_i32_pair 10)) + (set current (vec_i32_pair 40)) + (std.vec.i32.index current 1)) + +(fn vec_i64_local_value () -> i64 + (var current (vec i64) (vec_i64_pair 10i64)) + (set current (vec_i64_pair 40i64)) + (std.vec.i64.index current 1)) + +(fn vec_f64_local_value () -> f64 + (var current (vec f64) (vec_f64_pair 10.0)) + (set current (vec_f64_pair 40.0)) + (std.vec.f64.index current 1)) + +(fn vec_bool_local_value () -> bool + (var current (vec bool) (vec_bool_pair true false)) + (set current (vec_bool_pair false true)) + (std.vec.bool.index current 1)) + +(fn vec_string_local_value () -> string + (var current (vec string) (vec_string_pair "oak" "elm")) + (set current (vec_string_pair "pine" "slovo")) + (std.vec.string.index current 1)) + +(fn option_i32_local_value () -> i32 + (var current (option i32) (none i32)) + (set current (some i32 42)) + (match current + ((some payload) + payload) + ((none) + 0))) + +(fn option_i64_local_value () -> i64 + (var current (option i64) (none i64)) + (set current (some i64 42000000000i64)) + (match current + ((some payload) + payload) + ((none) + 0i64))) + +(fn option_f64_local_value () -> f64 + (var current (option f64) (none f64)) + (set current (some f64 42.5)) + (match current + ((some payload) + payload) + ((none) + 0.0))) + +(fn option_bool_local_value () -> bool + (var current (option bool) (none bool)) + (set current (some bool true)) + (match current + ((some payload) + payload) + ((none) + false))) + +(fn option_string_local_value () -> string + (var current (option string) (none string)) + (set current (some string "branch")) + (match current + ((some payload) + payload) + ((none) + "fallback"))) + +(fn result_i32_local_value () -> i32 + (var current (result i32 i32) (err i32 i32 7)) + (set current (ok i32 i32 42)) + (match current + ((ok payload) + payload) + ((err code) + code))) + +(fn result_i64_local_value () -> i64 + (var current (result i64 i32) (err i64 i32 7)) + (set current (ok i64 i32 42000000000i64)) + (match current + ((ok payload) + payload) + ((err code) + 0i64))) + +(fn result_f64_local_value () -> f64 + (var current (result f64 i32) (err f64 i32 7)) + (set current (ok f64 i32 42.5)) + (match current + ((ok payload) + payload) + ((err code) + 0.0))) + +(fn result_bool_local_value () -> bool + (var current (result bool i32) (err bool i32 7)) + (set current (ok bool i32 true)) + (match current + ((ok payload) + payload) + ((err code) + false))) + +(fn result_string_local_value () -> string + (var current (result string i32) (err string i32 7)) + (set current (ok string i32 "slovo")) + (match current + ((ok payload) + payload) + ((err code) + "fallback"))) + +(fn point_local_sum () -> i32 + (var current Point (make_point 1 2)) + (set current (make_point 20 22)) + (+ (. current x) (. current y))) + +(fn primitive_record_local_label () -> string + (var current PrimitiveRecord (make_primitive_record 3 "alpha")) + (set current (make_primitive_record 7 "beta")) + (. current label)) + +(fn numeric_record_local_ratio () -> f64 + (var current NumericRecord (make_numeric_record 10i64 1.0)) + (set current (make_numeric_record 20i64 3.5)) + (. current ratio)) + +(fn signal_local_code () -> i32 + (var current Signal (Signal.Red)) + (set current (Signal.Green)) + (match current + ((Signal.Red) + 1) + ((Signal.Yellow) + 2) + ((Signal.Green) + 3))) + +(fn reading_local_code () -> i32 + (var current Reading (Reading.Missing)) + (set current (Reading.Value 42)) + (match current + ((Reading.Missing) + 0) + ((Reading.Value payload) + payload))) + +(fn tagged_local_code () -> i32 + (var current TaggedReading (make_tagged (Signal.Yellow) (Reading.Missing))) + (set current (make_tagged (Signal.Green) (Reading.Value 42))) + (match (. current reading) + ((Reading.Missing) + 0) + ((Reading.Value payload) + payload))) + +(test "mutable string local set works" + (= (swap_string "oak" "slovo") "slovo")) + +(test "mutable vec i32 local set works" + (= (vec_i32_local_value) 41)) + +(test "mutable vec i64 local set works" + (= (vec_i64_local_value) 41i64)) + +(test "mutable vec f64 local set works" + (= (vec_f64_local_value) 41.0)) + +(test "mutable vec bool local set works" + (vec_bool_local_value)) + +(test "mutable vec string local set works" + (= (vec_string_local_value) "slovo")) + +(test "mutable option i32 local set works" + (= (option_i32_local_value) 42)) + +(test "mutable option i64 local set works" + (= (option_i64_local_value) 42000000000i64)) + +(test "mutable option f64 local set works" + (= (option_f64_local_value) 42.5)) + +(test "mutable option bool local set works" + (option_bool_local_value)) + +(test "mutable option string local set works" + (= (option_string_local_value) "branch")) + +(test "mutable result i32 local set works" + (= (result_i32_local_value) 42)) + +(test "mutable result i64 local set works" + (= (result_i64_local_value) 42000000000i64)) + +(test "mutable result f64 local set works" + (= (result_f64_local_value) 42.5)) + +(test "mutable result bool local set works" + (result_bool_local_value)) + +(test "mutable result string local set works" + (= (result_string_local_value) "slovo")) + +(test "mutable plain struct local set works" + (= (point_local_sum) 42)) + +(test "mutable primitive struct local set works" + (= (primitive_record_local_label) "beta")) + +(test "mutable numeric struct local set works" + (= (numeric_record_local_ratio) 3.5)) + +(test "mutable payloadless enum local set works" + (= (signal_local_code) 3)) + +(test "mutable payload enum local set works" + (= (reading_local_code) 42)) + +(test "mutable enum struct local set works" + (= (tagged_local_code) 42)) + +(fn main () -> i32 + (tagged_local_code)) diff --git a/docs/language/examples/formatter/composite-struct-fields.slo b/docs/language/examples/formatter/composite-struct-fields.slo new file mode 100644 index 0000000..fd3028a --- /dev/null +++ b/docs/language/examples/formatter/composite-struct-fields.slo @@ -0,0 +1,212 @@ +(module main) + +(enum Status Ready Blocked) + +(struct Pair + (left i32) + (right i32)) + +(struct CompositeRecord + (ints (vec i32)) + (wides (vec i64)) + (ratios (vec f64)) + (flags (vec bool)) + (labels (vec string)) + (maybe_count (option i32)) + (maybe_wide (option i64)) + (maybe_ratio (option f64)) + (maybe_flag (option bool)) + (maybe_label (option string)) + (count_result (result i32 i32)) + (wide_result (result i64 i32)) + (ratio_result (result f64 i32)) + (flag_result (result bool i32)) + (label_result (result string i32)) + (pair Pair) + (status Status)) + +(fn make_pair ((left i32) (right i32)) -> Pair + (Pair (left left) (right right))) + +(fn vec_i32_pair ((base i32)) -> (vec i32) + (let values (vec i32) (std.vec.i32.empty)) + (let first (vec i32) (std.vec.i32.append values base)) + (std.vec.i32.append first (+ base 1))) + +(fn vec_i64_pair ((base i64)) -> (vec i64) + (let values (vec i64) (std.vec.i64.empty)) + (let first (vec i64) (std.vec.i64.append values base)) + (std.vec.i64.append first (+ base 1i64))) + +(fn vec_f64_pair ((base f64)) -> (vec f64) + (let values (vec f64) (std.vec.f64.empty)) + (let first (vec f64) (std.vec.f64.append values base)) + (std.vec.f64.append first (+ base 1.0))) + +(fn vec_bool_pair ((first bool) (second bool)) -> (vec bool) + (let values (vec bool) (std.vec.bool.empty)) + (let left (vec bool) (std.vec.bool.append values first)) + (std.vec.bool.append left second)) + +(fn vec_string_pair ((first string) (second string)) -> (vec string) + (let values (vec string) (std.vec.string.empty)) + (let left (vec string) (std.vec.string.append values first)) + (std.vec.string.append left second)) + +(fn make_record () -> CompositeRecord + (CompositeRecord (ints (vec_i32_pair 10)) (wides (vec_i64_pair 100i64)) (ratios (vec_f64_pair 3.5)) (flags (vec_bool_pair false true)) (labels (vec_string_pair "oak" "elm")) (maybe_count (some i32 7)) (maybe_wide (some i64 7000000000i64)) (maybe_ratio (some f64 7.5)) (maybe_flag (some bool true)) (maybe_label (some string "alpha")) (count_result (ok i32 i32 9)) (wide_result (ok i64 i32 9000000000i64)) (ratio_result (ok f64 i32 9.25)) (flag_result (ok bool i32 true)) (label_result (ok string i32 "beta")) (pair (make_pair 20 22)) (status (Status.Ready)))) + +(fn echo_record ((record CompositeRecord)) -> CompositeRecord + record) + +(fn local_record () -> CompositeRecord + (let record CompositeRecord (make_record)) + (echo_record record)) + +(fn ints_second ((record CompositeRecord)) -> i32 + (std.vec.i32.index (. record ints) 1)) + +(fn wides_second ((record CompositeRecord)) -> i64 + (std.vec.i64.index (. record wides) 1)) + +(fn ratios_second ((record CompositeRecord)) -> f64 + (std.vec.f64.index (. record ratios) 1)) + +(fn flags_second ((record CompositeRecord)) -> bool + (std.vec.bool.index (. record flags) 1)) + +(fn labels_second ((record CompositeRecord)) -> string + (std.vec.string.index (. record labels) 1)) + +(fn maybe_count_value ((record CompositeRecord)) -> i32 + (match (. record maybe_count) + ((some payload) + payload) + ((none) + 0))) + +(fn maybe_wide_value ((record CompositeRecord)) -> i64 + (match (. record maybe_wide) + ((some payload) + payload) + ((none) + 0i64))) + +(fn maybe_ratio_value ((record CompositeRecord)) -> f64 + (match (. record maybe_ratio) + ((some payload) + payload) + ((none) + 0.0))) + +(fn maybe_flag_value ((record CompositeRecord)) -> bool + (match (. record maybe_flag) + ((some payload) + payload) + ((none) + false))) + +(fn maybe_label_value ((record CompositeRecord)) -> string + (match (. record maybe_label) + ((some payload) + payload) + ((none) + "missing"))) + +(fn count_result_value ((record CompositeRecord)) -> i32 + (match (. record count_result) + ((ok payload) + payload) + ((err code) + code))) + +(fn wide_result_value ((record CompositeRecord)) -> i64 + (match (. record wide_result) + ((ok payload) + payload) + ((err code) + 0i64))) + +(fn ratio_result_value ((record CompositeRecord)) -> f64 + (match (. record ratio_result) + ((ok payload) + payload) + ((err code) + 0.0))) + +(fn flag_result_value ((record CompositeRecord)) -> bool + (match (. record flag_result) + ((ok payload) + payload) + ((err code) + false))) + +(fn label_result_value ((record CompositeRecord)) -> string + (match (. record label_result) + ((ok payload) + payload) + ((err code) + "missing"))) + +(fn pair_total ((record CompositeRecord)) -> i32 + (+ (. (. record pair) left) (. (. record pair) right))) + +(fn is_ready ((record CompositeRecord)) -> bool + (= (. record status) (Status.Ready))) + +(test "struct vec i32 field access" + (= (ints_second (make_record)) 11)) + +(test "struct vec i64 field access" + (= (wides_second (make_record)) 101i64)) + +(test "struct vec f64 field access" + (= (ratios_second (make_record)) 4.5)) + +(test "struct vec bool field access" + (flags_second (make_record))) + +(test "struct vec string field access" + (= (labels_second (make_record)) "elm")) + +(test "struct option i32 field access" + (= (maybe_count_value (make_record)) 7)) + +(test "struct option i64 field access" + (= (maybe_wide_value (make_record)) 7000000000i64)) + +(test "struct option f64 field access" + (= (maybe_ratio_value (make_record)) 7.5)) + +(test "struct option bool field access" + (maybe_flag_value (make_record))) + +(test "struct option string field access" + (= (maybe_label_value (make_record)) "alpha")) + +(test "struct result i32 field access" + (= (count_result_value (make_record)) 9)) + +(test "struct result i64 field access" + (= (wide_result_value (make_record)) 9000000000i64)) + +(test "struct result f64 field access" + (= (ratio_result_value (make_record)) 9.25)) + +(test "struct result bool field access" + (flag_result_value (make_record))) + +(test "struct result string field access" + (= (label_result_value (make_record)) "beta")) + +(test "nested struct field access" + (= (pair_total (make_record)) 42)) + +(test "nested struct local param return call flow" + (= (pair_total (local_record)) 42)) + +(test "direct enum struct field equality" + (is_ready (make_record))) + +(fn main () -> i32 + (pair_total (local_record))) diff --git a/docs/language/examples/formatter/enum-basic.slo b/docs/language/examples/formatter/enum-basic.slo new file mode 100644 index 0000000..b4e3799 --- /dev/null +++ b/docs/language/examples/formatter/enum-basic.slo @@ -0,0 +1,53 @@ +(module main) + +(enum Signal Red Yellow Green) + +(fn red () -> Signal + (Signal.Red)) + +(fn yellow () -> Signal + (Signal.Yellow)) + +(fn echo ((signal Signal)) -> Signal + signal) + +(fn local_signal () -> Signal + (let signal Signal (Signal.Green)) + signal) + +(fn same_signal ((left Signal) (right Signal)) -> bool + (= left right)) + +(fn signal_code ((signal Signal)) -> i32 + (match signal + ((Signal.Red) + 1) + ((Signal.Yellow) + (let code i32 2) + code) + ((Signal.Green) + 3))) + +(fn call_flow () -> Signal + (echo (local_signal))) + +(test "enum constructor equality" + (= (Signal.Red) (red))) + +(test "enum local return call flow" + (= (call_flow) (Signal.Green))) + +(test "enum parameter equality" + (same_signal (yellow) (Signal.Yellow))) + +(test "enum match red" + (= (signal_code (Signal.Red)) 1)) + +(test "enum match multi expression arm" + (= (signal_code (Signal.Yellow)) 2)) + +(test "enum match green" + (= (signal_code (call_flow)) 3)) + +(fn main () -> i32 + (signal_code (call_flow))) diff --git a/docs/language/examples/formatter/enum-payload-direct-scalars.slo b/docs/language/examples/formatter/enum-payload-direct-scalars.slo new file mode 100644 index 0000000..eb69cc7 --- /dev/null +++ b/docs/language/examples/formatter/enum-payload-direct-scalars.slo @@ -0,0 +1,202 @@ +(module main) + +(enum Reading + Missing + (Value i32) + (Offset i32)) + +(enum WideReading + Missing + (Value i64)) + +(enum RatioReading + Missing + (Value f64)) + +(enum FlagReading + Missing + (Value bool)) + +(enum LabelReading + Missing + (Value string)) + +(struct PayloadRecord + (reading Reading) + (wide WideReading) + (ratio RatioReading) + (flag FlagReading) + (label LabelReading)) + +(fn value ((payload i32)) -> Reading + (Reading.Value payload)) + +(fn echo_reading ((reading Reading)) -> Reading + reading) + +(fn local_reading ((payload i32)) -> Reading + (let reading Reading (Reading.Value payload)) + reading) + +(fn call_reading ((payload i32)) -> Reading + (echo_reading (local_reading payload))) + +(fn reading_code ((reading Reading)) -> i32 + (match reading + ((Reading.Missing) + 0) + ((Reading.Value payload) + payload) + ((Reading.Offset payload) + (+ payload 100)))) + +(fn wide_value ((payload i64)) -> WideReading + (WideReading.Value payload)) + +(fn echo_wide ((reading WideReading)) -> WideReading + reading) + +(fn local_wide ((payload i64)) -> WideReading + (let reading WideReading (WideReading.Value payload)) + reading) + +(fn call_wide ((payload i64)) -> WideReading + (echo_wide (local_wide payload))) + +(fn wide_code ((reading WideReading)) -> i64 + (match reading + ((WideReading.Missing) + 0i64) + ((WideReading.Value payload) + payload))) + +(fn ratio_value ((payload f64)) -> RatioReading + (RatioReading.Value payload)) + +(fn ratio_code ((reading RatioReading)) -> f64 + (match reading + ((RatioReading.Missing) + 0.0) + ((RatioReading.Value payload) + payload))) + +(fn flag_value ((payload bool)) -> FlagReading + (FlagReading.Value payload)) + +(fn flag_code ((reading FlagReading)) -> bool + (match reading + ((FlagReading.Missing) + false) + ((FlagReading.Value payload) + payload))) + +(fn label_value ((payload string)) -> LabelReading + (LabelReading.Value payload)) + +(fn echo_label ((reading LabelReading)) -> LabelReading + reading) + +(fn local_label ((payload string)) -> LabelReading + (let reading LabelReading (LabelReading.Value payload)) + reading) + +(fn call_label ((payload string)) -> LabelReading + (echo_label (local_label payload))) + +(fn label_text ((reading LabelReading)) -> string + (match reading + ((LabelReading.Missing) + "") + ((LabelReading.Value payload) + payload))) + +(fn pack_record ((reading Reading) (wide WideReading) (ratio RatioReading) (flag FlagReading) (label LabelReading)) -> PayloadRecord + (PayloadRecord (reading reading) (wide wide) (ratio ratio) (flag flag) (label label))) + +(fn make_record () -> PayloadRecord + (pack_record (Reading.Offset 5) (WideReading.Value 4294967296i64) (RatioReading.Value 3.5) (FlagReading.Value true) (LabelReading.Value "hello"))) + +(fn echo_record ((record PayloadRecord)) -> PayloadRecord + record) + +(fn local_record () -> PayloadRecord + (let record PayloadRecord (make_record)) + (echo_record record)) + +(fn record_reading_code ((record PayloadRecord)) -> i32 + (reading_code (. record reading))) + +(fn record_wide_code ((record PayloadRecord)) -> i64 + (wide_code (. record wide))) + +(fn record_ratio_code ((record PayloadRecord)) -> f64 + (ratio_code (. record ratio))) + +(fn record_flag_code ((record PayloadRecord)) -> bool + (flag_code (. record flag))) + +(fn record_label_text ((record PayloadRecord)) -> string + (label_text (. record label))) + +(test "i32 enum constructor equality" + (= (Reading.Value 7) (value 7))) + +(test "i32 enum equality compares payload" + (if (= (Reading.Value 7) (Reading.Value 8)) + false + true)) + +(test "i32 enum payloadless equality" + (= (Reading.Missing) (echo_reading (Reading.Missing)))) + +(test "i32 enum local return call flow" + (= (call_reading 9) (Reading.Value 9))) + +(test "i64 enum constructor equality" + (= (WideReading.Value 4294967296i64) (wide_value 4294967296i64))) + +(test "i64 enum local return call flow" + (= (call_wide 4294967297i64) (WideReading.Value 4294967297i64))) + +(test "f64 enum constructor equality" + (= (RatioReading.Value 3.5) (ratio_value 3.5))) + +(test "f64 enum equality compares payload" + (if (= (RatioReading.Value 3.5) (RatioReading.Value 4.5)) + false + true)) + +(test "bool enum constructor equality" + (= (FlagReading.Value true) (flag_value true))) + +(test "bool enum match value" + (flag_code (FlagReading.Value true))) + +(test "string enum constructor equality" + (= (LabelReading.Value "hello") (label_value "hello"))) + +(test "string enum equality compares payload" + (if (= (LabelReading.Value "left") (LabelReading.Value "right")) + false + true)) + +(test "string enum local return call flow" + (= (call_label "hello") (LabelReading.Value "hello"))) + +(test "struct field enum i32 flow" + (= (record_reading_code (local_record)) 105)) + +(test "struct field enum i64 flow" + (= (record_wide_code (local_record)) 4294967296i64)) + +(test "struct field enum f64 flow" + (= (record_ratio_code (local_record)) 3.5)) + +(test "struct field enum bool flow" + (record_flag_code (local_record))) + +(test "struct field enum string flow" + (= (record_label_text (local_record)) "hello")) + +(fn main () -> i32 + (record_reading_code (local_record))) diff --git a/docs/language/examples/formatter/enum-payload-i32.slo b/docs/language/examples/formatter/enum-payload-i32.slo new file mode 100644 index 0000000..39e9007 --- /dev/null +++ b/docs/language/examples/formatter/enum-payload-i32.slo @@ -0,0 +1,63 @@ +(module main) + +(enum Reading + Missing + (Value i32) + (Offset i32)) + +(fn missing () -> Reading + (Reading.Missing)) + +(fn value ((payload i32)) -> Reading + (Reading.Value payload)) + +(fn echo ((reading Reading)) -> Reading + reading) + +(fn local_reading ((payload i32)) -> Reading + (let reading Reading (Reading.Value payload)) + reading) + +(fn same_reading ((left Reading) (right Reading)) -> bool + (= left right)) + +(fn reading_code ((reading Reading)) -> i32 + (match reading + ((Reading.Missing) + 0) + ((Reading.Value payload) + payload) + ((Reading.Offset payload) + (+ payload 100)))) + +(fn call_flow ((payload i32)) -> Reading + (echo (local_reading payload))) + +(test "enum payload constructor equality" + (= (Reading.Value 7) (value 7))) + +(test "enum payload equality compares payload" + (if (= (Reading.Value 7) (Reading.Value 8)) + false + true)) + +(test "enum payloadless equality" + (= (Reading.Missing) (missing))) + +(test "enum payload local return call flow" + (= (call_flow 9) (Reading.Value 9))) + +(test "enum payload parameter equality" + (same_reading (value 11) (Reading.Value 11))) + +(test "enum payload match missing" + (= (reading_code (Reading.Missing)) 0)) + +(test "enum payload match value" + (= (reading_code (Reading.Value 12)) 12)) + +(test "enum payload match offset" + (= (reading_code (Reading.Offset 5)) 105)) + +(fn main () -> i32 + (reading_code (call_flow 42))) diff --git a/docs/language/examples/formatter/enum-payload-structs.slo b/docs/language/examples/formatter/enum-payload-structs.slo new file mode 100644 index 0000000..3589746 --- /dev/null +++ b/docs/language/examples/formatter/enum-payload-structs.slo @@ -0,0 +1,78 @@ +(module main) + +(struct Packet + (values (array i32 3)) + (labels (array string 2)) + (flags (array bool 2))) + +(enum PacketState + Missing + (Live Packet) + (Cached Packet)) + +(fn make_packet ((base i32) (head string)) -> Packet + (Packet (values (array i32 base (+ base 1) (+ base 2))) (labels (array string head "tail")) (flags (array bool false true)))) + +(fn live ((packet Packet)) -> PacketState + (PacketState.Live packet)) + +(fn cached ((packet Packet)) -> PacketState + (PacketState.Cached packet)) + +(fn echo_state ((state PacketState)) -> PacketState + state) + +(fn local_state ((base i32) (head string)) -> PacketState + (let state PacketState (PacketState.Live (make_packet base head))) + state) + +(fn call_state ((base i32) (head string)) -> PacketState + (echo_state (cached (make_packet base head)))) + +(fn state_value ((state PacketState) (i i32)) -> i32 + (match state + ((PacketState.Missing) + 0) + ((PacketState.Live payload) + (index (. payload values) i)) + ((PacketState.Cached payload) + (index (. payload values) i)))) + +(fn state_label ((state PacketState) (i i32)) -> string + (match state + ((PacketState.Missing) + "") + ((PacketState.Live payload) + (index (. payload labels) i)) + ((PacketState.Cached payload) + (index (. payload labels) i)))) + +(fn state_flag ((state PacketState) (i i32)) -> bool + (match state + ((PacketState.Missing) + false) + ((PacketState.Live payload) + (index (. payload flags) i)) + ((PacketState.Cached payload) + (index (. payload flags) i)))) + +(test "struct payload missing arm" + (= (state_value (PacketState.Missing) 0) 0)) + +(test "struct payload live constructor flow" + (= (state_value (live (make_packet 7 "sun")) 2) 9)) + +(test "struct payload cached param return call flow" + (= (state_value (call_state 7 "sun") 1) 8)) + +(test "struct payload string array field access" + (= (state_label (call_state 7 "sun") 0) "sun")) + +(test "struct payload bool array field access" + (state_flag (call_state 7 "sun") 1)) + +(test "struct payload local return flow" + (= (state_label (local_state 9 "orb") 1) "tail")) + +(fn main () -> i32 + (state_value (call_state 7 "sun") 2)) diff --git a/docs/language/examples/formatter/enum-struct-fields.slo b/docs/language/examples/formatter/enum-struct-fields.slo new file mode 100644 index 0000000..46ff92e --- /dev/null +++ b/docs/language/examples/formatter/enum-struct-fields.slo @@ -0,0 +1,67 @@ +(module main) + +(enum Status Ready Blocked) + +(enum Reading + Missing + (Value i32)) + +(struct TaggedReading + (status Status) + (reading Reading)) + +(fn make_tagged ((status Status) (reading Reading)) -> TaggedReading + (TaggedReading (status status) (reading reading))) + +(fn ready_value ((payload i32)) -> TaggedReading + (make_tagged (Status.Ready) (Reading.Value payload))) + +(fn missing_blocked () -> TaggedReading + (make_tagged (Status.Blocked) (Reading.Missing))) + +(fn echo_tagged ((tagged TaggedReading)) -> TaggedReading + tagged) + +(fn local_tagged ((payload i32)) -> TaggedReading + (let tagged TaggedReading (ready_value payload)) + (echo_tagged tagged)) + +(fn status_of ((tagged TaggedReading)) -> Status + (. tagged status)) + +(fn reading_of ((tagged TaggedReading)) -> Reading + (. tagged reading)) + +(fn is_ready ((tagged TaggedReading)) -> bool + (= (. tagged status) (Status.Ready))) + +(fn reading_matches ((tagged TaggedReading) (reading Reading)) -> bool + (= (. tagged reading) reading)) + +(fn reading_code ((tagged TaggedReading)) -> i32 + (match (. tagged reading) + ((Reading.Missing) + 0) + ((Reading.Value payload) + payload))) + +(test "enum struct field payloadless equality" + (= (status_of (missing_blocked)) (Status.Blocked))) + +(test "enum struct field unary payload equality" + (reading_matches (ready_value 7) (Reading.Value 7))) + +(test "enum struct field access return" + (= (reading_of (missing_blocked)) (Reading.Missing))) + +(test "enum struct field local param return call flow" + (= (reading_code (local_tagged 9)) 9)) + +(test "enum struct field match missing" + (= (reading_code (missing_blocked)) 0)) + +(test "enum struct field status predicate" + (is_ready (ready_value 11))) + +(fn main () -> i32 + (reading_code (local_tagged 42))) diff --git a/docs/language/examples/formatter/f64-numeric-primitive.slo b/docs/language/examples/formatter/f64-numeric-primitive.slo new file mode 100644 index 0000000..edd20b5 --- /dev/null +++ b/docs/language/examples/formatter/f64-numeric-primitive.slo @@ -0,0 +1,32 @@ +(module main) + +(fn half ((value f64)) -> f64 + (/ value 2.0)) + +(fn weighted ((base f64) (scale f64)) -> f64 + (+ base (* scale 2.5))) + +(fn local_total () -> f64 + (let subtotal f64 (weighted 4.0 3.0)) + (- subtotal 1.5)) + +(fn close_enough ((value f64)) -> bool + (if (> value 9.0) + (< value 11.0) + false)) + +(fn exact_literal () -> bool + (= (half 7.0) 3.5)) + +(fn main () -> i32 + (std.io.print_f64 (local_total)) + (if (close_enough (local_total)) 0 1)) + +(test "f64 arithmetic returns exact fixture value" + (= (local_total) 10.0)) + +(test "f64 comparison works in predicates" + (close_enough (local_total))) + +(test "f64 division and equality" + (exact_literal)) diff --git a/docs/language/examples/formatter/f64-to-i32-result.slo b/docs/language/examples/formatter/f64-to-i32-result.slo new file mode 100644 index 0000000..601c149 --- /dev/null +++ b/docs/language/examples/formatter/f64-to-i32-result.slo @@ -0,0 +1,48 @@ +(module main) + +(fn narrow ((value f64)) -> (result i32 i32) + (std.num.f64_to_i32_result value)) + +(fn zero_ok () -> (result i32 i32) + (narrow (- 2.5 2.5))) + +(fn negative_ok () -> (result i32 i32) + (narrow (- 1.0 13.0))) + +(fn fractional_err () -> (result i32 i32) + (narrow (/ 7.0 2.0))) + +(fn above_high_err () -> (result i32 i32) + (narrow 2147483648.0)) + +(test "f64 zero narrows to i32" + (let value (result i32 i32) (zero_ok)) + (if (std.result.is_ok value) + (= (std.result.unwrap_ok value) 0) + false)) + +(test "negative f64 narrows to i32" + (let value (result i32 i32) (negative_ok)) + (if (std.result.is_ok value) + (= (std.result.unwrap_ok value) -12) + false)) + +(test "fractional f64 returns err" + (let value (result i32 i32) (fractional_err)) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "above i32 range f64 returns err" + (let value (result i32 i32) (above_high_err)) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(fn main () -> i32 + (let value (result i32 i32) (negative_ok)) + (if (std.result.is_ok value) + (if (= (std.result.unwrap_ok value) -12) + 0 + 1) + (std.result.unwrap_err value))) diff --git a/docs/language/examples/formatter/f64-to-i64-result.slo b/docs/language/examples/formatter/f64-to-i64-result.slo new file mode 100644 index 0000000..8aedccd --- /dev/null +++ b/docs/language/examples/formatter/f64-to-i64-result.slo @@ -0,0 +1,48 @@ +(module main) + +(fn narrow ((value f64)) -> (result i64 i32) + (std.num.f64_to_i64_result value)) + +(fn zero_ok () -> (result i64 i32) + (narrow (- 2.5 2.5))) + +(fn negative_ok () -> (result i64 i32) + (narrow (- 1.0 13.0))) + +(fn fractional_err () -> (result i64 i32) + (narrow (/ 7.0 2.0))) + +(fn above_high_err () -> (result i64 i32) + (narrow 9223372036854776000.0)) + +(test "f64 zero narrows to i64" + (let value (result i64 i32) (zero_ok)) + (if (std.result.is_ok value) + (= (std.result.unwrap_ok value) 0i64) + false)) + +(test "negative f64 narrows to i64" + (let value (result i64 i32) (negative_ok)) + (if (std.result.is_ok value) + (= (std.result.unwrap_ok value) -12i64) + false)) + +(test "fractional f64 returns err for i64" + (let value (result i64 i32) (fractional_err)) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "above i64 range f64 returns err" + (let value (result i64 i32) (above_high_err)) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(fn main () -> i32 + (let value (result i64 i32) (negative_ok)) + (if (std.result.is_ok value) + (if (= (std.result.unwrap_ok value) -12i64) + 0 + 1) + (std.result.unwrap_err value))) diff --git a/docs/language/examples/formatter/f64-to-string.slo b/docs/language/examples/formatter/f64-to-string.slo new file mode 100644 index 0000000..f24c865 --- /dev/null +++ b/docs/language/examples/formatter/f64-to-string.slo @@ -0,0 +1,40 @@ +(module main) + +(fn f64_zero_text () -> string + (std.num.f64_to_string (- 2.5 2.5))) + +(fn f64_fractional_text () -> string + (std.num.f64_to_string (/ 7.0 2.0))) + +(fn f64_negative_text () -> string + (std.num.f64_to_string (- 2.5 4.0))) + +(fn f64_whole_text () -> string + (std.num.f64_to_string (+ 7.0 3.0))) + +(test "f64 zero to string" + (= (f64_zero_text) "0.0")) + +(test "f64 fractional to string" + (= (f64_fractional_text) "3.5")) + +(test "f64 negative to string" + (= (f64_negative_text) "-1.5")) + +(test "f64 whole to string" + (= (f64_whole_text) "10.0")) + +(test "f64 negative string length" + (= (std.string.len (f64_negative_text)) 4)) + +(test "f64 whole string length" + (= (std.string.len (f64_whole_text)) 4)) + +(fn main () -> i32 + (std.io.print_string (f64_zero_text)) + (std.io.print_string (f64_fractional_text)) + (std.io.print_string (f64_negative_text)) + (std.io.print_string (f64_whole_text)) + (if (= (std.string.len (f64_fractional_text)) 3) + 0 + 1)) diff --git a/docs/language/examples/formatter/host-io-result.slo b/docs/language/examples/formatter/host-io-result.slo new file mode 100644 index 0000000..3ed43a6 --- /dev/null +++ b/docs/language/examples/formatter/host-io-result.slo @@ -0,0 +1,77 @@ +(module main) + +(fn fixture_path () -> string + "slovo-exp10-host-io-result.txt") + +(fn ok_text ((value string)) -> (result string i32) + (ok string i32 value)) + +(fn err_text ((code i32)) -> (result string i32) + (err string i32 code)) + +(fn result_text_or_error ((value (result string i32))) -> string + (match value + ((ok payload) + payload) + ((err code) + (std.io.print_i32 code) + "error"))) + +(fn result_text_len_or_code ((value (result string i32))) -> i32 + (match value + ((ok payload) + (std.string.len payload)) + ((err code) + code))) + +(fn write_fixture_result () -> (result i32 i32) + (std.fs.write_text_result (fixture_path) "exp10 host io")) + +(fn read_fixture_result () -> (result string i32) + (std.fs.write_text_result (fixture_path) "exp10 host io") + (std.fs.read_text_result (fixture_path))) + +(fn missing_env_result () -> (result string i32) + (std.env.get_result "SLOVO_EXP10_MISSING_ENV")) + +(fn first_arg_ok_score () -> i32 + (if (< 0 (std.process.argc)) + (if (is_ok (std.process.arg_result 0)) + 1 + 0) + 0)) + +(fn read_unwrapped_text () -> string + (unwrap_ok (read_fixture_result))) + +(fn missing_env_code () -> i32 + (unwrap_err (missing_env_result))) + +(test "string result constructor ok" + (= (result_text_or_error (ok_text "constructed")) "constructed")) + +(test "string result constructor err" + (= (result_text_len_or_code (err_text 1)) 1)) + +(test "missing env returns err 1" + (= (missing_env_code) 1)) + +(test "arg result is ok when argc is positive" + (= (first_arg_ok_score) (if (< 0 (std.process.argc)) + 1 + 0))) + +(test "write text result returns ok zero" + (= (unwrap_ok (write_fixture_result)) 0)) + +(test "read text result returns written text" + (= (read_unwrapped_text) "exp10 host io")) + +(test "read text result match observes string payload" + (= (result_text_len_or_code (read_fixture_result)) 13)) + +(fn main () -> i32 + (std.io.eprint "exp-10 host io result") + (if (is_ok (write_fixture_result)) + (unwrap_ok (write_fixture_result)) + 1)) diff --git a/docs/language/examples/formatter/host-io.slo b/docs/language/examples/formatter/host-io.slo new file mode 100644 index 0000000..7d1eec5 --- /dev/null +++ b/docs/language/examples/formatter/host-io.slo @@ -0,0 +1,34 @@ +(module main) + +(fn fixture_path () -> string + "slovo-exp3-host-io.txt") + +(fn write_fixture () -> i32 + (std.fs.write_text (fixture_path) "host io")) + +(fn read_fixture () -> string + (std.fs.read_text (fixture_path))) + +(fn missing_env () -> string + (std.env.get "SLOVO_EXP3_MISSING")) + +(fn arg_count_plus_one () -> i32 + (+ (std.process.argc) 1)) + +(test "missing env returns empty string" + (= (missing_env) "")) + +(test "argc is not negative" + (< 0 (arg_count_plus_one))) + +(test "write text returns success" + (= (write_fixture) 0)) + +(test "read text returns written text" + (= (read_fixture) "host io")) + +(fn main () -> i32 + (std.io.eprint "exp-3 host io") + (std.io.print_string (std.process.arg 0)) + (std.io.print_string (read_fixture)) + (std.fs.write_text (fixture_path) "host io")) diff --git a/docs/language/examples/formatter/i64-numeric-primitive.slo b/docs/language/examples/formatter/i64-numeric-primitive.slo new file mode 100644 index 0000000..f297f92 --- /dev/null +++ b/docs/language/examples/formatter/i64-numeric-primitive.slo @@ -0,0 +1,37 @@ +(module main) + +(fn base () -> i64 + 2147483648i64) + +(fn adjust ((value i64) (delta i64)) -> i64 + (+ value delta)) + +(fn doubled ((value i64)) -> i64 + (* value 2i64)) + +(fn local_total () -> i64 + (let offset i64 -7i64) + (adjust (- (doubled (base)) 0i64) offset)) + +(fn high_enough ((value i64)) -> bool + (if (> value 4294967280i64) + (< value 4294967300i64) + false)) + +(fn exact_i64 () -> bool + (= (local_total) 4294967289i64)) + +(fn main () -> i32 + (std.io.print_i64 (local_total)) + (if (high_enough (local_total)) 0 1)) + +(test "i64 arithmetic returns exact fixture value" + (exact_i64)) + +(test "i64 comparison works in predicates" + (high_enough (local_total))) + +(test "i64 division and ordering" + (if (>= (/ (local_total) 3i64) 1431655763i64) + (<= (/ (local_total) 3i64) 1431655763i64) + false)) diff --git a/docs/language/examples/formatter/if.slo b/docs/language/examples/formatter/if.slo new file mode 100644 index 0000000..b94c047 --- /dev/null +++ b/docs/language/examples/formatter/if.slo @@ -0,0 +1,20 @@ +(module main) + +(fn choose ((value i32)) -> i32 + (if (< value 3) + 10 + 20)) + +(test "if chooses then" + (= (choose 2) 10)) + +(test "if chooses else" + (= (choose 4) 20)) + +(test "if returns bool" + (if (< 1 2) + true + false)) + +(fn main () -> i32 + (choose 2)) diff --git a/docs/language/examples/formatter/integer-bitwise.slo b/docs/language/examples/formatter/integer-bitwise.slo new file mode 100644 index 0000000..30b8022 --- /dev/null +++ b/docs/language/examples/formatter/integer-bitwise.slo @@ -0,0 +1,58 @@ +(module main) + +(fn i32_and () -> i32 + (bit_and 6 3)) + +(fn i32_or () -> i32 + (bit_or 4 2)) + +(fn i32_xor () -> i32 + (bit_xor 7 3)) + +(fn i64_and () -> i64 + (bit_and 6i64 3i64)) + +(fn i64_or () -> i64 + (bit_or 4i64 2i64)) + +(fn i64_xor () -> i64 + (bit_xor 7i64 3i64)) + +(fn bitwise_ok () -> bool + (if (= (i32_and) 2) + (if (= (i32_or) 6) + (if (= (i32_xor) 4) + (if (= (i64_and) 2i64) + (if (= (i64_or) 6i64) + (= (i64_xor) 4i64) + false) + false) + false) + false) + false)) + +(fn main () -> i32 + (if (bitwise_ok) + 42 + 1)) + +(test "i32 bit and" + (= (i32_and) 2)) + +(test "i32 bit or" + (= (i32_or) 6)) + +(test "i32 bit xor" + (= (i32_xor) 4)) + +(test "i64 bit and" + (= (i64_and) 2i64)) + +(test "i64 bit or" + (= (i64_or) 6i64)) + +(test "i64 bit xor" + (= (i64_xor) 4i64)) + +(test "integer bitwise summary" + (bitwise_ok)) diff --git a/docs/language/examples/formatter/integer-remainder.slo b/docs/language/examples/formatter/integer-remainder.slo new file mode 100644 index 0000000..94615f0 --- /dev/null +++ b/docs/language/examples/formatter/integer-remainder.slo @@ -0,0 +1,42 @@ +(module main) + +(fn i32_remainder () -> i32 + (% 17 5)) + +(fn i32_signed_remainder () -> i32 + (% -17 5)) + +(fn i64_remainder () -> i64 + (% 42i64 5i64)) + +(fn i64_signed_remainder () -> i64 + (% -17i64 5i64)) + +(fn remainder_ok () -> bool + (if (= (i32_remainder) 2) + (if (= (i32_signed_remainder) -2) + (if (= (i64_remainder) 2i64) + (= (i64_signed_remainder) -2i64) + false) + false) + false)) + +(fn main () -> i32 + (if (remainder_ok) + 42 + 1)) + +(test "i32 remainder" + (= (i32_remainder) 2)) + +(test "i32 signed remainder" + (= (i32_signed_remainder) -2)) + +(test "i64 remainder" + (= (i64_remainder) 2i64)) + +(test "i64 signed remainder" + (= (i64_signed_remainder) -2i64)) + +(test "integer remainder summary" + (remainder_ok)) diff --git a/docs/language/examples/formatter/integer-to-string.slo b/docs/language/examples/formatter/integer-to-string.slo new file mode 100644 index 0000000..e73bd4d --- /dev/null +++ b/docs/language/examples/formatter/integer-to-string.slo @@ -0,0 +1,54 @@ +(module main) + +(fn i32_zero_text () -> string + (std.num.i32_to_string 0)) + +(fn i32_negative_text () -> string + (std.num.i32_to_string -7)) + +(fn i32_high_text () -> string + (std.num.i32_to_string 2147483647)) + +(fn i64_low_text () -> string + (std.num.i64_to_string -9223372036854775808i64)) + +(fn i64_high_text () -> string + (std.num.i64_to_string 9223372036854775807i64)) + +(fn i64_beyond_i32_text () -> string + (std.num.i64_to_string 2147483648i64)) + +(test "i32 zero to string" + (= (i32_zero_text) "0")) + +(test "i32 negative to string" + (= (i32_negative_text) "-7")) + +(test "i32 high to string" + (= (i32_high_text) "2147483647")) + +(test "i32 negative string length" + (= (std.string.len (i32_negative_text)) 2)) + +(test "i64 low to string" + (= (i64_low_text) "-9223372036854775808")) + +(test "i64 high to string" + (= (i64_high_text) "9223372036854775807")) + +(test "i64 beyond i32 to string" + (= (i64_beyond_i32_text) "2147483648")) + +(test "i64 low string length" + (= (std.string.len (i64_low_text)) 20)) + +(fn main () -> i32 + (std.io.print_string (i32_zero_text)) + (std.io.print_string (i32_negative_text)) + (std.io.print_string (i32_high_text)) + (std.io.print_string (i64_low_text)) + (std.io.print_string (i64_high_text)) + (std.io.print_string (i64_beyond_i32_text)) + (if (= (std.string.len (i64_high_text)) 19) + 0 + 1)) diff --git a/docs/language/examples/formatter/local-variables.slo b/docs/language/examples/formatter/local-variables.slo new file mode 100644 index 0000000..441eab9 --- /dev/null +++ b/docs/language/examples/formatter/local-variables.slo @@ -0,0 +1,65 @@ +(module main) + +(fn add_local ((a i32)) -> i32 + (let one i32 1) + (var total i32 (+ a one)) + (set total (+ total 1)) + total) + +(fn keep_flag ((flag bool)) -> bool + (let local_flag bool flag) + local_flag) + +(fn flip_flag ((flag bool)) -> bool + (var current bool flag) + (set current (if current + false + true)) + current) + +(fn add_wide_local ((base i64)) -> i64 + (var count i64 base) + (set count (+ count 2i64)) + count) + +(fn add_ratio_local ((base f64)) -> f64 + (var amount f64 base) + (set amount (+ amount 0.5)) + amount) + +(test "locals work" + (let base i32 2) + (var value i32 (add_local base)) + (set value (+ value 1)) + (= value 5)) + +(test "bool let locals work" + (let expected bool true) + (let actual bool (keep_flag expected)) + actual) + +(test "bool let locals preserve false" + (if (keep_flag false) + false + true)) + +(test "bool var set flips true" + (if (flip_flag true) + false + true)) + +(test "bool var set flips false" + (flip_flag false)) + +(test "i64 var set works" + (= (add_wide_local 40i64) 42i64)) + +(test "f64 var set works" + (= (add_ratio_local 41.5) 42.0)) + +(fn main () -> i32 + (var flag bool false) + (set flag (flip_flag flag)) + (if flag + (add_local 2) + 1)) diff --git a/docs/language/examples/formatter/numeric-struct-fields.slo b/docs/language/examples/formatter/numeric-struct-fields.slo new file mode 100644 index 0000000..6034d90 --- /dev/null +++ b/docs/language/examples/formatter/numeric-struct-fields.slo @@ -0,0 +1,82 @@ +(module main) + +(struct NumericRecord + (wide i64) + (ratio f64)) + +(fn make_record ((wide i64) (ratio f64)) -> NumericRecord + (NumericRecord (wide wide) (ratio ratio))) + +(fn literal_record () -> NumericRecord + (NumericRecord (wide 2147483648i64) (ratio 3.5))) + +(fn echo_record ((record NumericRecord)) -> NumericRecord + record) + +(fn local_record ((wide i64) (ratio f64)) -> NumericRecord + (let record NumericRecord (make_record wide ratio)) + (echo_record record)) + +(fn record_wide ((record NumericRecord)) -> i64 + (. record wide)) + +(fn record_ratio ((record NumericRecord)) -> f64 + (. record ratio)) + +(fn wide_total ((record NumericRecord)) -> i64 + (+ (. record wide) 10i64)) + +(fn ratio_total ((record NumericRecord)) -> f64 + (+ (. record ratio) 1.5)) + +(fn wide_in_range ((record NumericRecord)) -> bool + (if (> (. record wide) 2147483640i64) + (< (. record wide) 2147483660i64) + false)) + +(fn ratio_in_range ((record NumericRecord)) -> bool + (if (> (. record ratio) 3.0) + (< (. record ratio) 4.0) + false)) + +(fn wide_text ((record NumericRecord)) -> string + (std.num.i64_to_string (. record wide))) + +(fn ratio_text ((record NumericRecord)) -> string + (std.num.f64_to_string (. record ratio))) + +(test "numeric struct i64 field access" + (= (record_wide (literal_record)) 2147483648i64)) + +(test "numeric struct f64 field access" + (= (record_ratio (literal_record)) 3.5)) + +(test "numeric struct i64 arithmetic after access" + (= (wide_total (literal_record)) 2147483658i64)) + +(test "numeric struct f64 arithmetic after access" + (= (ratio_total (literal_record)) 5.0)) + +(test "numeric struct i64 comparison after access" + (wide_in_range (literal_record))) + +(test "numeric struct f64 comparison after access" + (ratio_in_range (literal_record))) + +(test "numeric struct local param return call flow" + (= (wide_total (local_record 2147483648i64 3.5)) 2147483658i64)) + +(test "numeric struct i64 format after access" + (= (wide_text (literal_record)) "2147483648")) + +(test "numeric struct f64 format after access" + (= (ratio_text (literal_record)) "3.5")) + +(fn main () -> i32 + (std.io.print_i64 (record_wide (literal_record))) + (std.io.print_f64 (record_ratio (literal_record))) + (if (wide_in_range (local_record 2147483648i64 3.5)) + (if (ratio_in_range (local_record 2147483648i64 3.5)) + 0 + 1) + 1)) diff --git a/docs/language/examples/formatter/numeric-widening-conversions.slo b/docs/language/examples/formatter/numeric-widening-conversions.slo new file mode 100644 index 0000000..8efc10c --- /dev/null +++ b/docs/language/examples/formatter/numeric-widening-conversions.slo @@ -0,0 +1,32 @@ +(module main) + +(fn base_i64 () -> i64 + (std.num.i32_to_i64 2147483647)) + +(fn widened_i64 () -> i64 + (+ (base_i64) 1i64)) + +(fn half_step () -> f64 + (+ (std.num.i32_to_f64 5) 0.5)) + +(fn wide_as_f64 () -> f64 + (std.num.i64_to_f64 (widened_i64))) + +(fn main () -> i32 + (std.io.print_i64 (widened_i64)) + (std.io.print_f64 (wide_as_f64)) + (if (= (widened_i64) 2147483648i64) + 0 + 1)) + +(test "i32 widens to i64" + (= (std.num.i32_to_i64 42) 42i64)) + +(test "i32 to i64 feeds i64 arithmetic" + (= (widened_i64) 2147483648i64)) + +(test "i32 widens to f64" + (= (std.num.i32_to_f64 42) 42.0)) + +(test "i64 widens to f64" + (= (wide_as_f64) 2147483648.0)) diff --git a/docs/language/examples/formatter/option-result-flow.slo b/docs/language/examples/formatter/option-result-flow.slo new file mode 100644 index 0000000..14b9ba0 --- /dev/null +++ b/docs/language/examples/formatter/option-result-flow.slo @@ -0,0 +1,160 @@ +(module main) + +(fn maybe_value ((value i32)) -> (option i32) + (some i32 value)) + +(fn maybe_empty () -> (option i32) + (none i32)) + +(fn maybe_wide_value ((value i64)) -> (option i64) + (some i64 value)) + +(fn maybe_wide_empty () -> (option i64) + (none i64)) + +(fn maybe_float_value ((value f64)) -> (option f64) + (some f64 value)) + +(fn maybe_float_empty () -> (option f64) + (none f64)) + +(fn maybe_flag_value ((value bool)) -> (option bool) + (some bool value)) + +(fn maybe_flag_empty () -> (option bool) + (none bool)) + +(fn maybe_string_value ((value string)) -> (option string) + (some string value)) + +(fn maybe_string_empty () -> (option string) + (none string)) + +(fn option_score ((value (option i32))) -> i32 + (if (is_some value) + 1 + 0)) + +(fn option_empty_score ((value (option i32))) -> i32 + (if (is_none value) + 1 + 0)) + +(fn option_wide_score ((value (option i64))) -> i32 + (if (is_some value) + 1 + 0)) + +(fn option_wide_empty_score ((value (option i64))) -> i32 + (if (is_none value) + 1 + 0)) + +(fn option_float_score ((value (option f64))) -> i32 + (if (is_some value) + 1 + 0)) + +(fn option_float_empty_score ((value (option f64))) -> i32 + (if (is_none value) + 1 + 0)) + +(fn option_flag_score ((value (option bool))) -> i32 + (if (is_some value) + 1 + 0)) + +(fn option_flag_empty_score ((value (option bool))) -> i32 + (if (is_none value) + 1 + 0)) + +(fn option_string_score ((value (option string))) -> i32 + (if (is_some value) + 1 + 0)) + +(fn option_string_empty_score ((value (option string))) -> i32 + (if (is_none value) + 1 + 0)) + +(fn result_ok_value ((value i32)) -> (result i32 i32) + (ok i32 i32 value)) + +(fn result_err_value ((code i32)) -> (result i32 i32) + (err i32 i32 code)) + +(fn result_success_score ((value (result i32 i32))) -> i32 + (if (is_ok value) + 1 + 0)) + +(fn result_failure_score ((value (result i32 i32))) -> i32 + (if (is_err value) + 1 + 0)) + +(fn option_local_flow () -> i32 + (let value (option i32) (maybe_value 42)) + (option_score value)) + +(fn option_wide_local_flow () -> i32 + (let value (option i64) (maybe_wide_value 2147483648i64)) + (option_wide_score value)) + +(fn option_float_local_flow () -> i32 + (let value (option f64) (maybe_float_value 42.5)) + (option_float_score value)) + +(fn option_flag_local_flow () -> i32 + (let value (option bool) (maybe_flag_value true)) + (option_flag_score value)) + +(fn option_string_local_flow () -> i32 + (let value (option string) (maybe_string_value "slovo")) + (option_string_score value)) + +(fn result_local_flow () -> i32 + (let value (result i32 i32) (result_err_value 7)) + (result_failure_score value)) + +(test "option local value flow" + (= (option_local_flow) 1)) + +(test "option call observation" + (= (option_empty_score (maybe_empty)) 1)) + +(test "option i64 local value flow" + (= (option_wide_local_flow) 1)) + +(test "option i64 call observation" + (= (option_wide_empty_score (maybe_wide_empty)) 1)) + +(test "option f64 local value flow" + (= (option_float_local_flow) 1)) + +(test "option f64 call observation" + (= (option_float_empty_score (maybe_float_empty)) 1)) + +(test "option bool local value flow" + (= (option_flag_local_flow) 1)) + +(test "option bool call observation" + (= (option_flag_empty_score (maybe_flag_empty)) 1)) + +(test "option string local value flow" + (= (option_string_local_flow) 1)) + +(test "option string call observation" + (= (option_string_empty_score (maybe_string_empty)) 1)) + +(test "result call observation" + (= (result_success_score (result_ok_value 42)) 1)) + +(test "result local value flow" + (= (result_local_flow) 1)) + +(fn main () -> i32 + 0) diff --git a/docs/language/examples/formatter/option-result-match.slo b/docs/language/examples/formatter/option-result-match.slo new file mode 100644 index 0000000..827d6a3 --- /dev/null +++ b/docs/language/examples/formatter/option-result-match.slo @@ -0,0 +1,185 @@ +(module main) + +(fn maybe_value ((value i32)) -> (option i32) + (some i32 value)) + +(fn maybe_empty () -> (option i32) + (none i32)) + +(fn maybe_wide_value ((value i64)) -> (option i64) + (some i64 value)) + +(fn maybe_wide_empty () -> (option i64) + (none i64)) + +(fn maybe_float_value ((value f64)) -> (option f64) + (some f64 value)) + +(fn maybe_float_empty () -> (option f64) + (none f64)) + +(fn maybe_flag_value ((value bool)) -> (option bool) + (some bool value)) + +(fn maybe_flag_empty () -> (option bool) + (none bool)) + +(fn maybe_string_value ((value string)) -> (option string) + (some string value)) + +(fn maybe_string_empty () -> (option string) + (none string)) + +(fn result_ok_value ((value i32)) -> (result i32 i32) + (ok i32 i32 value)) + +(fn result_err_value ((code i32)) -> (result i32 i32) + (err i32 i32 code)) + +(fn option_value_or ((value (option i32)) (fallback i32)) -> i32 + (match value + ((some payload) + payload) + ((none) + fallback))) + +(fn option_bump_or_zero ((value (option i32))) -> i32 + (match value + ((some payload) + (let bumped i32 (+ payload 1)) + bumped) + ((none) + 0))) + +(fn option_wide_value_or ((value (option i64)) (fallback i64)) -> i64 + (match value + ((some payload) + payload) + ((none) + fallback))) + +(fn option_wide_bump_or_zero ((value (option i64))) -> i64 + (match value + ((some payload) + (let bumped i64 (+ payload 1i64)) + bumped) + ((none) + 0i64))) + +(fn option_float_value_or ((value (option f64)) (fallback f64)) -> f64 + (match value + ((some payload) + payload) + ((none) + fallback))) + +(fn option_float_bump_or_zero ((value (option f64))) -> f64 + (match value + ((some payload) + (let bumped f64 (+ payload 1.0)) + bumped) + ((none) + 0.0))) + +(fn option_flag_value_or ((value (option bool)) (fallback bool)) -> bool + (match value + ((some payload) + payload) + ((none) + fallback))) + +(fn option_flag_selected_or ((value (option bool)) (fallback bool)) -> bool + (match value + ((some payload) + (if payload + true + false) + payload) + ((none) + fallback))) + +(fn option_string_value_or ((value (option string)) (fallback string)) -> string + (match value + ((some payload) + payload) + ((none) + fallback))) + +(fn option_string_selected_or ((value (option string)) (fallback string)) -> string + (match value + ((some payload) + (let chosen string payload) + chosen) + ((none) + fallback))) + +(fn result_value_or_code ((value (result i32 i32))) -> i32 + (match value + ((ok payload) + payload) + ((err code) + code))) + +(fn result_score ((value (result i32 i32))) -> i32 + (match value + ((ok payload) + (+ payload 10)) + ((err code) + code))) + +(test "option match some payload" + (= (option_value_or (maybe_value 42) 0) 42)) + +(test "option match none fallback" + (= (option_value_or (maybe_empty) 7) 7)) + +(test "option match multi expression arm" + (= (option_bump_or_zero (maybe_value 8)) 9)) + +(test "option i64 match some payload" + (= (option_wide_value_or (maybe_wide_value 2147483648i64) 0i64) 2147483648i64)) + +(test "option i64 match none fallback" + (= (option_wide_value_or (maybe_wide_empty) 7i64) 7i64)) + +(test "option i64 match multi expression arm" + (= (option_wide_bump_or_zero (maybe_wide_value 8i64)) 9i64)) + +(test "option f64 match some payload" + (= (option_float_value_or (maybe_float_value 42.5) 0.0) 42.5)) + +(test "option f64 match none fallback" + (= (option_float_value_or (maybe_float_empty) 7.0) 7.0)) + +(test "option f64 match multi expression arm" + (= (option_float_bump_or_zero (maybe_float_value 8.5)) 9.5)) + +(test "option bool match some payload" + (option_flag_value_or (maybe_flag_value true) false)) + +(test "option bool match none fallback" + (option_flag_value_or (maybe_flag_empty) true)) + +(test "option bool match multi expression arm" + (option_flag_selected_or (maybe_flag_value true) false)) + +(test "option string match some payload" + (= (option_string_value_or (maybe_string_value "slovo") "fallback") "slovo")) + +(test "option string match none fallback" + (= (option_string_value_or (maybe_string_empty) "fallback") "fallback")) + +(test "option string match multi expression arm" + (= (option_string_selected_or (maybe_string_value "oak") "fallback") "oak")) + +(test "result match ok payload" + (= (result_value_or_code (result_ok_value 30)) 30)) + +(test "result match err payload" + (= (result_value_or_code (result_err_value 5)) 5)) + +(test "result match computed arm" + (= (result_score (result_ok_value 2)) 12)) + +(fn main () -> i32 + 0) diff --git a/docs/language/examples/formatter/option-result-payload.slo b/docs/language/examples/formatter/option-result-payload.slo new file mode 100644 index 0000000..80af456 --- /dev/null +++ b/docs/language/examples/formatter/option-result-payload.slo @@ -0,0 +1,259 @@ +(module main) + +(fn maybe_value ((value i32)) -> (option i32) + (some i32 value)) + +(fn maybe_empty () -> (option i32) + (none i32)) + +(fn maybe_wide_value ((value i64)) -> (option i64) + (some i64 value)) + +(fn maybe_wide_empty () -> (option i64) + (none i64)) + +(fn maybe_float_value ((value f64)) -> (option f64) + (some f64 value)) + +(fn maybe_float_empty () -> (option f64) + (none f64)) + +(fn maybe_flag_value ((value bool)) -> (option bool) + (some bool value)) + +(fn maybe_flag_empty () -> (option bool) + (none bool)) + +(fn maybe_string_value ((value string)) -> (option string) + (some string value)) + +(fn maybe_string_empty () -> (option string) + (none string)) + +(fn result_ok_value ((value i32)) -> (result i32 i32) + (ok i32 i32 value)) + +(fn result_err_value ((code i32)) -> (result i32 i32) + (err i32 i32 code)) + +(fn option_direct_payload () -> i32 + (unwrap_some (some i32 42))) + +(fn option_param_payload ((value (option i32))) -> i32 + (unwrap_some value)) + +(fn option_local_payload () -> i32 + (let value (option i32) (maybe_value 21)) + (unwrap_some value)) + +(fn option_call_payload () -> i32 + (unwrap_some (maybe_value 22))) + +(fn option_guarded_payload ((value (option i32))) -> i32 + (if (is_some value) + (unwrap_some value) + 0)) + +(fn option_wide_direct_payload () -> i64 + (unwrap_some (some i64 2147483648i64))) + +(fn option_wide_param_payload ((value (option i64))) -> i64 + (unwrap_some value)) + +(fn option_wide_local_payload () -> i64 + (let value (option i64) (maybe_wide_value 21i64)) + (unwrap_some value)) + +(fn option_wide_call_payload () -> i64 + (unwrap_some (maybe_wide_value 22i64))) + +(fn option_wide_guarded_payload ((value (option i64))) -> i64 + (if (is_some value) + (unwrap_some value) + 0i64)) + +(fn option_float_direct_payload () -> f64 + (unwrap_some (some f64 42.5))) + +(fn option_float_param_payload ((value (option f64))) -> f64 + (unwrap_some value)) + +(fn option_float_local_payload () -> f64 + (let value (option f64) (maybe_float_value 21.5)) + (unwrap_some value)) + +(fn option_float_call_payload () -> f64 + (unwrap_some (maybe_float_value 22.5))) + +(fn option_float_guarded_payload ((value (option f64))) -> f64 + (if (is_some value) + (unwrap_some value) + 0.0)) + +(fn option_flag_direct_payload () -> bool + (unwrap_some (some bool true))) + +(fn option_flag_param_payload ((value (option bool))) -> bool + (unwrap_some value)) + +(fn option_flag_local_payload () -> bool + (let value (option bool) (maybe_flag_value true)) + (unwrap_some value)) + +(fn option_flag_call_payload () -> bool + (unwrap_some (maybe_flag_value true))) + +(fn option_flag_guarded_payload ((value (option bool))) -> bool + (if (is_some value) + (unwrap_some value) + false)) + +(fn option_string_direct_payload () -> string + (unwrap_some (some string "slovo"))) + +(fn option_string_param_payload ((value (option string))) -> string + (unwrap_some value)) + +(fn option_string_local_payload () -> string + (let value (option string) (maybe_string_value "oak")) + (unwrap_some value)) + +(fn option_string_call_payload () -> string + (unwrap_some (maybe_string_value "pine"))) + +(fn option_string_guarded_payload ((value (option string))) -> string + (if (is_some value) + (unwrap_some value) + "fallback")) + +(fn result_ok_direct_payload () -> i32 + (unwrap_ok (ok i32 i32 30))) + +(fn result_err_direct_payload () -> i32 + (unwrap_err (err i32 i32 7))) + +(fn result_ok_param_payload ((value (result i32 i32))) -> i32 + (unwrap_ok value)) + +(fn result_err_param_payload ((value (result i32 i32))) -> i32 + (unwrap_err value)) + +(fn result_ok_local_payload () -> i32 + (let value (result i32 i32) (result_ok_value 31)) + (unwrap_ok value)) + +(fn result_err_call_payload () -> i32 + (unwrap_err (result_err_value 9))) + +(test "unwrap some direct constructor" + (= (option_direct_payload) 42)) + +(test "unwrap some local" + (= (option_local_payload) 21)) + +(test "unwrap some call" + (= (option_call_payload) 22)) + +(test "unwrap some parameter" + (= (option_param_payload (maybe_value 23)) 23)) + +(test "guarded unwrap some present" + (= (option_guarded_payload (maybe_value 24)) 24)) + +(test "guarded unwrap some absent" + (= (option_guarded_payload (maybe_empty)) 0)) + +(test "unwrap some i64 direct constructor" + (= (option_wide_direct_payload) 2147483648i64)) + +(test "unwrap some i64 local" + (= (option_wide_local_payload) 21i64)) + +(test "unwrap some i64 call" + (= (option_wide_call_payload) 22i64)) + +(test "unwrap some i64 parameter" + (= (option_wide_param_payload (maybe_wide_value 23i64)) 23i64)) + +(test "guarded unwrap some i64 present" + (= (option_wide_guarded_payload (maybe_wide_value 24i64)) 24i64)) + +(test "guarded unwrap some i64 absent" + (= (option_wide_guarded_payload (maybe_wide_empty)) 0i64)) + +(test "unwrap some f64 direct constructor" + (= (option_float_direct_payload) 42.5)) + +(test "unwrap some f64 local" + (= (option_float_local_payload) 21.5)) + +(test "unwrap some f64 call" + (= (option_float_call_payload) 22.5)) + +(test "unwrap some f64 parameter" + (= (option_float_param_payload (maybe_float_value 23.5)) 23.5)) + +(test "guarded unwrap some f64 present" + (= (option_float_guarded_payload (maybe_float_value 24.5)) 24.5)) + +(test "guarded unwrap some f64 absent" + (= (option_float_guarded_payload (maybe_float_empty)) 0.0)) + +(test "unwrap some bool direct constructor" + (option_flag_direct_payload)) + +(test "unwrap some bool local" + (option_flag_local_payload)) + +(test "unwrap some bool call" + (option_flag_call_payload)) + +(test "unwrap some bool parameter" + (option_flag_param_payload (maybe_flag_value true))) + +(test "guarded unwrap some bool present" + (option_flag_guarded_payload (maybe_flag_value true))) + +(test "guarded unwrap some bool absent" + (if (option_flag_guarded_payload (maybe_flag_empty)) + false + true)) + +(test "unwrap some string direct constructor" + (= (option_string_direct_payload) "slovo")) + +(test "unwrap some string local" + (= (option_string_local_payload) "oak")) + +(test "unwrap some string call" + (= (option_string_call_payload) "pine")) + +(test "unwrap some string parameter" + (= (option_string_param_payload (maybe_string_value "birch")) "birch")) + +(test "guarded unwrap some string present" + (= (option_string_guarded_payload (maybe_string_value "cedar")) "cedar")) + +(test "guarded unwrap some string absent" + (= (option_string_guarded_payload (maybe_string_empty)) "fallback")) + +(test "unwrap ok direct constructor" + (= (result_ok_direct_payload) 30)) + +(test "unwrap err direct constructor" + (= (result_err_direct_payload) 7)) + +(test "unwrap ok parameter" + (= (result_ok_param_payload (result_ok_value 32)) 32)) + +(test "unwrap err parameter" + (= (result_err_param_payload (result_err_value 8)) 8)) + +(test "unwrap ok local" + (= (result_ok_local_payload) 31)) + +(test "unwrap err call" + (= (result_err_call_payload) 9)) + +(fn main () -> i32 + 0) diff --git a/docs/language/examples/formatter/option-result.slo b/docs/language/examples/formatter/option-result.slo new file mode 100644 index 0000000..d54a5fe --- /dev/null +++ b/docs/language/examples/formatter/option-result.slo @@ -0,0 +1,40 @@ +(module main) + +(fn maybe_value () -> (option i32) + (some i32 42)) + +(fn maybe_empty () -> (option i32) + (none i32)) + +(fn maybe_wide_value () -> (option i64) + (some i64 2147483648i64)) + +(fn maybe_wide_empty () -> (option i64) + (none i64)) + +(fn maybe_float_value () -> (option f64) + (some f64 42.5)) + +(fn maybe_float_empty () -> (option f64) + (none f64)) + +(fn maybe_flag_value () -> (option bool) + (some bool true)) + +(fn maybe_flag_empty () -> (option bool) + (none bool)) + +(fn maybe_string_value () -> (option string) + (some string "slovo")) + +(fn maybe_string_empty () -> (option string) + (none string)) + +(fn result_ok () -> (result i32 i32) + (ok i32 i32 42)) + +(fn result_err () -> (result i32 i32) + (err i32 i32 7)) + +(fn main () -> i32 + 0) diff --git a/docs/language/examples/formatter/owned-string-concat.slo b/docs/language/examples/formatter/owned-string-concat.slo new file mode 100644 index 0000000..808c345 --- /dev/null +++ b/docs/language/examples/formatter/owned-string-concat.slo @@ -0,0 +1,21 @@ +(module main) + +(fn join ((left string) (right string)) -> string + (std.string.concat left right)) + +(fn greeting () -> string + (std.string.concat "hello, " "slovo")) + +(fn greeting_len () -> i32 + (std.string.len (greeting))) + +(test "owned string concat equality" + (= (greeting) "hello, slovo")) + +(test "owned string concat length" + (= (greeting_len) 12)) + +(fn main () -> i32 + (std.io.print_string (join "hello, " "slovo")) + (std.io.print_i32 (std.string.len (join "ab" "cd"))) + 0) diff --git a/docs/language/examples/formatter/primitive-struct-fields.slo b/docs/language/examples/formatter/primitive-struct-fields.slo new file mode 100644 index 0000000..5083c6b --- /dev/null +++ b/docs/language/examples/formatter/primitive-struct-fields.slo @@ -0,0 +1,66 @@ +(module main) + +(struct PrimitiveRecord + (id i32) + (active bool) + (label string)) + +(fn make_record ((id i32) (label string)) -> PrimitiveRecord + (PrimitiveRecord (id id) (active (= id 7)) (label label))) + +(fn expected_record () -> PrimitiveRecord + (make_record 7 "alpha")) + +(fn inactive_record () -> PrimitiveRecord + (PrimitiveRecord (id 3) (active false) (label "beta"))) + +(fn echo_record ((record PrimitiveRecord)) -> PrimitiveRecord + record) + +(fn local_record ((label string)) -> PrimitiveRecord + (let record PrimitiveRecord (make_record 7 label)) + (echo_record record)) + +(fn record_id ((record PrimitiveRecord)) -> i32 + (. record id)) + +(fn record_active ((record PrimitiveRecord)) -> bool + (. record active)) + +(fn record_label ((record PrimitiveRecord)) -> string + (. record label)) + +(fn label_matches ((record PrimitiveRecord) (label string)) -> bool + (= (. record label) label)) + +(fn active_score ((record PrimitiveRecord)) -> i32 + (if (. record active) + (. record id) + 0)) + +(fn label_len ((record PrimitiveRecord)) -> i32 + (std.string.len (. record label))) + +(test "primitive struct i32 field access" + (= (record_id (expected_record)) 7)) + +(test "primitive struct bool field predicate" + (record_active (expected_record))) + +(test "primitive struct bool field false branch" + (= (active_score (inactive_record)) 0)) + +(test "primitive struct string field equality" + (label_matches (expected_record) "alpha")) + +(test "primitive struct string field length" + (= (label_len (expected_record)) 5)) + +(test "primitive struct local param return call flow" + (= (active_score (local_record "alpha")) 7)) + +(test "primitive struct string field access return" + (= (record_label (local_record "alpha")) "alpha")) + +(fn main () -> i32 + (active_score (local_record "alpha"))) diff --git a/docs/language/examples/formatter/print-bool.slo b/docs/language/examples/formatter/print-bool.slo new file mode 100644 index 0000000..04d8439 --- /dev/null +++ b/docs/language/examples/formatter/print-bool.slo @@ -0,0 +1,7 @@ +(module main) + +(fn main () -> i32 + (print_bool true) + (print_bool false) + (print_bool (= "slovo" "slovo")) + 0) diff --git a/docs/language/examples/formatter/random.slo b/docs/language/examples/formatter/random.slo new file mode 100644 index 0000000..fb387ce --- /dev/null +++ b/docs/language/examples/formatter/random.slo @@ -0,0 +1,16 @@ +(module main) + +(fn random_i32 () -> i32 + (std.random.i32)) + +(fn random_is_non_negative () -> bool + (if (< (random_i32) 0) + false + true)) + +(test "random i32 is non-negative" + (random_is_non_negative)) + +(fn main () -> i32 + (std.io.print_bool (random_is_non_negative)) + 0) diff --git a/docs/language/examples/formatter/result-helpers.slo b/docs/language/examples/formatter/result-helpers.slo new file mode 100644 index 0000000..70ace9d --- /dev/null +++ b/docs/language/examples/formatter/result-helpers.slo @@ -0,0 +1,71 @@ +(module main) + +(fn i32_ok () -> (result i32 i32) + (ok i32 i32 42)) + +(fn i32_err () -> (result i32 i32) + (err i32 i32 7)) + +(fn text_ok () -> (result string i32) + (ok string i32 "value")) + +(fn text_err () -> (result string i32) + (err string i32 9)) + +(fn observe_i32_ok () -> bool + (std.result.is_ok (i32_ok))) + +(fn observe_i32_err () -> bool + (std.result.is_err (i32_err))) + +(fn unwrap_i32_ok () -> i32 + (std.result.unwrap_ok (i32_ok))) + +(fn unwrap_i32_err () -> i32 + (std.result.unwrap_err (i32_err))) + +(fn observe_text_ok () -> bool + (std.result.is_ok (text_ok))) + +(fn observe_text_err () -> bool + (std.result.is_err (text_err))) + +(fn unwrap_text_ok () -> string + (std.result.unwrap_ok (text_ok))) + +(fn unwrap_text_err () -> i32 + (std.result.unwrap_err (text_err))) + +(fn legacy_i32_ok () -> bool + (is_ok (i32_ok))) + +(fn legacy_text_err () -> i32 + (unwrap_err (text_err))) + +(test "std result i32 observers" + (if (std.result.is_ok (i32_ok)) + (std.result.is_err (i32_err)) + false)) + +(test "std result i32 unwraps" + (= (+ (std.result.unwrap_ok (i32_ok)) (std.result.unwrap_err (i32_err))) 49)) + +(test "std result string observers" + (if (std.result.is_ok (text_ok)) + (std.result.is_err (text_err)) + false)) + +(test "std result string unwraps" + (if (= (std.result.unwrap_ok (text_ok)) "value") + (= (std.result.unwrap_err (text_err)) 9) + false)) + +(test "legacy result helpers still work" + (if (is_ok (i32_ok)) + (= (unwrap_err (text_err)) 9) + false)) + +(fn main () -> i32 + (if (std.result.is_ok (i32_ok)) + (std.result.unwrap_ok (i32_ok)) + (std.result.unwrap_err (i32_err)))) diff --git a/docs/language/examples/formatter/standard-runtime.slo b/docs/language/examples/formatter/standard-runtime.slo new file mode 100644 index 0000000..607fd52 --- /dev/null +++ b/docs/language/examples/formatter/standard-runtime.slo @@ -0,0 +1,22 @@ +(module main) + +(fn label () -> string + "standard") + +(fn echo ((value string)) -> string + value) + +(fn label_len () -> i32 + (std.string.len (echo "standard"))) + +(test "std string equality" + (= (echo "standard") "standard")) + +(test "std string byte length" + (= (label_len) 8)) + +(fn main () -> i32 + (std.io.print_string (echo "standard")) + (std.io.print_bool (= (echo "standard") "standard")) + (std.io.print_i32 (std.string.len "standard")) + 0) diff --git a/docs/language/examples/formatter/std-source-layout-alpha.slo b/docs/language/examples/formatter/std-source-layout-alpha.slo new file mode 100644 index 0000000..cb3ebcc --- /dev/null +++ b/docs/language/examples/formatter/std-source-layout-alpha.slo @@ -0,0 +1,17 @@ +(module main) + +(fn local_abs_i32 ((value i32)) -> i32 + (if (< value 0) + (- 0 value) + value)) + +(fn local_square_i32 ((value i32)) -> i32 + (* value value)) + +(fn main () -> i32 + (std.io.print_i32 (local_abs_i32 (- 0 3))) + (std.io.print_i32 (local_square_i32 4)) + 0) + +(test "layout contract helper shape" + (= (local_square_i32 (local_abs_i32 (- 0 3))) 9)) diff --git a/docs/language/examples/formatter/stdin-result.slo b/docs/language/examples/formatter/stdin-result.slo new file mode 100644 index 0000000..2a55836 --- /dev/null +++ b/docs/language/examples/formatter/stdin-result.slo @@ -0,0 +1,38 @@ +(module main) + +(fn stdin_result () -> (result string i32) + (std.io.read_stdin_result)) + +(fn stdin_text_or_empty ((value (result string i32))) -> string + (match value + ((ok text) + text) + ((err code) + ""))) + +(fn stdin_len_or_code ((value (result string i32))) -> i32 + (match value + ((ok text) + (std.string.len text)) + ((err code) + code))) + +(fn stdin_ok_len () -> i32 + (std.string.len (unwrap_ok (stdin_result)))) + +(test "stdin result test runner returns ok" + (is_ok (stdin_result))) + +(test "stdin result payload length matches match" + (let value (result string i32) (stdin_result)) + (= (std.string.len (unwrap_ok value)) (stdin_len_or_code value))) + +(test "stdin result match observes ok payload" + (let value (result string i32) (stdin_result)) + (= (std.string.len (stdin_text_or_empty value)) (stdin_len_or_code value))) + +(fn main () -> i32 + (let value (result string i32) (stdin_result)) + (if (is_ok value) + (std.string.len (unwrap_ok value)) + 1)) diff --git a/docs/language/examples/formatter/string-parse-bool-result.slo b/docs/language/examples/formatter/string-parse-bool-result.slo new file mode 100644 index 0000000..881f083 --- /dev/null +++ b/docs/language/examples/formatter/string-parse-bool-result.slo @@ -0,0 +1,55 @@ +(module main) + +(fn parse_bool ((text string)) -> (result bool i32) + (std.string.parse_bool_result text)) + +(fn bool_score ((text string)) -> i32 + (let value (result bool i32) (parse_bool text)) + (if (std.result.is_ok value) + (if (std.result.unwrap_ok value) + 1 + 0) + (std.result.unwrap_err value))) + +(test "parse bool true ok" + (let value (result bool i32) (parse_bool "true")) + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + false)) + +(test "parse bool false ok" + (let value (result bool i32) (parse_bool "false")) + (if (std.result.is_ok value) + (if (std.result.unwrap_ok value) + false + true) + false)) + +(test "parse bool uppercase err" + (let value (result bool i32) (parse_bool "TRUE")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "parse bool empty err" + (let value (result bool i32) (parse_bool "")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "parse bool whitespace err" + (let value (result bool i32) (parse_bool " true")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "parse bool helper flow" + (= (+ (bool_score "true") (bool_score "false")) 1)) + +(fn main () -> i32 + (let value (result bool i32) (parse_bool "true")) + (if (std.result.is_ok value) + (if (std.result.unwrap_ok value) + 0 + 1) + (std.result.unwrap_err value))) diff --git a/docs/language/examples/formatter/string-parse-f64-result.slo b/docs/language/examples/formatter/string-parse-f64-result.slo new file mode 100644 index 0000000..6d48b57 --- /dev/null +++ b/docs/language/examples/formatter/string-parse-f64-result.slo @@ -0,0 +1,36 @@ +(module main) + +(fn parse_f64 ((text string)) -> (result f64 i32) + (std.string.parse_f64_result text)) + +(test "parse f64 decimal ok" + (let value (result f64 i32) (parse_f64 "12.5")) + (if (std.result.is_ok value) + (= (std.result.unwrap_ok value) 12.5) + false)) + +(test "parse f64 negative decimal ok" + (let value (result f64 i32) (parse_f64 "-0.25")) + (if (std.result.is_ok value) + (= (std.result.unwrap_ok value) (- 0.0 0.25)) + false)) + +(test "parse f64 text err" + (let value (result f64 i32) (parse_f64 "abc")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "parse f64 nan err" + (let value (result f64 i32) (parse_f64 "nan")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(fn main () -> i32 + (let value (result f64 i32) (parse_f64 "-0.25")) + (if (std.result.is_ok value) + (if (= (std.result.unwrap_ok value) (- 0.0 0.25)) + 0 + 1) + (std.result.unwrap_err value))) diff --git a/docs/language/examples/formatter/string-parse-i32-result.slo b/docs/language/examples/formatter/string-parse-i32-result.slo new file mode 100644 index 0000000..6f02489 --- /dev/null +++ b/docs/language/examples/formatter/string-parse-i32-result.slo @@ -0,0 +1,41 @@ +(module main) + +(fn parse_text ((text string)) -> (result i32 i32) + (std.string.parse_i32_result text)) + +(fn parse_stdin_text () -> (result i32 i32) + (let input (result string i32) (std.io.read_stdin_result)) + (match input + ((ok text) + (std.string.parse_i32_result text)) + ((err code) + (err i32 i32 code)))) + +(test "parse 42 ok" + (= (unwrap_ok (parse_text "42")) 42)) + +(test "parse negative 7 ok" + (= (unwrap_ok (parse_text "-7")) -7)) + +(test "parse empty err" + (= (unwrap_err (parse_text "")) 1)) + +(test "parse trailing byte err" + (= (unwrap_err (parse_text "12x")) 1)) + +(test "parse overflow err" + (= (unwrap_err (parse_text "2147483648")) 1)) + +(test "parse stdin text structurally" + (let value (result i32 i32) (parse_stdin_text)) + (match value + ((ok parsed) + (= parsed parsed)) + ((err code) + (= code 1)))) + +(fn main () -> i32 + (let value (result i32 i32) (parse_stdin_text)) + (if (is_ok value) + (unwrap_ok value) + (unwrap_err value))) diff --git a/docs/language/examples/formatter/string-parse-i64-result.slo b/docs/language/examples/formatter/string-parse-i64-result.slo new file mode 100644 index 0000000..2b35533 --- /dev/null +++ b/docs/language/examples/formatter/string-parse-i64-result.slo @@ -0,0 +1,45 @@ +(module main) + +(fn parse_i64 ((text string)) -> (result i64 i32) + (std.string.parse_i64_result text)) + +(fn parsed_text ((text string)) -> string + (std.num.i64_to_string (std.result.unwrap_ok (parse_i64 text)))) + +(test "parse i64 zero ok" + (= (parsed_text "0") "0")) + +(test "parse i64 negative ok" + (= (parsed_text "-7") "-7")) + +(test "parse i64 low ok" + (= (parsed_text "-9223372036854775808") "-9223372036854775808")) + +(test "parse i64 high ok" + (= (parsed_text "9223372036854775807") "9223372036854775807")) + +(test "parse i64 empty err" + (let value (result i64 i32) (parse_i64 "")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "parse i64 plus err" + (let value (result i64 i32) (parse_i64 "+1")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "parse i64 above range err" + (let value (result i64 i32) (parse_i64 "9223372036854775808")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(fn main () -> i32 + (let value (result i64 i32) (parse_i64 "-7")) + (if (std.result.is_ok value) + (if (= (std.num.i64_to_string (std.result.unwrap_ok value)) "-7") + 0 + 1) + (std.result.unwrap_err value))) diff --git a/docs/language/examples/formatter/string-parse-u32-result.slo b/docs/language/examples/formatter/string-parse-u32-result.slo new file mode 100644 index 0000000..76fad8d --- /dev/null +++ b/docs/language/examples/formatter/string-parse-u32-result.slo @@ -0,0 +1,45 @@ +(module main) + +(fn parse_u32 ((text string)) -> (result u32 i32) + (std.string.parse_u32_result text)) + +(fn parsed_text ((text string)) -> string + (std.num.u32_to_string (std.result.unwrap_ok (parse_u32 text)))) + +(test "parse u32 zero ok" + (= (parsed_text "0") "0")) + +(test "parse u32 high ok" + (= (parsed_text "4294967295") "4294967295")) + +(test "parse u32 empty err" + (let value (result u32 i32) (parse_u32 "")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "parse u32 plus err" + (let value (result u32 i32) (parse_u32 "+1")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "parse u32 negative err" + (let value (result u32 i32) (parse_u32 "-1")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "parse u32 above range err" + (let value (result u32 i32) (parse_u32 "4294967296")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(fn main () -> i32 + (let value (result u32 i32) (parse_u32 "42")) + (if (std.result.is_ok value) + (if (= (std.num.u32_to_string (std.result.unwrap_ok value)) "42") + 0 + 1) + (std.result.unwrap_err value))) diff --git a/docs/language/examples/formatter/string-parse-u64-result.slo b/docs/language/examples/formatter/string-parse-u64-result.slo new file mode 100644 index 0000000..362ac5f --- /dev/null +++ b/docs/language/examples/formatter/string-parse-u64-result.slo @@ -0,0 +1,45 @@ +(module main) + +(fn parse_u64 ((text string)) -> (result u64 i32) + (std.string.parse_u64_result text)) + +(fn parsed_text ((text string)) -> string + (std.num.u64_to_string (std.result.unwrap_ok (parse_u64 text)))) + +(test "parse u64 zero ok" + (= (parsed_text "0") "0")) + +(test "parse u64 high ok" + (= (parsed_text "18446744073709551615") "18446744073709551615")) + +(test "parse u64 empty err" + (let value (result u64 i32) (parse_u64 "")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "parse u64 plus err" + (let value (result u64 i32) (parse_u64 "+1")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "parse u64 negative err" + (let value (result u64 i32) (parse_u64 "-1")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "parse u64 above range err" + (let value (result u64 i32) (parse_u64 "18446744073709551616")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(fn main () -> i32 + (let value (result u64 i32) (parse_u64 "42")) + (if (std.result.is_ok value) + (if (= (std.num.u64_to_string (std.result.unwrap_ok value)) "42") + 0 + 1) + (std.result.unwrap_err value))) diff --git a/docs/language/examples/formatter/string-print.slo b/docs/language/examples/formatter/string-print.slo new file mode 100644 index 0000000..abd8fa5 --- /dev/null +++ b/docs/language/examples/formatter/string-print.slo @@ -0,0 +1,6 @@ +(module main) + +(fn main () -> i32 + (print_string "hello") + (print_string "line\nquote\"slash\\tab\t") + 0) diff --git a/docs/language/examples/formatter/string-value-flow.slo b/docs/language/examples/formatter/string-value-flow.slo new file mode 100644 index 0000000..8780425 --- /dev/null +++ b/docs/language/examples/formatter/string-value-flow.slo @@ -0,0 +1,30 @@ +(module main) + +(fn label () -> string + "slovo") + +(fn echo ((value string)) -> string + value) + +(fn local_label () -> string + (let value string (label)) + value) + +(fn label_len () -> i32 + (string_len (local_label))) + +(test "string literal equality" + (= "slovo" "slovo")) + +(test "string parameter equality" + (= (echo "runtime") "runtime")) + +(test "string call return equality" + (= (local_label) "slovo")) + +(test "string byte length" + (= (label_len) 5)) + +(fn main () -> i32 + (print_string (local_label)) + (label_len)) diff --git a/docs/language/examples/formatter/struct-value-flow.slo b/docs/language/examples/formatter/struct-value-flow.slo new file mode 100644 index 0000000..6b73912 --- /dev/null +++ b/docs/language/examples/formatter/struct-value-flow.slo @@ -0,0 +1,31 @@ +(module main) + +(struct Point + (x i32) + (y i32)) + +(fn make_point ((x i32) (y i32)) -> Point + (Point (x x) (y y))) + +(fn point_x ((p Point)) -> i32 + (. p x)) + +(fn point_sum ((p Point)) -> i32 + (+ (. p x) (. p y))) + +(fn local_point_sum () -> i32 + (let p Point (make_point 20 22)) + (point_sum p)) + +(test "struct local value flow" + (= (local_point_sum) 42)) + +(test "struct parameter value flow" + (= (point_x (make_point 7 9)) 7)) + +(test "stored struct field access" + (let p Point (make_point 3 4)) + (= (. p y) 4)) + +(fn main () -> i32 + (local_point_sum)) diff --git a/docs/language/examples/formatter/struct.slo b/docs/language/examples/formatter/struct.slo new file mode 100644 index 0000000..1cecdcb --- /dev/null +++ b/docs/language/examples/formatter/struct.slo @@ -0,0 +1,17 @@ +(module main) + +(struct Point + (x i32) + (y i32)) + +(fn point_sum () -> i32 + (+ (. (Point (x 20) (y 22)) x) (. (Point (x 20) (y 22)) y))) + +(test "struct field access" + (= (point_sum) 42)) + +(test "struct field compares" + (= (. (Point (x 7) (y 9)) y) 9)) + +(fn main () -> i32 + (point_sum)) diff --git a/docs/language/examples/formatter/time-sleep.slo b/docs/language/examples/formatter/time-sleep.slo new file mode 100644 index 0000000..f01577c --- /dev/null +++ b/docs/language/examples/formatter/time-sleep.slo @@ -0,0 +1,21 @@ +(module main) + +(fn monotonic_self_equal () -> bool + (let now i32 (std.time.monotonic_ms)) + (= now now)) + +(fn sleep_zero_then_self_equal () -> bool + (std.time.sleep_ms 0) + (monotonic_self_equal)) + +(test "monotonic value is self equal" + (monotonic_self_equal)) + +(test "sleep zero returns" + (sleep_zero_then_self_equal)) + +(fn main () -> i32 + (std.time.sleep_ms 0) + (if (monotonic_self_equal) + 0 + 1)) diff --git a/docs/language/examples/formatter/top-level-test.slo b/docs/language/examples/formatter/top-level-test.slo new file mode 100644 index 0000000..9004f81 --- /dev/null +++ b/docs/language/examples/formatter/top-level-test.slo @@ -0,0 +1,13 @@ +; status: formatter-canonical +; Scope: promoted top-level test formatter contract. + +(module tests) + +(fn add ((a i32) (b i32)) -> i32 + (+ a b)) + +(test "add works" + (= (add 2 3) 5)) + +(fn main () -> i32 + 0) diff --git a/docs/language/examples/formatter/u32-numeric-primitive.slo b/docs/language/examples/formatter/u32-numeric-primitive.slo new file mode 100644 index 0000000..672e636 --- /dev/null +++ b/docs/language/examples/formatter/u32-numeric-primitive.slo @@ -0,0 +1,39 @@ +(module main) + +(fn base () -> u32 + 1073741824u32) + +(fn adjust ((value u32) (delta u32)) -> u32 + (+ value delta)) + +(fn doubled ((value u32)) -> u32 + (* value 2u32)) + +(fn local_total () -> u32 + (let offset u32 15u32) + (adjust (doubled (base)) offset)) + +(fn high_enough ((value u32)) -> bool + (if (> value 2147483660u32) + (< value 2147483670u32) + false)) + +(fn exact_u32 () -> bool + (= (local_total) 2147483663u32)) + +(fn main () -> i32 + (std.io.print_u32 (local_total)) + (if (high_enough (local_total)) + 0 + 1)) + +(test "u32 arithmetic returns exact fixture value" + (exact_u32)) + +(test "u32 comparison works in predicates" + (high_enough (local_total))) + +(test "u32 division and ordering" + (if (>= (/ (local_total) 3u32) 715827887u32) + (<= (/ (local_total) 3u32) 715827887u32) + false)) diff --git a/docs/language/examples/formatter/u64-numeric-primitive.slo b/docs/language/examples/formatter/u64-numeric-primitive.slo new file mode 100644 index 0000000..6d3441d --- /dev/null +++ b/docs/language/examples/formatter/u64-numeric-primitive.slo @@ -0,0 +1,39 @@ +(module main) + +(fn base () -> u64 + 4294967296u64) + +(fn adjust ((value u64) (delta u64)) -> u64 + (+ value delta)) + +(fn doubled ((value u64)) -> u64 + (* value 2u64)) + +(fn local_total () -> u64 + (let offset u64 19u64) + (adjust (/ (doubled (base)) 2u64) offset)) + +(fn high_enough ((value u64)) -> bool + (if (> value 4294967300u64) + (< value 4294967320u64) + false)) + +(fn exact_u64 () -> bool + (= (local_total) 4294967315u64)) + +(fn main () -> i32 + (std.io.print_u64 (local_total)) + (if (high_enough (local_total)) + 0 + 1)) + +(test "u64 arithmetic returns exact fixture value" + (exact_u64)) + +(test "u64 comparison works in predicates" + (high_enough (local_total))) + +(test "u64 division and ordering" + (if (>= (/ (local_total) 5u64) 858993463u64) + (<= (/ (local_total) 5u64) 858993463u64) + false)) diff --git a/docs/language/examples/formatter/unsafe.slo b/docs/language/examples/formatter/unsafe.slo new file mode 100644 index 0000000..ab8651c --- /dev/null +++ b/docs/language/examples/formatter/unsafe.slo @@ -0,0 +1,16 @@ +(module main) + +(fn add_one_in_unsafe ((value i32)) -> i32 + (unsafe + (let one i32 1) + (+ value one))) + +(test "unsafe block returns final value" + (= (add_one_in_unsafe 4) 5)) + +(test "unsafe block can return bool" + (unsafe + (= (add_one_in_unsafe 1) 2))) + +(fn main () -> i32 + (add_one_in_unsafe 41)) diff --git a/docs/language/examples/formatter/unsigned-integer-to-string.slo b/docs/language/examples/formatter/unsigned-integer-to-string.slo new file mode 100644 index 0000000..1ef0efe --- /dev/null +++ b/docs/language/examples/formatter/unsigned-integer-to-string.slo @@ -0,0 +1,47 @@ +(module main) + +(fn u32_zero_text () -> string + (std.num.u32_to_string 0u32)) + +(fn u32_high_text () -> string + (std.num.u32_to_string 4294967295u32)) + +(fn u64_zero_text () -> string + (std.num.u64_to_string 0u64)) + +(fn u64_high_text () -> string + (std.num.u64_to_string 18446744073709551615u64)) + +(fn u64_beyond_u32_text () -> string + (std.num.u64_to_string 4294967296u64)) + +(test "u32 zero to string" + (= (u32_zero_text) "0")) + +(test "u32 high to string" + (= (u32_high_text) "4294967295")) + +(test "u32 high string length" + (= (std.string.len (u32_high_text)) 10)) + +(test "u64 zero to string" + (= (u64_zero_text) "0")) + +(test "u64 high to string" + (= (u64_high_text) "18446744073709551615")) + +(test "u64 beyond u32 to string" + (= (u64_beyond_u32_text) "4294967296")) + +(test "u64 high string length" + (= (std.string.len (u64_high_text)) 20)) + +(fn main () -> i32 + (std.io.print_string (u32_zero_text)) + (std.io.print_string (u32_high_text)) + (std.io.print_string (u64_zero_text)) + (std.io.print_string (u64_high_text)) + (std.io.print_string (u64_beyond_u32_text)) + (if (= (std.string.len (u64_beyond_u32_text)) 10) + 0 + 1)) diff --git a/docs/language/examples/formatter/vec-bool.slo b/docs/language/examples/formatter/vec-bool.slo new file mode 100644 index 0000000..4d327b3 --- /dev/null +++ b/docs/language/examples/formatter/vec-bool.slo @@ -0,0 +1,45 @@ +(module main) + +(fn empty_values () -> (vec bool) + (std.vec.bool.empty)) + +(fn pair () -> (vec bool) + (let values (vec bool) (std.vec.bool.empty)) + (let first (vec bool) (std.vec.bool.append values true)) + (std.vec.bool.append first false)) + +(fn echo ((values (vec bool))) -> (vec bool) + values) + +(fn length ((values (vec bool))) -> i32 + (std.vec.bool.len values)) + +(fn at ((values (vec bool)) (i i32)) -> bool + (std.vec.bool.index values i)) + +(fn call_return_value () -> bool + (at (echo (pair)) 1)) + +(fn original_len_after_append () -> i32 + (let values (vec bool) (std.vec.bool.empty)) + (let appended (vec bool) (std.vec.bool.append values true)) + (std.vec.bool.len values)) + +(test "vec bool empty length" + (= (std.vec.bool.len (empty_values)) 0)) + +(test "vec bool append length" + (= (length (pair)) 2)) + +(test "vec bool index" + (= (at (pair) 1) false)) + +(test "vec bool append is immutable" + (= (original_len_after_append) 0)) + +(test "vec bool equality" + (= (pair) (std.vec.bool.append (std.vec.bool.append (std.vec.bool.empty) true) false))) + +(fn main () -> i32 + (std.io.print_bool (call_return_value)) + 0) diff --git a/docs/language/examples/formatter/vec-f64.slo b/docs/language/examples/formatter/vec-f64.slo new file mode 100644 index 0000000..d5e6d62 --- /dev/null +++ b/docs/language/examples/formatter/vec-f64.slo @@ -0,0 +1,45 @@ +(module main) + +(fn empty_values () -> (vec f64) + (std.vec.f64.empty)) + +(fn pair ((base f64)) -> (vec f64) + (let values (vec f64) (std.vec.f64.empty)) + (let first (vec f64) (std.vec.f64.append values base)) + (std.vec.f64.append first (+ base 1.0))) + +(fn echo ((values (vec f64))) -> (vec f64) + values) + +(fn length ((values (vec f64))) -> i32 + (std.vec.f64.len values)) + +(fn at ((values (vec f64)) (i i32)) -> f64 + (std.vec.f64.index values i)) + +(fn call_return_value () -> f64 + (at (echo (pair 2147483648.0)) 1)) + +(fn original_len_after_append () -> i32 + (let values (vec f64) (std.vec.f64.empty)) + (let appended (vec f64) (std.vec.f64.append values 1.0)) + (std.vec.f64.len values)) + +(test "vec f64 empty length" + (= (std.vec.f64.len (empty_values)) 0)) + +(test "vec f64 append length" + (= (length (pair 40.0)) 2)) + +(test "vec f64 index" + (= (at (pair 40.0) 1) 41.0)) + +(test "vec f64 append is immutable" + (= (original_len_after_append) 0)) + +(test "vec f64 equality" + (= (pair 5.0) (std.vec.f64.append (std.vec.f64.append (std.vec.f64.empty) 5.0) 6.0))) + +(fn main () -> i32 + (std.io.print_f64 (call_return_value)) + 0) diff --git a/docs/language/examples/formatter/vec-i32.slo b/docs/language/examples/formatter/vec-i32.slo new file mode 100644 index 0000000..ce3cda0 --- /dev/null +++ b/docs/language/examples/formatter/vec-i32.slo @@ -0,0 +1,45 @@ +(module main) + +(fn empty_values () -> (vec i32) + (std.vec.i32.empty)) + +(fn pair ((base i32)) -> (vec i32) + (let values (vec i32) (std.vec.i32.empty)) + (let first (vec i32) (std.vec.i32.append values base)) + (std.vec.i32.append first (+ base 1))) + +(fn echo ((values (vec i32))) -> (vec i32) + values) + +(fn length ((values (vec i32))) -> i32 + (std.vec.i32.len values)) + +(fn at ((values (vec i32)) (i i32)) -> i32 + (std.vec.i32.index values i)) + +(fn call_return_len () -> i32 + (std.vec.i32.len (echo (pair 20)))) + +(fn original_len_after_append () -> i32 + (let values (vec i32) (std.vec.i32.empty)) + (let appended (vec i32) (std.vec.i32.append values 1)) + (std.vec.i32.len values)) + +(test "vec i32 empty length" + (= (std.vec.i32.len (empty_values)) 0)) + +(test "vec i32 append length" + (= (length (pair 40)) 2)) + +(test "vec i32 index" + (= (at (pair 40) 1) 41)) + +(test "vec i32 append is immutable" + (= (original_len_after_append) 0)) + +(test "vec i32 equality" + (= (pair 5) (std.vec.i32.append (std.vec.i32.append (std.vec.i32.empty) 5) 6))) + +(fn main () -> i32 + (std.io.print_i32 (call_return_len)) + (at (pair 40) 1)) diff --git a/docs/language/examples/formatter/vec-i64.slo b/docs/language/examples/formatter/vec-i64.slo new file mode 100644 index 0000000..dba0e81 --- /dev/null +++ b/docs/language/examples/formatter/vec-i64.slo @@ -0,0 +1,45 @@ +(module main) + +(fn empty_values () -> (vec i64) + (std.vec.i64.empty)) + +(fn pair ((base i64)) -> (vec i64) + (let values (vec i64) (std.vec.i64.empty)) + (let first (vec i64) (std.vec.i64.append values base)) + (std.vec.i64.append first (+ base 1i64))) + +(fn echo ((values (vec i64))) -> (vec i64) + values) + +(fn length ((values (vec i64))) -> i32 + (std.vec.i64.len values)) + +(fn at ((values (vec i64)) (i i32)) -> i64 + (std.vec.i64.index values i)) + +(fn call_return_value () -> i64 + (at (echo (pair 2147483648i64)) 1)) + +(fn original_len_after_append () -> i32 + (let values (vec i64) (std.vec.i64.empty)) + (let appended (vec i64) (std.vec.i64.append values 1i64)) + (std.vec.i64.len values)) + +(test "vec i64 empty length" + (= (std.vec.i64.len (empty_values)) 0)) + +(test "vec i64 append length" + (= (length (pair 40i64)) 2)) + +(test "vec i64 index" + (= (at (pair 40i64) 1) 41i64)) + +(test "vec i64 append is immutable" + (= (original_len_after_append) 0)) + +(test "vec i64 equality" + (= (pair 5i64) (std.vec.i64.append (std.vec.i64.append (std.vec.i64.empty) 5i64) 6i64))) + +(fn main () -> i32 + (std.io.print_i64 (call_return_value)) + 0) diff --git a/docs/language/examples/formatter/vec-string.slo b/docs/language/examples/formatter/vec-string.slo new file mode 100644 index 0000000..f7fcf42 --- /dev/null +++ b/docs/language/examples/formatter/vec-string.slo @@ -0,0 +1,45 @@ +(module main) + +(fn empty_values () -> (vec string) + (std.vec.string.empty)) + +(fn pair ((first string) (second string)) -> (vec string) + (let values (vec string) (std.vec.string.empty)) + (let first_values (vec string) (std.vec.string.append values first)) + (std.vec.string.append first_values second)) + +(fn echo ((values (vec string))) -> (vec string) + values) + +(fn length ((values (vec string))) -> i32 + (std.vec.string.len values)) + +(fn at ((values (vec string)) (i i32)) -> string + (std.vec.string.index values i)) + +(fn call_return_value () -> string + (at (echo (pair "slovo" "tree")) 1)) + +(fn original_len_after_append () -> i32 + (let values (vec string) (std.vec.string.empty)) + (let appended (vec string) (std.vec.string.append values "branch")) + (std.vec.string.len values)) + +(test "vec string empty length" + (= (std.vec.string.len (empty_values)) 0)) + +(test "vec string append length" + (= (length (pair "alpha" "beta")) 2)) + +(test "vec string index" + (= (at (pair "slovo" "tree") 1) "tree")) + +(test "vec string append is immutable" + (= (original_len_after_append) 0)) + +(test "vec string equality" + (= (pair "leaf" "root") (std.vec.string.append (std.vec.string.append (std.vec.string.empty) "leaf") "root"))) + +(fn main () -> i32 + (std.io.print_string (call_return_value)) + 0) diff --git a/docs/language/examples/formatter/while.slo b/docs/language/examples/formatter/while.slo new file mode 100644 index 0000000..6c60d5b --- /dev/null +++ b/docs/language/examples/formatter/while.slo @@ -0,0 +1,22 @@ +(module main) + +(fn count_to ((limit i32)) -> i32 + (var i i32 0) + (while (< i limit) + (set i (+ i 1))) + i) + +(test "while counts" + (var i i32 0) + (while (< i 3) + (set i (+ i 1))) + (= i 3)) + +(test "while false skips" + (var i i32 0) + (while false + (set i (+ i 1))) + (= i 0)) + +(fn main () -> i32 + (count_to 4)) diff --git a/docs/language/examples/projects/basic/slovo.toml b/docs/language/examples/projects/basic/slovo.toml new file mode 100644 index 0000000..9b5fe3c --- /dev/null +++ b/docs/language/examples/projects/basic/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "basic" +source_root = "src" +entry = "main" diff --git a/docs/language/examples/projects/basic/src/main.slo b/docs/language/examples/projects/basic/src/main.slo new file mode 100644 index 0000000..836d927 --- /dev/null +++ b/docs/language/examples/projects/basic/src/main.slo @@ -0,0 +1,10 @@ +(module main) + +(import math (add_one)) + +(fn main () -> i32 + (print_i32 (add_one 41)) + 0) + +(test "imported add one" + (= (add_one 41) 42)) diff --git a/docs/language/examples/projects/basic/src/math.slo b/docs/language/examples/projects/basic/src/math.slo new file mode 100644 index 0000000..1693dd7 --- /dev/null +++ b/docs/language/examples/projects/basic/src/math.slo @@ -0,0 +1,7 @@ +(module math (export add_one)) + +(fn add_one ((value i32)) -> i32 + (+ value 1)) + +(test "add one" + (= (add_one 41) 42)) diff --git a/docs/language/examples/projects/enum-imports/slovo.toml b/docs/language/examples/projects/enum-imports/slovo.toml new file mode 100644 index 0000000..38ceca9 --- /dev/null +++ b/docs/language/examples/projects/enum-imports/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "enum-imports" +source_root = "src" +entry = "main" diff --git a/docs/language/examples/projects/enum-imports/src/main.slo b/docs/language/examples/projects/enum-imports/src/main.slo new file mode 100644 index 0000000..24ff863 --- /dev/null +++ b/docs/language/examples/projects/enum-imports/src/main.slo @@ -0,0 +1,60 @@ +(module main) + +(import readings (Reading)) + +(fn missing () -> Reading + (Reading.Missing)) + +(fn value ((payload i32)) -> Reading + (Reading.Value payload)) + +(fn echo ((reading Reading)) -> Reading + reading) + +(fn local_reading ((payload i32)) -> Reading + (let reading Reading (Reading.Value payload)) + reading) + +(fn same_reading ((left Reading) (right Reading)) -> bool + (= left right)) + +(fn reading_code ((reading Reading)) -> i32 + (match reading + ((Reading.Missing) + 0) + ((Reading.Value payload) + payload) + ((Reading.Offset payload) + (+ payload 100)))) + +(fn call_flow ((payload i32)) -> Reading + (echo (local_reading payload))) + +(test "imported enum payload constructor equality" + (= (Reading.Value 7) (value 7))) + +(test "imported enum payload equality compares payload" + (if (= (Reading.Value 7) (Reading.Value 8)) + false + true)) + +(test "imported enum payloadless equality" + (= (Reading.Missing) (missing))) + +(test "imported enum local return call flow" + (= (call_flow 9) (Reading.Value 9))) + +(test "imported enum parameter equality" + (same_reading (value 11) (Reading.Value 11))) + +(test "imported enum match missing" + (= (reading_code (Reading.Missing)) 0)) + +(test "imported enum match value" + (= (reading_code (Reading.Value 12)) 12)) + +(test "imported enum match offset" + (= (reading_code (Reading.Offset 5)) 105)) + +(fn main () -> i32 + (reading_code (call_flow 42))) diff --git a/docs/language/examples/projects/enum-imports/src/readings.slo b/docs/language/examples/projects/enum-imports/src/readings.slo new file mode 100644 index 0000000..50b9ae9 --- /dev/null +++ b/docs/language/examples/projects/enum-imports/src/readings.slo @@ -0,0 +1,9 @@ +(module readings (export Reading)) + +(enum Reading + Missing + (Value i32) + (Offset i32)) + +(test "local exported enum constructor" + (= (Reading.Missing) (Reading.Missing))) diff --git a/docs/language/examples/projects/std-import-cli/slovo.toml b/docs/language/examples/projects/std-import-cli/slovo.toml new file mode 100644 index 0000000..30f83aa --- /dev/null +++ b/docs/language/examples/projects/std-import-cli/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-import-cli" +source_root = "src" +entry = "main" diff --git a/docs/language/examples/projects/std-import-cli/src/main.slo b/docs/language/examples/projects/std-import-cli/src/main.slo new file mode 100644 index 0000000..db2bb31 --- /dev/null +++ b/docs/language/examples/projects/std-import-cli/src/main.slo @@ -0,0 +1,278 @@ +(module main) + +(import std.cli (arg_text_result arg_text_option arg_i32_result arg_i32_option arg_i32_or_zero arg_i32_or arg_u32_result arg_u32_option arg_u32_or_zero arg_u32_or arg_i64_result arg_i64_option arg_i64_or_zero arg_i64_or arg_u64_result arg_u64_option arg_u64_or_zero arg_u64_or arg_f64_result arg_f64_option arg_f64_or_zero arg_f64_or arg_bool_result arg_bool_option arg_bool_or_false arg_bool_or)) + +(fn impossible_index () -> i32 + 99999) + +(fn first_index () -> i32 + 0) + +(fn imported_cli_arg_text_missing () -> bool + (match (arg_text_result (impossible_index)) + ((ok text) + false) + ((err code) + (= code 1)))) + +(fn imported_cli_arg_text_option_ok () -> bool + (if (match (arg_text_option (first_index)) + ((some text) + true) + ((none) + false)) + (match (arg_text_option (impossible_index)) + ((some text) + false) + ((none) + true)) + false)) + +(fn imported_cli_arg_i32_missing () -> bool + (match (arg_i32_result (impossible_index)) + ((ok value) + false) + ((err code) + (= code 1)))) + +(fn imported_cli_arg_i32_or_zero_missing () -> bool + (= (arg_i32_or_zero (impossible_index)) 0)) + +(fn imported_cli_arg_u32_missing () -> bool + (match (arg_u32_result (impossible_index)) + ((ok value) + false) + ((err code) + (= code 1)))) + +(fn imported_cli_arg_u32_or_zero_missing () -> bool + (= (arg_u32_or_zero (impossible_index)) 0u32)) + +(fn imported_cli_arg_i64_missing () -> bool + (match (arg_i64_result (impossible_index)) + ((ok value) + false) + ((err code) + (= code 1)))) + +(fn imported_cli_arg_i64_or_zero_missing () -> bool + (= (arg_i64_or_zero (impossible_index)) 0i64)) + +(fn imported_cli_arg_u64_missing () -> bool + (match (arg_u64_result (impossible_index)) + ((ok value) + false) + ((err code) + (= code 1)))) + +(fn imported_cli_arg_u64_or_zero_missing () -> bool + (= (arg_u64_or_zero (impossible_index)) 0u64)) + +(fn imported_cli_arg_f64_missing () -> bool + (match (arg_f64_result (impossible_index)) + ((ok value) + false) + ((err code) + (= code 1)))) + +(fn imported_cli_arg_f64_or_zero_missing () -> bool + (= (arg_f64_or_zero (impossible_index)) 0.0)) + +(fn imported_cli_arg_bool_missing () -> bool + (match (arg_bool_result (impossible_index)) + ((ok value) + false) + ((err code) + (= code 1)))) + +(fn imported_cli_arg_bool_or_false_missing () -> bool + (if (arg_bool_or_false (impossible_index)) + false + true)) + +(fn imported_cli_typed_options_ok () -> bool + (if (match (arg_i32_option (impossible_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_i32_option (first_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_u32_option (impossible_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_u32_option (first_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_i64_option (impossible_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_i64_option (first_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_u64_option (impossible_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_u64_option (first_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_f64_option (impossible_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_f64_option (first_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_bool_option (impossible_index)) + ((some value) + false) + ((none) + true)) + (match (arg_bool_option (first_index)) + ((some value) + false) + ((none) + true)) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_cli_typed_custom_fallbacks_ok () -> bool + (if (= (arg_i32_or (impossible_index) 7) 7) + (if (= (arg_i32_or (first_index) 7) 7) + (if (= (arg_u32_or (impossible_index) 7u32) 7u32) + (if (= (arg_u32_or (first_index) 7u32) 7u32) + (if (= (arg_i64_or (impossible_index) 9i64) 9i64) + (if (= (arg_i64_or (first_index) 9i64) 9i64) + (if (= (arg_u64_or (impossible_index) 9u64) 9u64) + (if (= (arg_u64_or (first_index) 9u64) 9u64) + (if (= (arg_f64_or (impossible_index) 1.5) 1.5) + (if (= (arg_f64_or (first_index) 1.5) 1.5) + (if (arg_bool_or (impossible_index) true) + (arg_bool_or (first_index) true) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_cli_facade_ok () -> bool + (if (imported_cli_arg_text_missing) + (if (imported_cli_arg_text_option_ok) + (if (imported_cli_arg_i32_missing) + (if (imported_cli_arg_i32_or_zero_missing) + (if (imported_cli_arg_u32_missing) + (if (imported_cli_arg_u32_or_zero_missing) + (if (imported_cli_arg_i64_missing) + (if (imported_cli_arg_i64_or_zero_missing) + (if (imported_cli_arg_u64_missing) + (if (imported_cli_arg_u64_or_zero_missing) + (if (imported_cli_arg_f64_missing) + (if (imported_cli_arg_f64_or_zero_missing) + (if (imported_cli_arg_bool_missing) + (if (imported_cli_arg_bool_or_false_missing) + (if (imported_cli_typed_options_ok) + (imported_cli_typed_custom_fallbacks_ok) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn main () -> i32 + (if (imported_cli_facade_ok) + 42 + 1)) + +(test "explicit std cli arg text missing" + (imported_cli_arg_text_missing)) + +(test "explicit std cli arg text option" + (imported_cli_arg_text_option_ok)) + +(test "explicit std cli arg i32 missing" + (imported_cli_arg_i32_missing)) + +(test "explicit std cli arg i32 fallback missing" + (imported_cli_arg_i32_or_zero_missing)) + +(test "explicit std cli arg u32 missing" + (imported_cli_arg_u32_missing)) + +(test "explicit std cli arg u32 fallback missing" + (imported_cli_arg_u32_or_zero_missing)) + +(test "explicit std cli arg i64 missing" + (imported_cli_arg_i64_missing)) + +(test "explicit std cli arg i64 fallback missing" + (imported_cli_arg_i64_or_zero_missing)) + +(test "explicit std cli arg u64 missing" + (imported_cli_arg_u64_missing)) + +(test "explicit std cli arg u64 fallback missing" + (imported_cli_arg_u64_or_zero_missing)) + +(test "explicit std cli arg f64 missing" + (imported_cli_arg_f64_missing)) + +(test "explicit std cli arg f64 fallback missing" + (imported_cli_arg_f64_or_zero_missing)) + +(test "explicit std cli arg bool missing" + (imported_cli_arg_bool_missing)) + +(test "explicit std cli arg bool fallback missing" + (imported_cli_arg_bool_or_false_missing)) + +(test "explicit std cli typed option" + (imported_cli_typed_options_ok)) + +(test "explicit std cli typed custom fallback" + (imported_cli_typed_custom_fallbacks_ok)) + +(test "explicit std cli facade all" + (= (main) 42)) diff --git a/docs/language/examples/projects/std-import-env/slovo.toml b/docs/language/examples/projects/std-import-env/slovo.toml new file mode 100644 index 0000000..8072f45 --- /dev/null +++ b/docs/language/examples/projects/std-import-env/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-import-env" +source_root = "src" +entry = "main" diff --git a/docs/language/examples/projects/std-import-env/src/main.slo b/docs/language/examples/projects/std-import-env/src/main.slo new file mode 100644 index 0000000..1da6918 --- /dev/null +++ b/docs/language/examples/projects/std-import-env/src/main.slo @@ -0,0 +1,364 @@ +(module main) + +(import std.env (get get_result get_option has get_or get_i32_result get_i32_option get_i32_or_zero get_i32_or get_u32_result get_u32_option get_u32_or_zero get_u32_or get_i64_result get_i64_option get_i64_or_zero get_i64_or get_u64_result get_u64_option get_u64_or_zero get_u64_or get_f64_result get_f64_option get_f64_or_zero get_f64_or get_bool_result get_bool_option get_bool_or_false get_bool_or)) + +(fn missing_env_name () -> string + "GLAGOL_STD_IMPORT_ENV_ALPHA_UNLIKELY_MISSING") + +(fn present_env_name () -> string + "GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT") + +(fn present_env_value () -> string + "glagol-std-import-env-alpha-value") + +(fn present_i32_env_name () -> string + "GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT_I32") + +(fn present_i64_env_name () -> string + "GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT_I64") + +(fn present_u32_env_name () -> string + "GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT_U32") + +(fn present_u64_env_name () -> string + "GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT_U64") + +(fn present_f64_env_name () -> string + "GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT_F64") + +(fn present_bool_env_name () -> string + "GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT_BOOL") + +(fn invalid_env_name () -> string + "GLAGOL_STD_IMPORT_ENV_ALPHA_INVALID") + +(fn imported_env_missing_get_empty () -> bool + (= (get (missing_env_name)) "")) + +(fn imported_env_missing_result_err_one () -> bool + (match (get_result (missing_env_name)) + ((ok payload) + false) + ((err code) + (= code 1)))) + +(fn imported_env_has_missing_false () -> bool + (if (has (missing_env_name)) + false + true)) + +(fn imported_env_get_or_missing_fallback () -> bool + (= (get_or (missing_env_name) "fallback") "fallback")) + +(fn imported_env_get_option_missing_none () -> bool + (match (get_option (missing_env_name)) + ((some payload) + false) + ((none) + true))) + +(fn imported_env_has_present_true () -> bool + (has (present_env_name))) + +(fn imported_env_get_or_present_value () -> bool + (if (= (get_or (present_env_name) "fallback") (present_env_value)) + (= (get (present_env_name)) (present_env_value)) + false)) + +(fn imported_env_get_option_present_some () -> bool + (match (get_option (present_env_name)) + ((some payload) + (= payload (present_env_value))) + ((none) + false))) + +(fn imported_env_get_i32_result_present () -> bool + (match (get_i32_result (present_i32_env_name)) + ((ok value) + (= value 42)) + ((err code) + false))) + +(fn imported_env_get_i32_or_zero_invalid () -> bool + (= (get_i32_or_zero (invalid_env_name)) 0)) + +(fn imported_env_get_u32_result_present () -> bool + (match (get_u32_result (present_u32_env_name)) + ((ok value) + (= value 42u32)) + ((err code) + false))) + +(fn imported_env_get_u32_or_zero_invalid () -> bool + (= (get_u32_or_zero (invalid_env_name)) 0u32)) + +(fn imported_env_get_i64_result_present () -> bool + (match (get_i64_result (present_i64_env_name)) + ((ok value) + (= value 42000000000i64)) + ((err code) + false))) + +(fn imported_env_get_i64_or_zero_missing () -> bool + (= (get_i64_or_zero (missing_env_name)) 0i64)) + +(fn imported_env_get_u64_result_present () -> bool + (match (get_u64_result (present_u64_env_name)) + ((ok value) + (= value 4294967296u64)) + ((err code) + false))) + +(fn imported_env_get_u64_or_zero_missing () -> bool + (= (get_u64_or_zero (missing_env_name)) 0u64)) + +(fn imported_env_get_f64_result_present () -> bool + (match (get_f64_result (present_f64_env_name)) + ((ok value) + (= value 42.5)) + ((err code) + false))) + +(fn imported_env_get_f64_or_zero_invalid () -> bool + (= (get_f64_or_zero (invalid_env_name)) 0.0)) + +(fn imported_env_get_bool_result_present () -> bool + (match (get_bool_result (present_bool_env_name)) + ((ok value) + value) + ((err code) + false))) + +(fn imported_env_get_bool_or_false_invalid () -> bool + (if (get_bool_or_false (invalid_env_name)) + false + true)) + +(fn imported_env_typed_options_ok () -> bool + (let i32_present bool (match (get_i32_option (present_i32_env_name)) + ((some payload) + (= payload 42)) + ((none) + false))) + (let i32_invalid bool (match (get_i32_option (invalid_env_name)) + ((some payload) + false) + ((none) + true))) + (let u32_present bool (match (get_u32_option (present_u32_env_name)) + ((some payload) + (= payload 42u32)) + ((none) + false))) + (let u32_invalid bool (match (get_u32_option (invalid_env_name)) + ((some payload) + false) + ((none) + true))) + (let i64_present bool (match (get_i64_option (present_i64_env_name)) + ((some payload) + (= payload 42000000000i64)) + ((none) + false))) + (let i64_missing bool (match (get_i64_option (missing_env_name)) + ((some payload) + false) + ((none) + true))) + (let u64_present bool (match (get_u64_option (present_u64_env_name)) + ((some payload) + (= payload 4294967296u64)) + ((none) + false))) + (let u64_missing bool (match (get_u64_option (missing_env_name)) + ((some payload) + false) + ((none) + true))) + (let f64_present bool (match (get_f64_option (present_f64_env_name)) + ((some payload) + (= payload 42.5)) + ((none) + false))) + (let f64_invalid bool (match (get_f64_option (invalid_env_name)) + ((some payload) + false) + ((none) + true))) + (let bool_present bool (match (get_bool_option (present_bool_env_name)) + ((some payload) + payload) + ((none) + false))) + (let bool_invalid bool (match (get_bool_option (invalid_env_name)) + ((some payload) + false) + ((none) + true))) + (if i32_present + (if i32_invalid + (if u32_present + (if u32_invalid + (if i64_present + (if i64_missing + (if u64_present + (if u64_missing + (if f64_present + (if f64_invalid + (if bool_present + bool_invalid + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_env_typed_custom_fallbacks_ok () -> bool + (if (= (get_i32_or (present_i32_env_name) 7) 42) + (if (= (get_i32_or (invalid_env_name) 7) 7) + (if (= (get_u32_or (present_u32_env_name) 7u32) 42u32) + (if (= (get_u32_or (invalid_env_name) 7u32) 7u32) + (if (= (get_i64_or (present_i64_env_name) 9i64) 42000000000i64) + (if (= (get_i64_or (invalid_env_name) 9i64) 9i64) + (if (= (get_u64_or (present_u64_env_name) 9u64) 4294967296u64) + (if (= (get_u64_or (invalid_env_name) 9u64) 9u64) + (if (= (get_f64_or (present_f64_env_name) 1.5) 42.5) + (if (= (get_f64_or (invalid_env_name) 1.5) 1.5) + (if (get_bool_or (present_bool_env_name) false) + (get_bool_or (invalid_env_name) true) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_env_facade_ok () -> bool + (if (imported_env_missing_get_empty) + (if (imported_env_missing_result_err_one) + (if (imported_env_has_missing_false) + (if (imported_env_get_or_missing_fallback) + (if (imported_env_get_option_missing_none) + (if (imported_env_has_present_true) + (if (imported_env_get_or_present_value) + (if (imported_env_get_option_present_some) + (if (imported_env_get_i32_result_present) + (if (imported_env_get_i32_or_zero_invalid) + (if (imported_env_get_u32_result_present) + (if (imported_env_get_u32_or_zero_invalid) + (if (imported_env_get_i64_result_present) + (if (imported_env_get_i64_or_zero_missing) + (if (imported_env_get_u64_result_present) + (if (imported_env_get_u64_or_zero_missing) + (if (imported_env_get_f64_result_present) + (if (imported_env_get_f64_or_zero_invalid) + (if (imported_env_get_bool_result_present) + (if (imported_env_get_bool_or_false_invalid) + (if (imported_env_typed_options_ok) + (imported_env_typed_custom_fallbacks_ok) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn main () -> i32 + (if (imported_env_facade_ok) + 42 + 1)) + +(test "explicit std env get missing facade" + (imported_env_missing_get_empty)) + +(test "explicit std env get result missing facade" + (imported_env_missing_result_err_one)) + +(test "explicit std env has missing facade" + (imported_env_has_missing_false)) + +(test "explicit std env get or missing facade" + (imported_env_get_or_missing_fallback)) + +(test "explicit std env get option missing facade" + (imported_env_get_option_missing_none)) + +(test "explicit std env has present facade" + (imported_env_has_present_true)) + +(test "explicit std env get or present facade" + (imported_env_get_or_present_value)) + +(test "explicit std env get option present facade" + (imported_env_get_option_present_some)) + +(test "explicit std env get i32 result present facade" + (imported_env_get_i32_result_present)) + +(test "explicit std env get i32 or zero invalid facade" + (imported_env_get_i32_or_zero_invalid)) + +(test "explicit std env get u32 result present facade" + (imported_env_get_u32_result_present)) + +(test "explicit std env get u32 or zero invalid facade" + (imported_env_get_u32_or_zero_invalid)) + +(test "explicit std env get i64 result present facade" + (imported_env_get_i64_result_present)) + +(test "explicit std env get i64 or zero missing facade" + (imported_env_get_i64_or_zero_missing)) + +(test "explicit std env get u64 result present facade" + (imported_env_get_u64_result_present)) + +(test "explicit std env get u64 or zero missing facade" + (imported_env_get_u64_or_zero_missing)) + +(test "explicit std env get f64 result present facade" + (imported_env_get_f64_result_present)) + +(test "explicit std env get f64 or zero invalid facade" + (imported_env_get_f64_or_zero_invalid)) + +(test "explicit std env get bool result present facade" + (imported_env_get_bool_result_present)) + +(test "explicit std env get bool or false invalid facade" + (imported_env_get_bool_or_false_invalid)) + +(test "explicit std env typed option facade" + (imported_env_typed_options_ok)) + +(test "explicit std env typed custom fallback facade" + (imported_env_typed_custom_fallbacks_ok)) + +(test "explicit std env facade all" + (= (main) 42)) diff --git a/docs/language/examples/projects/std-import-fs/slovo.toml b/docs/language/examples/projects/std-import-fs/slovo.toml new file mode 100644 index 0000000..feab5dd --- /dev/null +++ b/docs/language/examples/projects/std-import-fs/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-import-fs" +source_root = "src" +entry = "main" diff --git a/docs/language/examples/projects/std-import-fs/src/main.slo b/docs/language/examples/projects/std-import-fs/src/main.slo new file mode 100644 index 0000000..1928b8a --- /dev/null +++ b/docs/language/examples/projects/std-import-fs/src/main.slo @@ -0,0 +1,387 @@ +(module main) + +(import std.fs (read_text read_text_result read_text_option write_text_status write_text_result read_text_or write_text_ok read_i32_result read_i32_option read_i32_or_zero read_i32_or read_u32_result read_u32_option read_u32_or_zero read_u32_or read_i64_result read_i64_option read_i64_or_zero read_i64_or read_u64_result read_u64_option read_u64_or_zero read_u64_or read_f64_result read_f64_option read_f64_or_zero read_f64_or read_bool_result read_bool_option read_bool_or_false read_bool_or)) + +(fn fixture_path () -> string + "glagol-std-import-fs-alpha.txt") + +(fn fixture_text () -> string + "std fs source search alpha") + +(fn missing_fixture_path () -> string + "glagol-std-import-fs-alpha-missing.txt") + +(fn fallback_text () -> string + "std fs source fallback alpha") + +(fn i32_fixture_path () -> string + "glagol-std-import-fs-alpha-i32.txt") + +(fn i64_fixture_path () -> string + "glagol-std-import-fs-alpha-i64.txt") + +(fn u32_fixture_path () -> string + "glagol-std-import-fs-alpha-u32.txt") + +(fn u64_fixture_path () -> string + "glagol-std-import-fs-alpha-u64.txt") + +(fn f64_fixture_path () -> string + "glagol-std-import-fs-alpha-f64.txt") + +(fn bool_fixture_path () -> string + "glagol-std-import-fs-alpha-bool.txt") + +(fn invalid_fixture_path () -> string + "glagol-std-import-fs-alpha-invalid.txt") + +(fn imported_write_text_status () -> i32 + (write_text_status (fixture_path) (fixture_text))) + +(fn imported_read_text () -> string + (write_text_status (fixture_path) (fixture_text)) + (read_text (fixture_path))) + +(fn imported_write_text_result () -> (result i32 i32) + (write_text_result (fixture_path) (fixture_text))) + +(fn imported_read_text_result () -> (result string i32) + (write_text_status (fixture_path) (fixture_text)) + (read_text_result (fixture_path))) + +(fn imported_read_text_option_missing_none () -> bool + (match (read_text_option (missing_fixture_path)) + ((some text) + false) + ((none) + true))) + +(fn imported_read_text_option_present_some () -> bool + (write_text_status (fixture_path) (fixture_text)) + (match (read_text_option (fixture_path)) + ((some text) + (= text (fixture_text))) + ((none) + false))) + +(fn imported_read_text_or_missing_fallback () -> bool + (= (read_text_or (missing_fixture_path) (fallback_text)) (fallback_text))) + +(fn imported_read_text_or_present_value () -> bool + (write_text_status (fixture_path) (fixture_text)) + (= (read_text_or (fixture_path) (fallback_text)) (fixture_text))) + +(fn imported_write_text_ok () -> bool + (write_text_ok (fixture_path) (fixture_text))) + +(fn imported_read_i32_result_present () -> bool + (write_text_status (i32_fixture_path) "42") + (match (read_i32_result (i32_fixture_path)) + ((ok value) + (= value 42)) + ((err code) + false))) + +(fn imported_read_i32_or_zero_invalid () -> bool + (write_text_status (invalid_fixture_path) "bad") + (= (read_i32_or_zero (invalid_fixture_path)) 0)) + +(fn imported_read_u32_result_present () -> bool + (write_text_status (u32_fixture_path) "42") + (match (read_u32_result (u32_fixture_path)) + ((ok value) + (= value 42u32)) + ((err code) + false))) + +(fn imported_read_u32_or_zero_invalid () -> bool + (write_text_status (invalid_fixture_path) "bad") + (= (read_u32_or_zero (invalid_fixture_path)) 0u32)) + +(fn imported_read_i64_result_present () -> bool + (write_text_status (i64_fixture_path) "42000000000") + (match (read_i64_result (i64_fixture_path)) + ((ok value) + (= value 42000000000i64)) + ((err code) + false))) + +(fn imported_read_i64_or_zero_missing () -> bool + (= (read_i64_or_zero (missing_fixture_path)) 0i64)) + +(fn imported_read_u64_result_present () -> bool + (write_text_status (u64_fixture_path) "4294967296") + (match (read_u64_result (u64_fixture_path)) + ((ok value) + (= value 4294967296u64)) + ((err code) + false))) + +(fn imported_read_u64_or_zero_missing () -> bool + (= (read_u64_or_zero (missing_fixture_path)) 0u64)) + +(fn imported_read_f64_result_present () -> bool + (write_text_status (f64_fixture_path) "42.5") + (match (read_f64_result (f64_fixture_path)) + ((ok value) + (= value 42.5)) + ((err code) + false))) + +(fn imported_read_f64_or_zero_invalid () -> bool + (write_text_status (invalid_fixture_path) "bad") + (= (read_f64_or_zero (invalid_fixture_path)) 0.0)) + +(fn imported_read_bool_result_present () -> bool + (write_text_status (bool_fixture_path) "true") + (match (read_bool_result (bool_fixture_path)) + ((ok value) + value) + ((err code) + false))) + +(fn imported_read_bool_or_false_invalid () -> bool + (write_text_status (invalid_fixture_path) "bad") + (if (read_bool_or_false (invalid_fixture_path)) + false + true)) + +(fn imported_typed_options_ok () -> bool + (write_text_status (i32_fixture_path) "42") + (write_text_status (u32_fixture_path) "42") + (write_text_status (i64_fixture_path) "42000000000") + (write_text_status (u64_fixture_path) "4294967296") + (write_text_status (f64_fixture_path) "42.5") + (write_text_status (bool_fixture_path) "true") + (write_text_status (invalid_fixture_path) "bad") + (if (match (read_i32_option (i32_fixture_path)) + ((some value) + (= value 42)) + ((none) + false)) + (if (match (read_i32_option (invalid_fixture_path)) + ((some value) + false) + ((none) + true)) + (if (match (read_u32_option (u32_fixture_path)) + ((some value) + (= value 42u32)) + ((none) + false)) + (if (match (read_u32_option (invalid_fixture_path)) + ((some value) + false) + ((none) + true)) + (if (match (read_i64_option (i64_fixture_path)) + ((some value) + (= value 42000000000i64)) + ((none) + false)) + (if (match (read_i64_option (missing_fixture_path)) + ((some value) + false) + ((none) + true)) + (if (match (read_u64_option (u64_fixture_path)) + ((some value) + (= value 4294967296u64)) + ((none) + false)) + (if (match (read_u64_option (missing_fixture_path)) + ((some value) + false) + ((none) + true)) + (if (match (read_f64_option (f64_fixture_path)) + ((some value) + (= value 42.5)) + ((none) + false)) + (if (match (read_f64_option (invalid_fixture_path)) + ((some value) + false) + ((none) + true)) + (if (match (read_bool_option (bool_fixture_path)) + ((some value) + value) + ((none) + false)) + (match (read_bool_option (invalid_fixture_path)) + ((some value) + false) + ((none) + true)) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_typed_custom_fallbacks_ok () -> bool + (write_text_status (i32_fixture_path) "42") + (write_text_status (u32_fixture_path) "42") + (write_text_status (i64_fixture_path) "42000000000") + (write_text_status (u64_fixture_path) "4294967296") + (write_text_status (f64_fixture_path) "42.5") + (write_text_status (bool_fixture_path) "true") + (write_text_status (invalid_fixture_path) "bad") + (if (= (read_i32_or (i32_fixture_path) 7) 42) + (if (= (read_i32_or (invalid_fixture_path) 7) 7) + (if (= (read_u32_or (u32_fixture_path) 7u32) 42u32) + (if (= (read_u32_or (invalid_fixture_path) 7u32) 7u32) + (if (= (read_i64_or (i64_fixture_path) 9i64) 42000000000i64) + (if (= (read_i64_or (invalid_fixture_path) 9i64) 9i64) + (if (= (read_u64_or (u64_fixture_path) 9u64) 4294967296u64) + (if (= (read_u64_or (invalid_fixture_path) 9u64) 9u64) + (if (= (read_f64_or (f64_fixture_path) 1.5) 42.5) + (if (= (read_f64_or (invalid_fixture_path) 1.5) 1.5) + (if (read_bool_or (bool_fixture_path) false) + (read_bool_or (invalid_fixture_path) true) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_status_roundtrip_ok () -> bool + (write_text_status (fixture_path) (fixture_text)) + (= (read_text (fixture_path)) (fixture_text))) + +(fn imported_result_roundtrip_ok () -> bool + (write_text_result (fixture_path) (fixture_text)) + (= (unwrap_ok (read_text_result (fixture_path))) (fixture_text))) + +(fn imported_fs_facade_ok () -> bool + (if (imported_status_roundtrip_ok) + (if (imported_result_roundtrip_ok) + (if (imported_read_text_option_missing_none) + (if (imported_read_text_option_present_some) + (if (imported_read_text_or_missing_fallback) + (if (imported_read_text_or_present_value) + (if (imported_write_text_ok) + (if (imported_read_i32_result_present) + (if (imported_read_i32_or_zero_invalid) + (if (imported_read_u32_result_present) + (if (imported_read_u32_or_zero_invalid) + (if (imported_read_i64_result_present) + (if (imported_read_i64_or_zero_missing) + (if (imported_read_u64_result_present) + (if (imported_read_u64_or_zero_missing) + (if (imported_read_f64_result_present) + (if (imported_read_f64_or_zero_invalid) + (if (imported_read_bool_result_present) + (if (imported_read_bool_or_false_invalid) + (if (imported_typed_options_ok) + (imported_typed_custom_fallbacks_ok) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn main () -> i32 + (if (imported_fs_facade_ok) + 42 + 1)) + +(test "explicit std fs write text status facade" + (= (imported_write_text_status) 0)) + +(test "explicit std fs read text facade" + (= (imported_read_text) (fixture_text))) + +(test "explicit std fs write text result facade" + (= (unwrap_ok (imported_write_text_result)) 0)) + +(test "explicit std fs read text result facade" + (= (unwrap_ok (imported_read_text_result)) (fixture_text))) + +(test "explicit std fs read text option missing facade" + (imported_read_text_option_missing_none)) + +(test "explicit std fs read text option present facade" + (imported_read_text_option_present_some)) + +(test "explicit std fs read text or missing facade" + (imported_read_text_or_missing_fallback)) + +(test "explicit std fs read text or present facade" + (imported_read_text_or_present_value)) + +(test "explicit std fs write text ok facade" + (imported_write_text_ok)) + +(test "explicit std fs read i32 result present facade" + (imported_read_i32_result_present)) + +(test "explicit std fs read i32 or zero invalid facade" + (imported_read_i32_or_zero_invalid)) + +(test "explicit std fs read u32 result present facade" + (imported_read_u32_result_present)) + +(test "explicit std fs read u32 or zero invalid facade" + (imported_read_u32_or_zero_invalid)) + +(test "explicit std fs read i64 result present facade" + (imported_read_i64_result_present)) + +(test "explicit std fs read i64 or zero missing facade" + (imported_read_i64_or_zero_missing)) + +(test "explicit std fs read u64 result present facade" + (imported_read_u64_result_present)) + +(test "explicit std fs read u64 or zero missing facade" + (imported_read_u64_or_zero_missing)) + +(test "explicit std fs read f64 result present facade" + (imported_read_f64_result_present)) + +(test "explicit std fs read f64 or zero invalid facade" + (imported_read_f64_or_zero_invalid)) + +(test "explicit std fs read bool result present facade" + (imported_read_bool_result_present)) + +(test "explicit std fs read bool or false invalid facade" + (imported_read_bool_or_false_invalid)) + +(test "explicit std fs typed option facade" + (imported_typed_options_ok)) + +(test "explicit std fs typed custom fallback facade" + (imported_typed_custom_fallbacks_ok)) + +(test "explicit std fs facade all" + (= (main) 42)) diff --git a/docs/language/examples/projects/std-import-io/slovo.toml b/docs/language/examples/projects/std-import-io/slovo.toml new file mode 100644 index 0000000..3b24318 --- /dev/null +++ b/docs/language/examples/projects/std-import-io/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-import-io" +source_root = "src" +entry = "main" diff --git a/docs/language/examples/projects/std-import-io/src/main.slo b/docs/language/examples/projects/std-import-io/src/main.slo new file mode 100644 index 0000000..4279801 --- /dev/null +++ b/docs/language/examples/projects/std-import-io/src/main.slo @@ -0,0 +1,221 @@ +(module main) + +(import std.io (print_i32_zero print_u32_zero print_i64_zero print_u64_zero print_f64_zero print_string_zero print_bool_zero print_i32_value print_u32_value print_i64_value print_u64_value print_f64_value print_string_value print_bool_value read_stdin_result read_stdin_option read_stdin_or read_stdin_i32_result read_stdin_i32_option read_stdin_i32_or_zero read_stdin_i32_or read_stdin_u32_result read_stdin_u32_option read_stdin_u32_or_zero read_stdin_u32_or read_stdin_i64_result read_stdin_i64_option read_stdin_i64_or_zero read_stdin_i64_or read_stdin_u64_result read_stdin_u64_option read_stdin_u64_or_zero read_stdin_u64_or read_stdin_f64_result read_stdin_f64_option read_stdin_f64_or_zero read_stdin_f64_or read_stdin_bool_result read_stdin_bool_option read_stdin_bool_or_false read_stdin_bool_or)) + +(fn imported_print_i32_status () -> i32 + (print_i32_zero 42)) + +(fn imported_print_i64_status () -> i32 + (print_i64_zero 42i64)) + +(fn imported_print_u32_status () -> i32 + (print_u32_zero 42u32)) + +(fn imported_print_u64_status () -> i32 + (print_u64_zero 42u64)) + +(fn imported_print_f64_status () -> i32 + (print_f64_zero 42.5)) + +(fn imported_print_string_status () -> i32 + (print_string_zero "slovo")) + +(fn imported_print_bool_status () -> i32 + (print_bool_zero true)) + +(fn imported_print_value_helpers_ok () -> bool + (if (= (print_u32_value 42u32) 42u32) + (if (= (print_i64_value 42i64) 42i64) + (if (= (print_u64_value 42u64) 42u64) + (if (= (print_f64_value 42.5) 42.5) + true + false) + false) + false) + false)) + +(fn imported_stdin_result_ok () -> bool + (match (read_stdin_result) + ((ok payload) + (= payload "")) + ((err code) + false))) + +(fn imported_stdin_option_ok () -> bool + (match (read_stdin_option) + ((some payload) + (= payload "")) + ((none) + false))) + +(fn imported_stdin_text_fallback_ok () -> bool + (= (read_stdin_or "fallback") "")) + +(fn imported_stdin_typed_results_ok () -> bool + (if (match (read_stdin_i32_result) + ((ok payload) + false) + ((err code) + true)) + (if (match (read_stdin_u32_result) + ((ok payload) + false) + ((err code) + true)) + (if (match (read_stdin_i64_result) + ((ok payload) + false) + ((err code) + true)) + (if (match (read_stdin_u64_result) + ((ok payload) + false) + ((err code) + true)) + (if (match (read_stdin_f64_result) + ((ok payload) + false) + ((err code) + true)) + (match (read_stdin_bool_result) + ((ok payload) + false) + ((err code) + true)) + false) + false) + false) + false) + false)) + +(fn imported_stdin_typed_options_ok () -> bool + (if (match (read_stdin_i32_option) + ((some payload) + false) + ((none) + true)) + (if (match (read_stdin_u32_option) + ((some payload) + false) + ((none) + true)) + (if (match (read_stdin_i64_option) + ((some payload) + false) + ((none) + true)) + (if (match (read_stdin_u64_option) + ((some payload) + false) + ((none) + true)) + (if (match (read_stdin_f64_option) + ((some payload) + false) + ((none) + true)) + (match (read_stdin_bool_option) + ((some payload) + false) + ((none) + true)) + false) + false) + false) + false) + false)) + +(fn imported_stdin_bool_fallbacks_ok () -> bool + (let empty_bool bool (read_stdin_bool_or_false)) + (let fallback_bool bool (read_stdin_bool_or true)) + (if empty_bool + false + fallback_bool)) + +(fn imported_stdin_typed_fallbacks_ok () -> bool + (if (= (read_stdin_i32_or_zero) 0) + (if (= (read_stdin_i32_or 7) 7) + (if (= (read_stdin_u32_or_zero) 0u32) + (if (= (read_stdin_u32_or 7u32) 7u32) + (if (= (read_stdin_i64_or_zero) 0i64) + (if (= (read_stdin_i64_or 9i64) 9i64) + (if (= (read_stdin_u64_or_zero) 0u64) + (if (= (read_stdin_u64_or 9u64) 9u64) + (if (= (read_stdin_f64_or_zero) 0.0) + (if (= (read_stdin_f64_or 1.5) 1.5) + (imported_stdin_bool_fallbacks_ok) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_io_status_all () -> i32 + (imported_print_u32_status) + (imported_print_i64_status) + (imported_print_u64_status) + (imported_print_f64_status) + 42) + +(fn imported_io_helpers_ok () -> bool + (if (= (imported_io_status_all) 42) + (if (imported_print_value_helpers_ok) + (if (imported_stdin_result_ok) + (if (imported_stdin_option_ok) + (if (imported_stdin_text_fallback_ok) + (if (imported_stdin_typed_results_ok) + (if (imported_stdin_typed_options_ok) + (imported_stdin_typed_fallbacks_ok) + false) + false) + false) + false) + false) + false) + false)) + +(fn main () -> i32 + (if (imported_io_helpers_ok) + 42 + 1)) + +(test "explicit std io i64 zero facade" + (= (imported_print_i64_status) 0)) + +(test "explicit std io u32 zero facade" + (= (imported_print_u32_status) 0)) + +(test "explicit std io u64 zero facade" + (= (imported_print_u64_status) 0)) + +(test "explicit std io f64 zero facade" + (= (imported_print_f64_status) 0)) + +(test "explicit std io value facade" + (imported_print_value_helpers_ok)) + +(test "explicit std io stdin result facade" + (imported_stdin_result_ok)) + +(test "explicit std io stdin option facade" + (imported_stdin_option_ok)) + +(test "explicit std io stdin text fallback facade" + (imported_stdin_text_fallback_ok)) + +(test "explicit std io stdin typed result facade" + (imported_stdin_typed_results_ok)) + +(test "explicit std io stdin typed option facade" + (imported_stdin_typed_options_ok)) + +(test "explicit std io stdin typed fallback facade" + (imported_stdin_typed_fallbacks_ok)) + +(test "explicit std io helpers all" + (= (main) 42)) diff --git a/docs/language/examples/projects/std-import-math/slovo.toml b/docs/language/examples/projects/std-import-math/slovo.toml new file mode 100644 index 0000000..e0e742c --- /dev/null +++ b/docs/language/examples/projects/std-import-math/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-import-math" +source_root = "src" +entry = "main" diff --git a/docs/language/examples/projects/std-import-math/src/main.slo b/docs/language/examples/projects/std-import-math/src/main.slo new file mode 100644 index 0000000..cf2beb4 --- /dev/null +++ b/docs/language/examples/projects/std-import-math/src/main.slo @@ -0,0 +1,102 @@ +(module main) + +(import std.math (abs_i32 neg_i32 rem_i32 bit_and_i32 bit_or_i32 bit_xor_i32 is_even_i32 is_odd_i32 min_i32 max_i32 clamp_i32 square_i32 cube_i32 is_zero_i32 is_positive_i32 is_negative_i32 in_range_i32 abs_i64 neg_i64 rem_i64 bit_and_i64 bit_or_i64 bit_xor_i64 is_even_i64 is_odd_i64 min_i64 max_i64 clamp_i64 square_i64 cube_i64 is_zero_i64 is_positive_i64 is_negative_i64 in_range_i64 abs_f64 neg_f64 min_f64 max_f64 clamp_f64 square_f64 cube_f64 is_zero_f64 is_positive_f64 is_negative_f64 in_range_f64)) + +(fn imported_i32_score () -> i32 + (clamp_i32 (+ (square_i32 (abs_i32 (neg_i32 6))) (max_i32 (min_i32 (cube_i32 1) 7) 6)) 0 42)) + +(fn imported_i64_score () -> i64 + (clamp_i64 (+ (square_i64 (abs_i64 (neg_i64 6i64))) (max_i64 (min_i64 (cube_i64 1i64) 7i64) 6i64)) 0i64 42i64)) + +(fn imported_f64_score () -> f64 + (clamp_f64 (+ (square_f64 (abs_f64 (neg_f64 6.0))) (max_f64 (min_f64 (cube_f64 1.0) 7.0) 6.0)) 0.0 42.0)) + +(fn imported_i32_predicates_ok () -> bool + (if (is_zero_i32 (neg_i32 0)) + (if (is_positive_i32 (abs_i32 -5)) + (if (is_negative_i32 (neg_i32 5)) + (if (= (rem_i32 17 5) 2) + (if (is_even_i32 42) + (if (is_odd_i32 -41) + (if (= (bit_and_i32 6 3) 2) + (if (= (bit_or_i32 4 2) 6) + (if (= (bit_xor_i32 7 3) 4) + (in_range_i32 (imported_i32_score) 0 42) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_i64_predicates_ok () -> bool + (if (is_zero_i64 (neg_i64 0i64)) + (if (is_positive_i64 (abs_i64 -5i64)) + (if (is_negative_i64 (neg_i64 5i64)) + (if (= (rem_i64 17i64 5i64) 2i64) + (if (is_even_i64 42i64) + (if (is_odd_i64 -41i64) + (if (= (bit_and_i64 6i64 3i64) 2i64) + (if (= (bit_or_i64 4i64 2i64) 6i64) + (if (= (bit_xor_i64 7i64 3i64) 4i64) + (in_range_i64 (imported_i64_score) 0i64 42i64) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_f64_predicates_ok () -> bool + (if (is_zero_f64 (neg_f64 0.0)) + (if (is_positive_f64 (abs_f64 -5.0)) + (if (is_negative_f64 (neg_f64 5.0)) + (in_range_f64 (imported_f64_score) 0.0 42.0) + false) + false) + false)) + +(fn imported_i32_helpers_ok () -> bool + (if (= (imported_i32_score) 42) + (imported_i32_predicates_ok) + false)) + +(fn imported_i64_helpers_ok () -> bool + (if (= (imported_i64_score) 42i64) + (imported_i64_predicates_ok) + false)) + +(fn imported_f64_helpers_ok () -> bool + (if (= (imported_f64_score) 42.0) + (imported_f64_predicates_ok) + false)) + +(fn imported_math_helpers_ok () -> bool + (if (imported_i32_helpers_ok) + (if (imported_i64_helpers_ok) + (imported_f64_helpers_ok) + false) + false)) + +(fn main () -> i32 + (if (imported_math_helpers_ok) + 42 + 1)) + +(test "explicit std math import i32 helpers" + (imported_i32_helpers_ok)) + +(test "explicit std math import i64 helpers" + (imported_i64_helpers_ok)) + +(test "explicit std math import f64 helpers" + (imported_f64_helpers_ok)) + +(test "explicit std math import all helpers" + (= (main) 42)) diff --git a/docs/language/examples/projects/std-import-num/slovo.toml b/docs/language/examples/projects/std-import-num/slovo.toml new file mode 100644 index 0000000..440bbd0 --- /dev/null +++ b/docs/language/examples/projects/std-import-num/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-import-num" +source_root = "src" +entry = "main" diff --git a/docs/language/examples/projects/std-import-num/src/main.slo b/docs/language/examples/projects/std-import-num/src/main.slo new file mode 100644 index 0000000..989a86d --- /dev/null +++ b/docs/language/examples/projects/std-import-num/src/main.slo @@ -0,0 +1,70 @@ +(module main) + +(import std.num (i32_to_i64 i32_to_f64 i64_to_f64 i64_to_i32_result f64_to_i32_result f64_to_i64_result i32_to_string u32_to_string i64_to_string u64_to_string f64_to_string i64_to_i32_or f64_to_i32_or f64_to_i64_or)) + +(fn imported_num_widening_ok () -> bool + (if (= (i32_to_i64 42) 42i64) + (if (= (i32_to_f64 42) 42.0) + (= (i64_to_f64 42i64) 42.0) + false) + false)) + +(fn imported_num_checked_ok () -> bool + (if (= (unwrap_ok (i64_to_i32_result 42i64)) 42) + (if (= (unwrap_ok (f64_to_i32_result 42.0)) 42) + (= (unwrap_ok (f64_to_i64_result 42.0)) 42i64) + false) + false)) + +(fn imported_num_strings_ok () -> bool + (if (= (i32_to_string 42) "42") + (if (= (u32_to_string 42u32) "42") + (if (= (i64_to_string 42i64) "42") + (if (= (u64_to_string 42u64) "42") + (= (f64_to_string 42.0) "42.0") + false) + false) + false) + false)) + +(fn imported_num_fallbacks_ok () -> bool + (if (= (i64_to_i32_or 42i64 7) 42) + (if (= (i64_to_i32_or 2147483648i64 7) 7) + (if (= (f64_to_i32_or 42.0 7) 42) + (if (= (f64_to_i32_or 2147483648.0 7) 7) + (if (= (f64_to_i64_or 42.0 7i64) 42i64) + (= (f64_to_i64_or 100000000000000000000.0 7i64) 7i64) + false) + false) + false) + false) + false)) + +(fn imported_num_helpers_ok () -> bool + (if (imported_num_widening_ok) + (if (imported_num_checked_ok) + (if (imported_num_strings_ok) + (imported_num_fallbacks_ok) + false) + false) + false)) + +(fn main () -> i32 + (if (imported_num_helpers_ok) + 42 + 1)) + +(test "explicit std num widening" + (imported_num_widening_ok)) + +(test "explicit std num checked conversions" + (imported_num_checked_ok)) + +(test "explicit std num to string" + (imported_num_strings_ok)) + +(test "explicit std num checked fallbacks" + (imported_num_fallbacks_ok)) + +(test "explicit std num helpers all" + (= (main) 42)) diff --git a/docs/language/examples/projects/std-import-option/slovo.toml b/docs/language/examples/projects/std-import-option/slovo.toml new file mode 100644 index 0000000..03f924c --- /dev/null +++ b/docs/language/examples/projects/std-import-option/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-import-option" +source_root = "src" +entry = "main" diff --git a/docs/language/examples/projects/std-import-option/src/main.slo b/docs/language/examples/projects/std-import-option/src/main.slo new file mode 100644 index 0000000..f571792 --- /dev/null +++ b/docs/language/examples/projects/std-import-option/src/main.slo @@ -0,0 +1,408 @@ +(module main) + +(import std.option (some_i32 none_i32 is_some_i32 is_none_i32 unwrap_some_i32 unwrap_or_i32 some_or_err_i32 some_u32 none_u32 is_some_u32 is_none_u32 unwrap_some_u32 unwrap_or_u32 some_or_err_u32 some_i64 none_i64 is_some_i64 is_none_i64 unwrap_some_i64 unwrap_or_i64 some_or_err_i64 some_u64 none_u64 is_some_u64 is_none_u64 unwrap_some_u64 unwrap_or_u64 some_or_err_u64 some_f64 none_f64 is_some_f64 is_none_f64 unwrap_some_f64 unwrap_or_f64 some_or_err_f64 some_bool none_bool is_some_bool is_none_bool unwrap_some_bool unwrap_or_bool some_or_err_bool some_string none_string is_some_string is_none_string unwrap_some_string unwrap_or_string some_or_err_string)) + +(fn some_value_i32 ((value i32)) -> (option i32) + (some_i32 value)) + +(fn none_value_i32 () -> (option i32) + (none_i32)) + +(fn some_value_i64 ((value i64)) -> (option i64) + (some_i64 value)) + +(fn none_value_i64 () -> (option i64) + (none_i64)) + +(fn some_value_u32 ((value u32)) -> (option u32) + (some_u32 value)) + +(fn none_value_u32 () -> (option u32) + (none_u32)) + +(fn some_value_u64 ((value u64)) -> (option u64) + (some_u64 value)) + +(fn none_value_u64 () -> (option u64) + (none_u64)) + +(fn some_value_f64 ((value f64)) -> (option f64) + (some_f64 value)) + +(fn none_value_f64 () -> (option f64) + (none_f64)) + +(fn some_value_bool ((value bool)) -> (option bool) + (some_bool value)) + +(fn none_value_bool () -> (option bool) + (none_bool)) + +(fn some_value_string ((value string)) -> (option string) + (some_string value)) + +(fn none_value_string () -> (option string) + (none_string)) + +(fn imported_option_i32_observation_ok () -> bool + (if (is_some_i32 (some_value_i32 42)) + (is_none_i32 (none_value_i32)) + false)) + +(fn imported_option_i32_unwrap_some_score () -> i32 + (+ (unwrap_some_i32 (some_value_i32 20)) (unwrap_some_i32 (some_value_i32 22)))) + +(fn imported_option_i32_unwrap_or_score () -> i32 + (+ (unwrap_or_i32 (some_value_i32 40) 0) (unwrap_or_i32 (none_value_i32) 2))) + +(fn imported_option_i32_some_or_err_ok () -> bool + (match (some_or_err_i32 (some_value_i32 42) 5) + ((ok payload) + (= payload 42)) + ((err code) + false))) + +(fn imported_option_i32_some_or_err_err () -> bool + (match (some_or_err_i32 (none_value_i32) 5) + ((ok payload) + false) + ((err code) + (= code 5)))) + +(fn imported_option_i64_observation_ok () -> bool + (if (is_some_i64 (some_value_i64 2147483648i64)) + (is_none_i64 (none_value_i64)) + false)) + +(fn imported_option_i64_unwrap_some_score () -> i64 + (+ (unwrap_some_i64 (some_value_i64 2147483648i64)) (unwrap_some_i64 (some_value_i64 12i64)))) + +(fn imported_option_i64_unwrap_or_score () -> i64 + (+ (unwrap_or_i64 (some_value_i64 40i64) 0i64) (unwrap_or_i64 (none_value_i64) 2i64))) + +(fn imported_option_i64_some_or_err_ok () -> bool + (match (some_or_err_i64 (some_value_i64 2147483648i64) 6) + ((ok payload) + (= payload 2147483648i64)) + ((err code) + false))) + +(fn imported_option_i64_some_or_err_err () -> bool + (match (some_or_err_i64 (none_value_i64) 6) + ((ok payload) + false) + ((err code) + (= code 6)))) + +(fn imported_option_u32_observation_ok () -> bool + (if (is_some_u32 (some_value_u32 42u32)) + (is_none_u32 (none_value_u32)) + false)) + +(fn imported_option_u32_unwrap_some_score () -> u32 + (+ (unwrap_some_u32 (some_value_u32 20u32)) (unwrap_some_u32 (some_value_u32 22u32)))) + +(fn imported_option_u32_unwrap_or_score () -> u32 + (+ (unwrap_or_u32 (some_value_u32 40u32) 0u32) (unwrap_or_u32 (none_value_u32) 2u32))) + +(fn imported_option_u32_some_or_err_ok () -> bool + (match (some_or_err_u32 (some_value_u32 42u32) 10) + ((ok payload) + (= payload 42u32)) + ((err code) + false))) + +(fn imported_option_u32_some_or_err_err () -> bool + (match (some_or_err_u32 (none_value_u32) 10) + ((ok payload) + false) + ((err code) + (= code 10)))) + +(fn imported_option_u64_observation_ok () -> bool + (if (is_some_u64 (some_value_u64 4294967296u64)) + (is_none_u64 (none_value_u64)) + false)) + +(fn imported_option_u64_unwrap_some_score () -> u64 + (+ (unwrap_some_u64 (some_value_u64 4294967296u64)) (unwrap_some_u64 (some_value_u64 12u64)))) + +(fn imported_option_u64_unwrap_or_score () -> u64 + (+ (unwrap_or_u64 (some_value_u64 40u64) 0u64) (unwrap_or_u64 (none_value_u64) 2u64))) + +(fn imported_option_u64_some_or_err_ok () -> bool + (match (some_or_err_u64 (some_value_u64 4294967296u64) 11) + ((ok payload) + (= payload 4294967296u64)) + ((err code) + false))) + +(fn imported_option_u64_some_or_err_err () -> bool + (match (some_or_err_u64 (none_value_u64) 11) + ((ok payload) + false) + ((err code) + (= code 11)))) + +(fn imported_option_f64_observation_ok () -> bool + (if (is_some_f64 (some_value_f64 42.5)) + (is_none_f64 (none_value_f64)) + false)) + +(fn imported_option_f64_unwrap_some_score () -> f64 + (+ (unwrap_some_f64 (some_value_f64 20.5)) (unwrap_some_f64 (some_value_f64 21.5)))) + +(fn imported_option_f64_unwrap_or_score () -> f64 + (+ (unwrap_or_f64 (some_value_f64 40.0) 0.0) (unwrap_or_f64 (none_value_f64) 2.0))) + +(fn imported_option_f64_some_or_err_ok () -> bool + (match (some_or_err_f64 (some_value_f64 42.5) 7) + ((ok payload) + (= payload 42.5)) + ((err code) + false))) + +(fn imported_option_f64_some_or_err_err () -> bool + (match (some_or_err_f64 (none_value_f64) 7) + ((ok payload) + false) + ((err code) + (= code 7)))) + +(fn imported_option_bool_observation_ok () -> bool + (if (is_some_bool (some_value_bool true)) + (is_none_bool (none_value_bool)) + false)) + +(fn imported_option_bool_unwrap_some_ok () -> bool + (unwrap_some_bool (some_value_bool true))) + +(fn imported_option_bool_unwrap_or_ok () -> bool + (if (unwrap_or_bool (some_value_bool true) false) + (unwrap_or_bool (none_value_bool) true) + false)) + +(fn imported_option_bool_some_or_err_ok () -> bool + (match (some_or_err_bool (some_value_bool true) 8) + ((ok payload) + payload) + ((err code) + false))) + +(fn imported_option_bool_some_or_err_err () -> bool + (match (some_or_err_bool (none_value_bool) 8) + ((ok payload) + false) + ((err code) + (= code 8)))) + +(fn imported_option_string_observation_ok () -> bool + (if (is_some_string (some_value_string "slovo")) + (is_none_string (none_value_string)) + false)) + +(fn imported_option_string_unwrap_some_ok () -> bool + (= (unwrap_some_string (some_value_string "slovo")) "slovo")) + +(fn imported_option_string_unwrap_or_ok () -> bool + (if (= (unwrap_or_string (some_value_string "oak") "") "oak") + (= (unwrap_or_string (none_value_string) "fallback") "fallback") + false)) + +(fn imported_option_string_some_or_err_ok () -> bool + (match (some_or_err_string (some_value_string "slovo") 9) + ((ok payload) + (= payload "slovo")) + ((err code) + false))) + +(fn imported_option_string_some_or_err_err () -> bool + (match (some_or_err_string (none_value_string) 9) + ((ok payload) + false) + ((err code) + (= code 9)))) + +(fn imported_option_helpers_ok () -> bool + (if (imported_option_i32_observation_ok) + (if (= (imported_option_i32_unwrap_some_score) 42) + (if (= (imported_option_i32_unwrap_or_score) 42) + (if (imported_option_i32_some_or_err_ok) + (if (imported_option_i32_some_or_err_err) + (if (imported_option_u32_observation_ok) + (if (= (imported_option_u32_unwrap_some_score) 42u32) + (if (= (imported_option_u32_unwrap_or_score) 42u32) + (if (imported_option_u32_some_or_err_ok) + (if (imported_option_u32_some_or_err_err) + (if (imported_option_i64_observation_ok) + (if (= (imported_option_i64_unwrap_some_score) 2147483660i64) + (if (= (imported_option_i64_unwrap_or_score) 42i64) + (if (imported_option_i64_some_or_err_ok) + (if (imported_option_i64_some_or_err_err) + (if (imported_option_u64_observation_ok) + (if (= (imported_option_u64_unwrap_some_score) 4294967308u64) + (if (= (imported_option_u64_unwrap_or_score) 42u64) + (if (imported_option_u64_some_or_err_ok) + (if (imported_option_u64_some_or_err_err) + (if (imported_option_f64_observation_ok) + (if (= (imported_option_f64_unwrap_some_score) 42.0) + (if (= (imported_option_f64_unwrap_or_score) 42.0) + (if (imported_option_f64_some_or_err_ok) + (if (imported_option_f64_some_or_err_err) + (if (imported_option_bool_observation_ok) + (if (imported_option_bool_unwrap_some_ok) + (if (imported_option_bool_unwrap_or_ok) + (if (imported_option_bool_some_or_err_ok) + (if (imported_option_bool_some_or_err_err) + (if (imported_option_string_observation_ok) + (if (imported_option_string_unwrap_some_ok) + (if (imported_option_string_unwrap_or_ok) + (if (imported_option_string_some_or_err_ok) + (imported_option_string_some_or_err_err) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn main () -> i32 + (if (imported_option_helpers_ok) + 42 + 1)) + +(test "explicit std option i32 observation" + (imported_option_i32_observation_ok)) + +(test "explicit std option i32 unwrap_some" + (= (imported_option_i32_unwrap_some_score) 42)) + +(test "explicit std option i32 unwrap_or" + (= (imported_option_i32_unwrap_or_score) 42)) + +(test "explicit std option i32 some_or_err ok" + (imported_option_i32_some_or_err_ok)) + +(test "explicit std option i32 some_or_err err" + (imported_option_i32_some_or_err_err)) + +(test "explicit std option u32 observation" + (imported_option_u32_observation_ok)) + +(test "explicit std option u32 unwrap_some" + (= (imported_option_u32_unwrap_some_score) 42u32)) + +(test "explicit std option u32 unwrap_or" + (= (imported_option_u32_unwrap_or_score) 42u32)) + +(test "explicit std option u32 some_or_err ok" + (imported_option_u32_some_or_err_ok)) + +(test "explicit std option u32 some_or_err err" + (imported_option_u32_some_or_err_err)) + +(test "explicit std option i64 observation" + (imported_option_i64_observation_ok)) + +(test "explicit std option i64 unwrap_some" + (= (imported_option_i64_unwrap_some_score) 2147483660i64)) + +(test "explicit std option i64 unwrap_or" + (= (imported_option_i64_unwrap_or_score) 42i64)) + +(test "explicit std option i64 some_or_err ok" + (imported_option_i64_some_or_err_ok)) + +(test "explicit std option i64 some_or_err err" + (imported_option_i64_some_or_err_err)) + +(test "explicit std option u64 observation" + (imported_option_u64_observation_ok)) + +(test "explicit std option u64 unwrap_some" + (= (imported_option_u64_unwrap_some_score) 4294967308u64)) + +(test "explicit std option u64 unwrap_or" + (= (imported_option_u64_unwrap_or_score) 42u64)) + +(test "explicit std option u64 some_or_err ok" + (imported_option_u64_some_or_err_ok)) + +(test "explicit std option u64 some_or_err err" + (imported_option_u64_some_or_err_err)) + +(test "explicit std option f64 observation" + (imported_option_f64_observation_ok)) + +(test "explicit std option f64 unwrap_some" + (= (imported_option_f64_unwrap_some_score) 42.0)) + +(test "explicit std option f64 unwrap_or" + (= (imported_option_f64_unwrap_or_score) 42.0)) + +(test "explicit std option f64 some_or_err ok" + (imported_option_f64_some_or_err_ok)) + +(test "explicit std option f64 some_or_err err" + (imported_option_f64_some_or_err_err)) + +(test "explicit std option bool observation" + (imported_option_bool_observation_ok)) + +(test "explicit std option bool unwrap_some" + (imported_option_bool_unwrap_some_ok)) + +(test "explicit std option bool unwrap_or" + (imported_option_bool_unwrap_or_ok)) + +(test "explicit std option bool some_or_err ok" + (imported_option_bool_some_or_err_ok)) + +(test "explicit std option bool some_or_err err" + (imported_option_bool_some_or_err_err)) + +(test "explicit std option string observation" + (imported_option_string_observation_ok)) + +(test "explicit std option string unwrap_some" + (imported_option_string_unwrap_some_ok)) + +(test "explicit std option string unwrap_or" + (imported_option_string_unwrap_or_ok)) + +(test "explicit std option string some_or_err ok" + (imported_option_string_some_or_err_ok)) + +(test "explicit std option string some_or_err err" + (imported_option_string_some_or_err_err)) + +(test "explicit std option helpers all" + (= (main) 42)) diff --git a/docs/language/examples/projects/std-import-process/slovo.toml b/docs/language/examples/projects/std-import-process/slovo.toml new file mode 100644 index 0000000..5d444fe --- /dev/null +++ b/docs/language/examples/projects/std-import-process/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-import-process" +source_root = "src" +entry = "main" diff --git a/docs/language/examples/projects/std-import-process/src/main.slo b/docs/language/examples/projects/std-import-process/src/main.slo new file mode 100644 index 0000000..75290c5 --- /dev/null +++ b/docs/language/examples/projects/std-import-process/src/main.slo @@ -0,0 +1,319 @@ +(module main) + +(import std.process (argc arg arg_result arg_option has_arg arg_or arg_or_empty arg_i32_result arg_i32_option arg_i32_or_zero arg_i32_or arg_u32_result arg_u32_option arg_u32_or_zero arg_u32_or arg_i64_result arg_i64_option arg_i64_or_zero arg_i64_or arg_u64_result arg_u64_option arg_u64_or_zero arg_u64_or arg_f64_result arg_f64_option arg_f64_or_zero arg_f64_or arg_bool_result arg_bool_option arg_bool_or_false arg_bool_or)) + +(fn impossible_index () -> i32 + 99999) + +(fn first_arg () -> string + (arg 0)) + +(fn first_index () -> i32 + 0) + +(fn imported_arg_count_positive () -> bool + (< 0 (argc))) + +(fn imported_arg_result_oob_err_one () -> bool + (match (arg_result (impossible_index)) + ((ok payload) + false) + ((err code) + (= code 1)))) + +(fn imported_arg_option_flows_ok () -> bool + (if (match (arg_option (first_index)) + ((some payload) + (= payload (arg (first_index)))) + ((none) + false)) + (match (arg_option (impossible_index)) + ((some payload) + false) + ((none) + true)) + false)) + +(fn imported_has_arg_zero () -> bool + (has_arg 0)) + +(fn imported_arg_fallback_missing_ok () -> bool + (if (= (arg_or (impossible_index) "fallback") "fallback") + (= (arg_or_empty (impossible_index)) "") + false)) + +(fn imported_arg_fallback_present_ok () -> bool + (if (has_arg 0) + (if (= (arg_or 0 "fallback") (arg 0)) + (= (arg_or_empty 0) (arg 0)) + false) + false)) + +(fn imported_arg_i32_missing_err_one () -> bool + (match (arg_i32_result (impossible_index)) + ((ok value) + false) + ((err code) + (= code 1)))) + +(fn imported_arg_i32_present_invalid_zero () -> bool + (= (arg_i32_or_zero (first_index)) 0)) + +(fn imported_arg_u32_missing_err_one () -> bool + (match (arg_u32_result (impossible_index)) + ((ok value) + false) + ((err code) + (= code 1)))) + +(fn imported_arg_u32_present_invalid_zero () -> bool + (= (arg_u32_or_zero (first_index)) 0u32)) + +(fn imported_arg_i64_missing_err_one () -> bool + (match (arg_i64_result (impossible_index)) + ((ok value) + false) + ((err code) + (= code 1)))) + +(fn imported_arg_i64_present_invalid_zero () -> bool + (= (arg_i64_or_zero (first_index)) 0i64)) + +(fn imported_arg_u64_missing_err_one () -> bool + (match (arg_u64_result (impossible_index)) + ((ok value) + false) + ((err code) + (= code 1)))) + +(fn imported_arg_u64_present_invalid_zero () -> bool + (= (arg_u64_or_zero (first_index)) 0u64)) + +(fn imported_arg_f64_missing_err_one () -> bool + (match (arg_f64_result (impossible_index)) + ((ok value) + false) + ((err code) + (= code 1)))) + +(fn imported_arg_f64_present_invalid_zero () -> bool + (= (arg_f64_or_zero (first_index)) 0.0)) + +(fn imported_arg_bool_missing_err_one () -> bool + (match (arg_bool_result (impossible_index)) + ((ok value) + false) + ((err code) + (= code 1)))) + +(fn imported_arg_bool_present_invalid_false () -> bool + (if (arg_bool_or_false (first_index)) + false + true)) + +(fn imported_arg_typed_options_ok () -> bool + (if (match (arg_i32_option (impossible_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_i32_option (first_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_u32_option (impossible_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_u32_option (first_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_i64_option (impossible_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_i64_option (first_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_u64_option (impossible_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_u64_option (first_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_f64_option (impossible_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_f64_option (first_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_bool_option (impossible_index)) + ((some value) + false) + ((none) + true)) + (match (arg_bool_option (first_index)) + ((some value) + false) + ((none) + true)) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_arg_typed_custom_fallbacks_ok () -> bool + (if (= (arg_i32_or (impossible_index) 7) 7) + (if (= (arg_i32_or (first_index) 7) 7) + (if (= (arg_u32_or (impossible_index) 7u32) 7u32) + (if (= (arg_u32_or (first_index) 7u32) 7u32) + (if (= (arg_i64_or (impossible_index) 9i64) 9i64) + (if (= (arg_i64_or (first_index) 9i64) 9i64) + (if (= (arg_u64_or (impossible_index) 9u64) 9u64) + (if (= (arg_u64_or (first_index) 9u64) 9u64) + (if (= (arg_f64_or (impossible_index) 1.5) 1.5) + (if (= (arg_f64_or (first_index) 1.5) 1.5) + (if (arg_bool_or (impossible_index) true) + (arg_bool_or (first_index) true) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_process_facade_ok () -> bool + (if (imported_arg_count_positive) + (if (imported_arg_result_oob_err_one) + (if (imported_arg_option_flows_ok) + (if (imported_has_arg_zero) + (if (imported_arg_fallback_missing_ok) + (if (imported_arg_fallback_present_ok) + (if (imported_arg_i32_missing_err_one) + (if (imported_arg_i32_present_invalid_zero) + (if (imported_arg_u32_missing_err_one) + (if (imported_arg_u32_present_invalid_zero) + (if (imported_arg_i64_missing_err_one) + (if (imported_arg_i64_present_invalid_zero) + (if (imported_arg_u64_missing_err_one) + (if (imported_arg_u64_present_invalid_zero) + (if (imported_arg_f64_missing_err_one) + (if (imported_arg_f64_present_invalid_zero) + (if (imported_arg_bool_missing_err_one) + (if (imported_arg_bool_present_invalid_false) + (if (imported_arg_typed_options_ok) + (imported_arg_typed_custom_fallbacks_ok) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn main () -> i32 + (if (imported_process_facade_ok) + 42 + 1)) + +(test "explicit std process argc facade" + (imported_arg_count_positive)) + +(test "explicit std process arg result oob facade" + (imported_arg_result_oob_err_one)) + +(test "explicit std process arg option facade" + (imported_arg_option_flows_ok)) + +(test "explicit std process has arg facade" + (imported_has_arg_zero)) + +(test "explicit std process arg fallback missing facade" + (imported_arg_fallback_missing_ok)) + +(test "explicit std process arg fallback present facade" + (imported_arg_fallback_present_ok)) + +(test "explicit std process arg i32 missing facade" + (imported_arg_i32_missing_err_one)) + +(test "explicit std process arg i32 invalid fallback facade" + (imported_arg_i32_present_invalid_zero)) + +(test "explicit std process arg u32 missing facade" + (imported_arg_u32_missing_err_one)) + +(test "explicit std process arg u32 invalid fallback facade" + (imported_arg_u32_present_invalid_zero)) + +(test "explicit std process arg i64 missing facade" + (imported_arg_i64_missing_err_one)) + +(test "explicit std process arg i64 invalid fallback facade" + (imported_arg_i64_present_invalid_zero)) + +(test "explicit std process arg u64 missing facade" + (imported_arg_u64_missing_err_one)) + +(test "explicit std process arg u64 invalid fallback facade" + (imported_arg_u64_present_invalid_zero)) + +(test "explicit std process arg f64 missing facade" + (imported_arg_f64_missing_err_one)) + +(test "explicit std process arg f64 invalid fallback facade" + (imported_arg_f64_present_invalid_zero)) + +(test "explicit std process arg bool missing facade" + (imported_arg_bool_missing_err_one)) + +(test "explicit std process arg bool invalid fallback facade" + (imported_arg_bool_present_invalid_false)) + +(test "explicit std process typed option facade" + (imported_arg_typed_options_ok)) + +(test "explicit std process typed custom fallback facade" + (imported_arg_typed_custom_fallbacks_ok)) + +(test "explicit std process facade all" + (= (main) 42)) diff --git a/docs/language/examples/projects/std-import-random/slovo.toml b/docs/language/examples/projects/std-import-random/slovo.toml new file mode 100644 index 0000000..27260be --- /dev/null +++ b/docs/language/examples/projects/std-import-random/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-import-random" +source_root = "src" +entry = "main" diff --git a/docs/language/examples/projects/std-import-random/src/main.slo b/docs/language/examples/projects/std-import-random/src/main.slo new file mode 100644 index 0000000..78d98ca --- /dev/null +++ b/docs/language/examples/projects/std-import-random/src/main.slo @@ -0,0 +1,22 @@ +(module main) + +(import std.random (random_i32 random_i32_non_negative)) + +(fn imported_random_i32_non_negative () -> bool + (>= (random_i32) 0)) + +(fn imported_random_facade_ok () -> bool + (if (imported_random_i32_non_negative) + (random_i32_non_negative) + false)) + +(fn main () -> i32 + (if (imported_random_facade_ok) + 42 + 1)) + +(test "explicit std random i32 non-negative facade" + (imported_random_i32_non_negative)) + +(test "explicit std random facade all" + (= (main) 42)) diff --git a/docs/language/examples/projects/std-import-result/slovo.toml b/docs/language/examples/projects/std-import-result/slovo.toml new file mode 100644 index 0000000..3edb270 --- /dev/null +++ b/docs/language/examples/projects/std-import-result/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-import-result" +source_root = "src" +entry = "main" diff --git a/docs/language/examples/projects/std-import-result/src/main.slo b/docs/language/examples/projects/std-import-result/src/main.slo new file mode 100644 index 0000000..48070ae --- /dev/null +++ b/docs/language/examples/projects/std-import-result/src/main.slo @@ -0,0 +1,353 @@ +(module main) + +(import std.result (ok_i32 err_i32 is_ok_i32 is_err_i32 unwrap_ok_i32 unwrap_err_i32 unwrap_or_i32 ok_or_none_i32 ok_u32 err_u32 is_ok_u32 is_err_u32 unwrap_ok_u32 unwrap_err_u32 unwrap_or_u32 ok_or_none_u32 ok_i64 err_i64 is_err_i64 unwrap_err_i64 unwrap_or_i64 ok_or_none_i64 ok_u64 err_u64 is_ok_u64 is_err_u64 unwrap_ok_u64 unwrap_err_u64 unwrap_or_u64 ok_or_none_u64 ok_string err_string is_err_string unwrap_err_string unwrap_or_string ok_or_none_string ok_f64 err_f64 is_err_f64 unwrap_err_f64 unwrap_or_f64 ok_or_none_f64 ok_bool err_bool is_ok_bool is_err_bool unwrap_ok_bool unwrap_err_bool unwrap_or_bool ok_or_none_bool)) + +(fn i32_ok () -> (result i32 i32) + (ok_i32 42)) + +(fn i32_err () -> (result i32 i32) + (err_i32 5)) + +(fn i64_ok () -> (result i64 i32) + (ok_i64 60i64)) + +(fn i64_err () -> (result i64 i32) + (err_i64 6)) + +(fn u32_ok () -> (result u32 i32) + (ok_u32 42u32)) + +(fn u32_err () -> (result u32 i32) + (err_u32 8)) + +(fn u64_ok () -> (result u64 i32) + (ok_u64 4294967296u64)) + +(fn u64_err () -> (result u64 i32) + (err_u64 9)) + +(fn string_ok () -> (result string i32) + (ok_string "value")) + +(fn string_err () -> (result string i32) + (err_string 7)) + +(fn f64_ok () -> (result f64 i32) + (ok_f64 12.5)) + +(fn f64_err () -> (result f64 i32) + (err_f64 1)) + +(fn bool_ok () -> (result bool i32) + (ok_bool true)) + +(fn bool_false () -> (result bool i32) + (ok_bool false)) + +(fn bool_err () -> (result bool i32) + (err_bool 1)) + +(fn imported_i32_result_wrappers_ok () -> bool + (if (is_ok_i32 (i32_ok)) + (if (= (unwrap_ok_i32 (i32_ok)) 42) + (if (is_err_i32 (i32_err)) + (= (unwrap_err_i32 (i32_err)) 5) + false) + false) + false)) + +(fn imported_result_error_wrappers_ok () -> bool + (if (is_err_i64 (i64_err)) + (if (= (unwrap_err_i64 (i64_err)) 6) + (if (is_err_string (string_err)) + (if (= (unwrap_err_string (string_err)) 7) + (if (is_err_f64 (f64_err)) + (= (unwrap_err_f64 (f64_err)) 1) + false) + false) + false) + false) + false)) + +(fn imported_unwrap_or_i32_score () -> i32 + (+ (unwrap_or_i32 (i32_ok) 0) (unwrap_or_i32 (i32_err) 5))) + +(fn imported_ok_or_none_i32_ok () -> bool + (match (ok_or_none_i32 (i32_ok)) + ((some payload) + (= payload 42)) + ((none) + false))) + +(fn imported_ok_or_none_i32_none () -> bool + (match (ok_or_none_i32 (i32_err)) + ((some payload) + false) + ((none) + true))) + +(fn imported_unwrap_or_i64_score () -> i64 + (+ (unwrap_or_i64 (i64_ok) 0i64) (unwrap_or_i64 (i64_err) 5i64))) + +(fn imported_u32_result_wrappers_ok () -> bool + (if (is_ok_u32 (u32_ok)) + (if (= (unwrap_ok_u32 (u32_ok)) 42u32) + (if (is_err_u32 (u32_err)) + (= (unwrap_err_u32 (u32_err)) 8) + false) + false) + false)) + +(fn imported_unwrap_or_u32_score () -> u32 + (+ (unwrap_or_u32 (u32_ok) 0u32) (unwrap_or_u32 (u32_err) 5u32))) + +(fn imported_ok_or_none_u32_ok () -> bool + (match (ok_or_none_u32 (u32_ok)) + ((some payload) + (= payload 42u32)) + ((none) + false))) + +(fn imported_ok_or_none_u32_none () -> bool + (match (ok_or_none_u32 (u32_err)) + ((some payload) + false) + ((none) + true))) + +(fn imported_ok_or_none_i64_ok () -> bool + (match (ok_or_none_i64 (i64_ok)) + ((some payload) + (= payload 60i64)) + ((none) + false))) + +(fn imported_ok_or_none_i64_none () -> bool + (match (ok_or_none_i64 (i64_err)) + ((some payload) + false) + ((none) + true))) + +(fn imported_u64_result_wrappers_ok () -> bool + (if (is_ok_u64 (u64_ok)) + (if (= (unwrap_ok_u64 (u64_ok)) 4294967296u64) + (if (is_err_u64 (u64_err)) + (= (unwrap_err_u64 (u64_err)) 9) + false) + false) + false)) + +(fn imported_unwrap_or_u64_score () -> u64 + (+ (unwrap_or_u64 (u64_ok) 0u64) (unwrap_or_u64 (u64_err) 5u64))) + +(fn imported_ok_or_none_u64_ok () -> bool + (match (ok_or_none_u64 (u64_ok)) + ((some payload) + (= payload 4294967296u64)) + ((none) + false))) + +(fn imported_ok_or_none_u64_none () -> bool + (match (ok_or_none_u64 (u64_err)) + ((some payload) + false) + ((none) + true))) + +(fn imported_unwrap_or_string_ok () -> bool + (if (= (unwrap_or_string (string_ok) "fallback") "value") + (= (unwrap_or_string (string_err) "fallback") "fallback") + false)) + +(fn imported_ok_or_none_string_ok () -> bool + (match (ok_or_none_string (string_ok)) + ((some payload) + (= payload "value")) + ((none) + false))) + +(fn imported_ok_or_none_string_none () -> bool + (match (ok_or_none_string (string_err)) + ((some payload) + false) + ((none) + true))) + +(fn imported_unwrap_or_f64_score () -> f64 + (+ (unwrap_or_f64 (f64_ok) 0.0) (unwrap_or_f64 (f64_err) 2.5))) + +(fn imported_ok_or_none_f64_ok () -> bool + (match (ok_or_none_f64 (f64_ok)) + ((some payload) + (= payload 12.5)) + ((none) + false))) + +(fn imported_ok_or_none_f64_none () -> bool + (match (ok_or_none_f64 (f64_err)) + ((some payload) + false) + ((none) + true))) + +(fn imported_bool_result_helpers_ok () -> bool + (if (is_ok_bool (bool_ok)) + (if (unwrap_ok_bool (bool_ok)) + (if (is_err_bool (bool_err)) + (if (= (unwrap_err_bool (bool_err)) 1) + (if (unwrap_or_bool (bool_false) true) + false + (unwrap_or_bool (bool_err) true)) + false) + false) + false) + false)) + +(fn imported_ok_or_none_bool_ok () -> bool + (match (ok_or_none_bool (bool_ok)) + ((some payload) + payload) + ((none) + false))) + +(fn imported_ok_or_none_bool_none () -> bool + (match (ok_or_none_bool (bool_err)) + ((some payload) + false) + ((none) + true))) + +(fn imported_result_helpers_ok () -> bool + (if (imported_i32_result_wrappers_ok) + (if (imported_u32_result_wrappers_ok) + (if (imported_result_error_wrappers_ok) + (if (= (imported_unwrap_or_i32_score) 47) + (if (imported_ok_or_none_i32_ok) + (if (imported_ok_or_none_i32_none) + (if (= (imported_unwrap_or_u32_score) 47u32) + (if (imported_ok_or_none_u32_ok) + (if (imported_ok_or_none_u32_none) + (if (= (imported_unwrap_or_i64_score) 65i64) + (if (imported_ok_or_none_i64_ok) + (if (imported_ok_or_none_i64_none) + (if (imported_u64_result_wrappers_ok) + (if (= (imported_unwrap_or_u64_score) 4294967301u64) + (if (imported_ok_or_none_u64_ok) + (if (imported_ok_or_none_u64_none) + (if (imported_unwrap_or_string_ok) + (if (imported_ok_or_none_string_ok) + (if (imported_ok_or_none_string_none) + (if (= (imported_unwrap_or_f64_score) 15.0) + (if (imported_ok_or_none_f64_ok) + (if (imported_ok_or_none_f64_none) + (if (imported_bool_result_helpers_ok) + (if (imported_ok_or_none_bool_ok) + (imported_ok_or_none_bool_none) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn main () -> i32 + (if (imported_result_helpers_ok) + 42 + 1)) + +(test "explicit std result i32 wrappers" + (imported_i32_result_wrappers_ok)) + +(test "explicit std result err wrappers" + (imported_result_error_wrappers_ok)) + +(test "explicit std result unwrap_or i32" + (= (imported_unwrap_or_i32_score) 47)) + +(test "explicit std result ok_or_none i32 ok" + (imported_ok_or_none_i32_ok)) + +(test "explicit std result ok_or_none i32 none" + (imported_ok_or_none_i32_none)) + +(test "explicit std result u32 wrappers" + (imported_u32_result_wrappers_ok)) + +(test "explicit std result unwrap_or u32" + (= (imported_unwrap_or_u32_score) 47u32)) + +(test "explicit std result ok_or_none u32 ok" + (imported_ok_or_none_u32_ok)) + +(test "explicit std result ok_or_none u32 none" + (imported_ok_or_none_u32_none)) + +(test "explicit std result unwrap_or i64" + (= (imported_unwrap_or_i64_score) 65i64)) + +(test "explicit std result ok_or_none i64 ok" + (imported_ok_or_none_i64_ok)) + +(test "explicit std result ok_or_none i64 none" + (imported_ok_or_none_i64_none)) + +(test "explicit std result u64 wrappers" + (imported_u64_result_wrappers_ok)) + +(test "explicit std result unwrap_or u64" + (= (imported_unwrap_or_u64_score) 4294967301u64)) + +(test "explicit std result ok_or_none u64 ok" + (imported_ok_or_none_u64_ok)) + +(test "explicit std result ok_or_none u64 none" + (imported_ok_or_none_u64_none)) + +(test "explicit std result unwrap_or string" + (imported_unwrap_or_string_ok)) + +(test "explicit std result ok_or_none string ok" + (imported_ok_or_none_string_ok)) + +(test "explicit std result ok_or_none string none" + (imported_ok_or_none_string_none)) + +(test "explicit std result unwrap_or f64" + (= (imported_unwrap_or_f64_score) 15.0)) + +(test "explicit std result ok_or_none f64 ok" + (imported_ok_or_none_f64_ok)) + +(test "explicit std result ok_or_none f64 none" + (imported_ok_or_none_f64_none)) + +(test "explicit std result bool helpers" + (imported_bool_result_helpers_ok)) + +(test "explicit std result ok_or_none bool ok" + (imported_ok_or_none_bool_ok)) + +(test "explicit std result ok_or_none bool none" + (imported_ok_or_none_bool_none)) + +(test "explicit std result helpers all" + (= (main) 42)) diff --git a/docs/language/examples/projects/std-import-string/slovo.toml b/docs/language/examples/projects/std-import-string/slovo.toml new file mode 100644 index 0000000..af442f5 --- /dev/null +++ b/docs/language/examples/projects/std-import-string/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-import-string" +source_root = "src" +entry = "main" diff --git a/docs/language/examples/projects/std-import-string/src/main.slo b/docs/language/examples/projects/std-import-string/src/main.slo new file mode 100644 index 0000000..5ae4c9f --- /dev/null +++ b/docs/language/examples/projects/std-import-string/src/main.slo @@ -0,0 +1,196 @@ +(module main) + +(import std.string (len concat parse_i32_result parse_i32_option parse_u32_result parse_u32_option parse_i64_result parse_i64_option parse_u64_result parse_u64_option parse_f64_result parse_f64_option parse_bool_result parse_bool_option parse_i32_or_zero parse_u32_or_zero parse_i64_or_zero parse_u64_or_zero parse_f64_or_zero parse_bool_or_false parse_i32_or parse_u32_or parse_i64_or parse_u64_or parse_f64_or parse_bool_or)) + +(fn imported_string_concat () -> string + (concat "slo" "vo")) + +(fn imported_string_len_concat_score () -> i32 + (+ (len (imported_string_concat)) 37)) + +(fn imported_string_parse_result_ok () -> bool + (if (= (unwrap_ok (parse_i32_result "40")) 40) + (if (= (unwrap_ok (parse_u32_result "40")) 40u32) + (if (= (unwrap_ok (parse_i64_result "40")) 40i64) + (if (= (unwrap_ok (parse_u64_result "40")) 40u64) + (if (= (unwrap_ok (parse_f64_result "40.0")) 40.0) + (imported_string_parse_bool_ok) + false) + false) + false) + false) + false)) + +(fn imported_string_parse_bool_ok () -> bool + (if (unwrap_ok (parse_bool_result "true")) + (= (unwrap_err (parse_bool_result "TRUE")) 1) + false)) + +(fn imported_string_parse_options_ok () -> bool + (if (match (parse_i32_option "40") + ((some payload) + (= payload 40)) + ((none) + false)) + (if (match (parse_i32_option "bad") + ((some payload) + false) + ((none) + true)) + (if (match (parse_u32_option "40") + ((some payload) + (= payload 40u32)) + ((none) + false)) + (if (match (parse_u32_option "bad") + ((some payload) + false) + ((none) + true)) + (if (match (parse_i64_option "40") + ((some payload) + (= payload 40i64)) + ((none) + false)) + (if (match (parse_i64_option "bad") + ((some payload) + false) + ((none) + true)) + (if (match (parse_u64_option "40") + ((some payload) + (= payload 40u64)) + ((none) + false)) + (if (match (parse_u64_option "bad") + ((some payload) + false) + ((none) + true)) + (if (match (parse_f64_option "40.0") + ((some payload) + (= payload 40.0)) + ((none) + false)) + (if (match (parse_f64_option "bad") + ((some payload) + false) + ((none) + true)) + (if (match (parse_bool_option "true") + ((some payload) + payload) + ((none) + false)) + (match (parse_bool_option "bad") + ((some payload) + false) + ((none) + true)) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_string_parse_integer_fallbacks_ok () -> bool + (if (= (parse_i32_or_zero "40") 40) + (if (= (parse_i32_or_zero "bad") 0) + (if (= (parse_u32_or_zero "40") 40u32) + (if (= (parse_u32_or_zero "bad") 0u32) + (if (= (parse_i64_or_zero "40") 40i64) + (if (= (parse_i64_or_zero "bad") 0i64) + (if (= (parse_u64_or_zero "40") 40u64) + (= (parse_u64_or_zero "bad") 0u64) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_string_parse_float_bool_fallbacks_ok () -> bool + (if (= (parse_f64_or_zero "40.0") 40.0) + (if (= (parse_f64_or_zero "bad") 0.0) + (if (parse_bool_or_false "true") + (if (parse_bool_or_false "bad") + false + (if (parse_bool_or_false "false") + false + true)) + false) + false) + false)) + +(fn imported_string_parse_custom_fallbacks_ok () -> bool + (if (= (parse_i32_or "40" 7) 40) + (if (= (parse_i32_or "bad" 7) 7) + (if (= (parse_u32_or "40" 9u32) 40u32) + (if (= (parse_u32_or "bad" 9u32) 9u32) + (if (= (parse_i64_or "40" 9i64) 40i64) + (if (= (parse_i64_or "bad" 9i64) 9i64) + (if (= (parse_u64_or "40" 11u64) 40u64) + (if (= (parse_u64_or "bad" 11u64) 11u64) + (if (= (parse_f64_or "40.0" 1.5) 40.0) + (if (= (parse_f64_or "bad" 1.5) 1.5) + (if (parse_bool_or "true" false) + (if (parse_bool_or "bad" true) + true + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_string_helpers_ok () -> bool + (if (= (imported_string_len_concat_score) 42) + (if (imported_string_parse_result_ok) + (if (imported_string_parse_options_ok) + (if (imported_string_parse_integer_fallbacks_ok) + (if (imported_string_parse_float_bool_fallbacks_ok) + (imported_string_parse_custom_fallbacks_ok) + false) + false) + false) + false) + false)) + +(fn main () -> i32 + (if (imported_string_helpers_ok) + 42 + 1)) + +(test "explicit std string len concat" + (= (imported_string_len_concat_score) 42)) + +(test "explicit std string parse result wrappers" + (imported_string_parse_result_ok)) + +(test "explicit std string parse option wrappers" + (imported_string_parse_options_ok)) + +(test "explicit std string parse integer fallbacks" + (imported_string_parse_integer_fallbacks_ok)) + +(test "explicit std string parse float bool fallbacks" + (imported_string_parse_float_bool_fallbacks_ok)) + +(test "explicit std string parse custom fallbacks" + (imported_string_parse_custom_fallbacks_ok)) + +(test "explicit std string helpers all" + (= (main) 42)) diff --git a/docs/language/examples/projects/std-import-time/slovo.toml b/docs/language/examples/projects/std-import-time/slovo.toml new file mode 100644 index 0000000..ef9e4ca --- /dev/null +++ b/docs/language/examples/projects/std-import-time/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-import-time" +source_root = "src" +entry = "main" diff --git a/docs/language/examples/projects/std-import-time/src/main.slo b/docs/language/examples/projects/std-import-time/src/main.slo new file mode 100644 index 0000000..596149c --- /dev/null +++ b/docs/language/examples/projects/std-import-time/src/main.slo @@ -0,0 +1,28 @@ +(module main) + +(import std.time (monotonic_ms sleep_ms_zero)) + +(fn imported_time_monotonic_non_negative () -> bool + (>= (monotonic_ms) 0)) + +(fn imported_time_sleep_zero_score () -> i32 + (+ (sleep_ms_zero) 42)) + +(fn imported_time_facade_ok () -> bool + (if (imported_time_monotonic_non_negative) + (= (imported_time_sleep_zero_score) 42) + false)) + +(fn main () -> i32 + (if (imported_time_facade_ok) + 42 + 1)) + +(test "explicit std time monotonic facade" + (imported_time_monotonic_non_negative)) + +(test "explicit std time sleep zero facade" + (= (imported_time_sleep_zero_score) 42)) + +(test "explicit std time facade all" + (= (main) 42)) diff --git a/docs/language/examples/projects/std-import-vec_bool/slovo.toml b/docs/language/examples/projects/std-import-vec_bool/slovo.toml new file mode 100644 index 0000000..d071d06 --- /dev/null +++ b/docs/language/examples/projects/std-import-vec_bool/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-import-vec-bool" +source_root = "src" +entry = "main" diff --git a/docs/language/examples/projects/std-import-vec_bool/src/main.slo b/docs/language/examples/projects/std-import-vec_bool/src/main.slo new file mode 100644 index 0000000..b7af2d1 --- /dev/null +++ b/docs/language/examples/projects/std-import-vec_bool/src/main.slo @@ -0,0 +1,382 @@ +(module main) + +(import std.vec_bool (empty append len at singleton append2 append3 pair triple is_empty index_or first_or last_or index_option first_option last_option index_of_option last_index_of_option contains count_of concat take starts_with without_prefix ends_with without_suffix drop reverse subvec insert_at insert_range replace_at replace_range remove_at remove_range)) + +(fn imported_empty_length () -> i32 + (len (empty))) + +(fn imported_pair_index () -> bool + (at (pair true false) 1)) + +(fn original_len_after_append () -> i32 + (let values (vec bool) (empty)) + (let more (vec bool) (append values true)) + (len values)) + +(fn option_bool_is_some_value ((value (option bool)) (expected bool)) -> bool + (if (is_some value) + (= (unwrap_some value) expected) + false)) + +(fn option_i32_is_some_value ((value (option i32)) (expected i32)) -> bool + (if (is_some value) + (= (unwrap_some value) expected) + false)) + +(fn imported_builder_helpers_ok () -> bool + (if (= (len (singleton true)) 1) + (if (= (len (append2 (empty) true false)) 2) + (if (= (len (append3 (singleton true) false true false)) 4) + (if (= (pair true false) (append2 (empty) true false)) + (= (triple true false true) (append3 (empty) true false true)) + false) + false) + false) + false)) + +(fn imported_query_helpers_ok () -> bool + (if (is_empty (empty)) + (if (= (first_or (empty) true) true) + (if (= (first_or (pair false true) true) false) + (if (= (last_or (triple true false false) true) false) + (if (= (index_or (pair true false) -1 true) true) + (if (= (index_or (pair true false) 9 true) true) + (= (index_or (pair true false) 1 true) false) + false) + false) + false) + false) + false) + false)) + +(fn imported_option_query_helpers_ok () -> bool + (let repeated (vec bool) (append3 (pair true false) true false true)) + (if (is_none (first_option (empty))) + (if (option_bool_is_some_value (first_option (pair true false)) true) + (if (is_none (last_option (empty))) + (if (option_bool_is_some_value (last_option (triple true false false)) false) + (if (is_none (index_option (pair true false) -1)) + (if (is_none (index_option (pair true false) 9)) + (if (option_bool_is_some_value (index_option (pair true false) 1) false) + (if (is_none (index_of_option (empty) true)) + (if (option_i32_is_some_value (index_of_option repeated false) 1) + (if (is_none (last_index_of_option (empty) false)) + (option_i32_is_some_value (last_index_of_option repeated false) 3) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_starts_with_helper_ok () -> bool + (let values (vec bool) (append2 (triple true false true) false true)) + (let prefix (vec bool) (pair true false)) + (let longer_prefix (vec bool) (append2 values true false)) + (let mismatched_prefix (vec bool) (pair false true)) + (if (starts_with values (empty)) + (if (starts_with values prefix) + (if (starts_with values values) + (if (starts_with values longer_prefix) + false + (if (starts_with values mismatched_prefix) + false + (if (= values (append2 (triple true false true) false true)) + (= prefix (pair true false)) + false))) + false) + false) + false)) + +(fn imported_ends_with_helper_ok () -> bool + (let values (vec bool) (append2 (triple true false true) false true)) + (let suffix (vec bool) (pair false true)) + (let longer_suffix (vec bool) (append2 values true false)) + (let mismatched_suffix (vec bool) (pair false false)) + (if (ends_with values (empty)) + (if (ends_with values suffix) + (if (ends_with values values) + (if (ends_with values longer_suffix) + false + (if (ends_with values mismatched_suffix) + false + (if (= values (append2 (triple true false true) false true)) + (= suffix (pair false true)) + false))) + false) + false) + false)) + +(fn imported_without_suffix_helper_ok () -> bool + (let values (vec bool) (append2 (triple true false true) false true)) + (let suffix (vec bool) (pair false true)) + (let longer_suffix (vec bool) (append2 values true false)) + (let mismatched_suffix (vec bool) (pair false false)) + (if (= (without_suffix values (empty)) values) + (if (= (without_suffix values values) (empty)) + (if (= (without_suffix values suffix) (triple true false true)) + (if (= (without_suffix values longer_suffix) values) + (if (= (without_suffix values mismatched_suffix) values) + (if (= values (append2 (triple true false true) false true)) + (= suffix (pair false true)) + false) + false) + false) + false) + false) + false)) + +(fn imported_without_prefix_helper_ok () -> bool + (let values (vec bool) (append2 (triple true false true) false true)) + (let prefix (vec bool) (pair true false)) + (let longer_prefix (vec bool) (append2 values true false)) + (let mismatched_prefix (vec bool) (pair true true)) + (if (= (without_prefix values (empty)) values) + (if (= (without_prefix values values) (empty)) + (if (= (without_prefix values prefix) (triple true false true)) + (if (= (without_prefix values longer_prefix) values) + (if (= (without_prefix values mismatched_prefix) values) + (if (= values (append2 (triple true false true) false true)) + (= prefix (pair true false)) + false) + false) + false) + false) + false) + false)) + +(fn imported_transform_helpers_ok () -> bool + (if (= (concat (empty) (pair true false)) (pair true false)) + (if (= (concat (pair true false) (triple false true false)) (append3 (pair true false) false true false)) + (if (= (take (triple true false true) -4) (empty)) + (if (= (take (triple true false true) 2) (pair true false)) + (if (= (take (pair true false) 9) (pair true false)) + (if (= (drop (triple true false true) -4) (triple true false true)) + (if (= (drop (triple true false true) 2) (singleton true)) + (if (= (drop (pair true false) 9) (empty)) + (if (= (reverse (empty)) (empty)) + (= (reverse (triple true false false)) (triple false false true)) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_subvec_helper_ok () -> bool + (let original (vec bool) (append2 (triple true false true) false true)) + (if (= (subvec original -1 2) (empty)) + (if (= (subvec original 3 3) (empty)) + (if (= (subvec original 9 10) (empty)) + (if (= (subvec original 2 99) (triple true false true)) + (if (= (subvec original 1 4) (triple false true false)) + (= original (append2 (triple true false true) false true)) + false) + false) + false) + false) + false)) + +(fn imported_insert_helper_ok () -> bool + (let values (vec bool) (triple true false true)) + (if (= (insert_at values 1 false) (append2 (pair true false) false true)) + (if (= (insert_at values 3 false) (append values false)) + (if (= values (triple true false true)) + (if (= (insert_at values -1 false) values) + (= (insert_at values 4 false) values) + false) + false) + false) + false)) + +(fn imported_insert_range_helper_ok () -> bool + (let values (vec bool) (append2 (triple true false true) false true)) + (let inserted (vec bool) (pair false false)) + (if (= (insert_range values -1 inserted) values) + (if (= (insert_range values 2 inserted) (append3 (append2 (pair true false) false false) true false true)) + (if (= (insert_range values 5 inserted) (append2 (append2 (triple true false true) false true) false false)) + (if (= values (append2 (triple true false true) false true)) + (if (= inserted (pair false false)) + (= (insert_range values 6 inserted) values) + false) + false) + false) + false) + false)) + +(fn imported_replace_helper_ok () -> bool + (if (= (replace_at (triple true false true) 1 true) (triple true true true)) + (if (= (replace_at (triple true false true) -1 true) (triple true false true)) + (= (replace_at (pair true false) 2 true) (pair true false)) + false) + false)) + +(fn imported_replace_range_helper_ok () -> bool + (let original (vec bool) (append2 (triple true false true) false true)) + (let replacement (vec bool) (pair false false)) + (if (= (replace_range original -1 2 replacement) original) + (if (= (replace_range original 3 3 replacement) original) + (if (= (replace_range original 4 2 replacement) original) + (if (= (replace_range original 5 9 replacement) original) + (if (= (replace_range original 2 99 replacement) (append2 (pair true false) false false)) + (if (= (replace_range original 1 4 replacement) (append2 (pair true false) false true)) + (if (= original (append2 (triple true false true) false true)) + (= replacement (pair false false)) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_remove_helper_ok () -> bool + (let original (vec bool) (triple true false true)) + (let removed_middle (vec bool) (remove_at original 1)) + (if (= removed_middle (pair true true)) + (if (= (len removed_middle) 2) + (if (= (remove_at original 0) (pair false true)) + (if (= (remove_at original 2) (pair true false)) + (if (= original (triple true false true)) + (if (= (remove_at original -1) original) + (= (remove_at original 3) original) + false) + false) + false) + false) + false) + false)) + +(fn imported_remove_range_helper_ok () -> bool + (let original (vec bool) (append2 (triple true false true) false true)) + (if (= (remove_range original -1 2) original) + (if (= (remove_range original 3 3) original) + (if (= (remove_range original 4 2) original) + (if (= (remove_range original 5 9) original) + (if (= (remove_range original 2 99) (pair true false)) + (if (= (remove_range original 1 4) (pair true true)) + (= original (append2 (triple true false true) false true)) + false) + false) + false) + false) + false) + false)) + +(fn imported_real_program_helpers_ok () -> bool + (let values (vec bool) (append2 (triple true false true) true true)) + (if (= (original_len_after_append) 0) + (if (contains values true) + (if (contains values false) + (if (= (count_of values true) 4) + (= (count_of values false) 1) + false) + false) + false) + false)) + +(fn helpers_all_ok () -> bool + (if (= (imported_empty_length) 0) + (if (= (imported_pair_index) false) + (if (imported_builder_helpers_ok) + (if (imported_query_helpers_ok) + (if (imported_option_query_helpers_ok) + (if (imported_starts_with_helper_ok) + (if (imported_ends_with_helper_ok) + (if (imported_without_suffix_helper_ok) + (if (imported_without_prefix_helper_ok) + (if (imported_transform_helpers_ok) + (if (imported_subvec_helper_ok) + (if (imported_insert_helper_ok) + (if (imported_insert_range_helper_ok) + (if (imported_replace_helper_ok) + (if (imported_replace_range_helper_ok) + (if (imported_remove_helper_ok) + (if (imported_remove_range_helper_ok) + (imported_real_program_helpers_ok) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(test "explicit std vec_bool empty len facade" + (= (imported_empty_length) 0)) + +(test "explicit std vec_bool direct at facade" + (= (imported_pair_index) false)) + +(test "explicit std vec_bool builder helpers" + (imported_builder_helpers_ok)) + +(test "explicit std vec_bool query helpers" + (imported_query_helpers_ok)) + +(test "explicit std vec_bool option query helpers" + (imported_option_query_helpers_ok)) + +(test "explicit std vec_bool starts_with helper" + (imported_starts_with_helper_ok)) + +(test "explicit std vec_bool ends_with helper" + (imported_ends_with_helper_ok)) + +(test "explicit std vec_bool without_suffix helper" + (imported_without_suffix_helper_ok)) + +(test "explicit std vec_bool without_prefix helper" + (imported_without_prefix_helper_ok)) + +(test "explicit std vec_bool transform helpers" + (imported_transform_helpers_ok)) + +(test "explicit std vec_bool subvec helper" + (imported_subvec_helper_ok)) + +(test "explicit std vec_bool insert helper" + (imported_insert_helper_ok)) + +(test "explicit std vec_bool insert range helper" + (imported_insert_range_helper_ok)) + +(test "explicit std vec_bool replace helper" + (imported_replace_helper_ok)) + +(test "explicit std vec_bool replace range helper" + (imported_replace_range_helper_ok)) + +(test "explicit std vec_bool remove helper" + (imported_remove_helper_ok)) + +(test "explicit std vec_bool remove range helper" + (imported_remove_range_helper_ok)) + +(test "explicit std vec_bool real program helpers" + (imported_real_program_helpers_ok)) + +(test "explicit std vec_bool helpers all" + (helpers_all_ok)) + +(fn main () -> i32 + (if (helpers_all_ok) + 42 + 1)) diff --git a/docs/language/examples/projects/std-import-vec_f64/slovo.toml b/docs/language/examples/projects/std-import-vec_f64/slovo.toml new file mode 100644 index 0000000..6f6e906 --- /dev/null +++ b/docs/language/examples/projects/std-import-vec_f64/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-import-vec-f64" +source_root = "src" +entry = "main" diff --git a/docs/language/examples/projects/std-import-vec_f64/src/main.slo b/docs/language/examples/projects/std-import-vec_f64/src/main.slo new file mode 100644 index 0000000..e4a49dd --- /dev/null +++ b/docs/language/examples/projects/std-import-vec_f64/src/main.slo @@ -0,0 +1,379 @@ +(module main) + +(import std.vec_f64 (empty append len at singleton append2 append3 pair triple is_empty index_or first_or last_or index_option first_option last_option index_of_option last_index_of_option contains sum concat take starts_with without_prefix ends_with without_suffix drop reverse subvec insert_at insert_range replace_at replace_range remove_at remove_range)) + +(fn imported_empty_length () -> i32 + (len (empty))) + +(fn imported_pair_index () -> f64 + (at (pair 40.0 41.0) 1)) + +(fn original_len_after_append () -> i32 + (let values (vec f64) (empty)) + (let more (vec f64) (append values 9.0)) + (len values)) + +(fn option_f64_is_some_value ((value (option f64)) (expected f64)) -> bool + (if (is_some value) + (= (unwrap_some value) expected) + false)) + +(fn option_i32_is_some_value ((value (option i32)) (expected i32)) -> bool + (if (is_some value) + (= (unwrap_some value) expected) + false)) + +(fn imported_builder_helpers_ok () -> bool + (if (= (len (singleton 42.0)) 1) + (if (= (len (append2 (empty) 10.0 20.0)) 2) + (if (= (len (append3 (singleton 10.0) 20.0 30.0 40.0)) 4) + (if (= (pair 5.0 6.0) (append2 (empty) 5.0 6.0)) + (= (triple 7.0 8.0 9.0) (append3 (empty) 7.0 8.0 9.0)) + false) + false) + false) + false)) + +(fn imported_query_helpers_ok () -> bool + (if (is_empty (empty)) + (if (= (first_or (empty) 9.0) 9.0) + (if (= (first_or (pair 40.0 41.0) 9.0) 40.0) + (if (= (last_or (triple 10.0 20.0 30.0) 9.0) 30.0) + (if (= (index_or (pair 40.0 41.0) -1 7.0) 7.0) + (if (= (index_or (pair 40.0 41.0) 9 7.0) 7.0) + (= (index_or (pair 40.0 41.0) 1 7.0) 41.0) + false) + false) + false) + false) + false) + false)) + +(fn imported_option_query_helpers_ok () -> bool + (if (is_none (first_option (empty))) + (if (option_f64_is_some_value (first_option (pair 40.0 41.0)) 40.0) + (if (is_none (last_option (empty))) + (if (option_f64_is_some_value (last_option (triple 10.0 20.0 30.0)) 30.0) + (if (is_none (index_option (pair 40.0 41.0) -1)) + (if (is_none (index_option (pair 40.0 41.0) 9)) + (if (option_f64_is_some_value (index_option (pair 40.0 41.0) 1) 41.0) + (if (is_none (index_of_option (append3 (pair 10.0 20.0) 30.0 20.0 10.0) 99.0)) + (if (option_i32_is_some_value (index_of_option (append3 (pair 10.0 20.0) 30.0 20.0 10.0) 20.0) 1) + (if (is_none (last_index_of_option (append3 (pair 10.0 20.0) 30.0 20.0 10.0) 99.0)) + (option_i32_is_some_value (last_index_of_option (append3 (pair 10.0 20.0) 30.0 20.0 10.0) 20.0) 3) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_starts_with_helper_ok () -> bool + (let values (vec f64) (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + (let prefix (vec f64) (pair 10.0 20.0)) + (let longer_prefix (vec f64) (append2 values 60.0 70.0)) + (let mismatched_prefix (vec f64) (pair 20.0 30.0)) + (if (starts_with values (empty)) + (if (starts_with values prefix) + (if (starts_with values values) + (if (starts_with values longer_prefix) + false + (if (starts_with values mismatched_prefix) + false + (if (= values (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + (= prefix (pair 10.0 20.0)) + false))) + false) + false) + false)) + +(fn imported_ends_with_helper_ok () -> bool + (let values (vec f64) (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + (let suffix (vec f64) (pair 40.0 50.0)) + (let longer_suffix (vec f64) (append2 values 60.0 70.0)) + (let mismatched_suffix (vec f64) (pair 40.0 51.0)) + (if (ends_with values (empty)) + (if (ends_with values suffix) + (if (ends_with values values) + (if (ends_with values longer_suffix) + false + (if (ends_with values mismatched_suffix) + false + (if (= values (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + (= suffix (pair 40.0 50.0)) + false))) + false) + false) + false)) + +(fn imported_without_suffix_helper_ok () -> bool + (let values (vec f64) (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + (let suffix (vec f64) (pair 40.0 50.0)) + (let longer_suffix (vec f64) (append2 values 60.0 70.0)) + (let mismatched_suffix (vec f64) (pair 40.0 51.0)) + (if (= (without_suffix values (empty)) values) + (if (= (without_suffix values values) (empty)) + (if (= (without_suffix values suffix) (triple 10.0 20.0 30.0)) + (if (= (without_suffix values longer_suffix) values) + (if (= (without_suffix values mismatched_suffix) values) + (if (= values (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + (= suffix (pair 40.0 50.0)) + false) + false) + false) + false) + false) + false)) + +(fn imported_without_prefix_helper_ok () -> bool + (let values (vec f64) (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + (let prefix (vec f64) (pair 10.0 20.0)) + (let longer_prefix (vec f64) (append2 values 60.0 70.0)) + (let mismatched_prefix (vec f64) (pair 10.0 21.0)) + (if (= (without_prefix values (empty)) values) + (if (= (without_prefix values values) (empty)) + (if (= (without_prefix values prefix) (triple 30.0 40.0 50.0)) + (if (= (without_prefix values longer_prefix) values) + (if (= (without_prefix values mismatched_prefix) values) + (if (= values (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + (= prefix (pair 10.0 20.0)) + false) + false) + false) + false) + false) + false)) + +(fn imported_transform_helpers_ok () -> bool + (if (= (concat (empty) (pair 7.0 8.0)) (pair 7.0 8.0)) + (if (= (concat (pair 1.0 2.0) (triple 3.0 4.0 5.0)) (append3 (pair 1.0 2.0) 3.0 4.0 5.0)) + (if (= (take (triple 10.0 20.0 30.0) -4) (empty)) + (if (= (take (triple 10.0 20.0 30.0) 2) (pair 10.0 20.0)) + (if (= (take (pair 10.0 20.0) 9) (pair 10.0 20.0)) + (if (= (drop (triple 10.0 20.0 30.0) -4) (triple 10.0 20.0 30.0)) + (if (= (drop (triple 10.0 20.0 30.0) 2) (singleton 30.0)) + (if (= (drop (pair 10.0 20.0) 9) (empty)) + (if (= (reverse (empty)) (empty)) + (= (reverse (append3 (pair 10.0 20.0) 30.0 40.0 50.0)) (append3 (pair 50.0 40.0) 30.0 20.0 10.0)) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_subvec_helper_ok () -> bool + (let original (vec f64) (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + (if (= (subvec original -1 2) (empty)) + (if (= (subvec original 3 3) (empty)) + (if (= (subvec original 9 10) (empty)) + (if (= (subvec original 2 99) (triple 30.0 40.0 50.0)) + (if (= (subvec original 1 4) (triple 20.0 30.0 40.0)) + (= original (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + false) + false) + false) + false) + false)) + +(fn imported_insert_helper_ok () -> bool + (let values (vec f64) (triple 10.0 20.0 30.0)) + (if (= (insert_at values 1 99.0) (append2 (pair 10.0 99.0) 20.0 30.0)) + (if (= (insert_at values 3 99.0) (append values 99.0)) + (if (= values (triple 10.0 20.0 30.0)) + (if (= (insert_at values -1 99.0) values) + (= (insert_at values 4 99.0) values) + false) + false) + false) + false)) + +(fn imported_insert_range_helper_ok () -> bool + (let values (vec f64) (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + (let inserted (vec f64) (pair 77.0 88.0)) + (if (= (insert_range values -1 inserted) values) + (if (= (insert_range values 2 inserted) (append3 (append2 (pair 10.0 20.0) 77.0 88.0) 30.0 40.0 50.0)) + (if (= (insert_range values 5 inserted) (append2 (append2 (triple 10.0 20.0 30.0) 40.0 50.0) 77.0 88.0)) + (if (= values (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + (if (= inserted (pair 77.0 88.0)) + (= (insert_range values 6 inserted) values) + false) + false) + false) + false) + false)) + +(fn imported_replace_helper_ok () -> bool + (if (= (replace_at (triple 10.0 20.0 30.0) 1 99.0) (triple 10.0 99.0 30.0)) + (if (= (replace_at (triple 10.0 20.0 30.0) -1 99.0) (triple 10.0 20.0 30.0)) + (= (replace_at (pair 10.0 20.0) 2 99.0) (pair 10.0 20.0)) + false) + false)) + +(fn imported_replace_range_helper_ok () -> bool + (let original (vec f64) (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + (let replacement (vec f64) (pair 77.0 88.0)) + (if (= (replace_range original -1 2 replacement) original) + (if (= (replace_range original 3 3 replacement) original) + (if (= (replace_range original 4 2 replacement) original) + (if (= (replace_range original 5 9 replacement) original) + (if (= (replace_range original 2 99 replacement) (append2 (pair 10.0 20.0) 77.0 88.0)) + (if (= (replace_range original 1 4 replacement) (append2 (pair 10.0 77.0) 88.0 50.0)) + (if (= original (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + (= replacement (pair 77.0 88.0)) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_remove_helper_ok () -> bool + (let original (vec f64) (triple 10.0 20.0 30.0)) + (let removed_middle (vec f64) (remove_at original 1)) + (if (= removed_middle (pair 10.0 30.0)) + (if (= (len removed_middle) 2) + (if (= (remove_at original 0) (pair 20.0 30.0)) + (if (= (remove_at original 2) (pair 10.0 20.0)) + (if (= original (triple 10.0 20.0 30.0)) + (if (= (remove_at original -1) original) + (= (remove_at original 3) original) + false) + false) + false) + false) + false) + false)) + +(fn imported_remove_range_helper_ok () -> bool + (let original (vec f64) (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + (if (= (remove_range original -1 2) original) + (if (= (remove_range original 3 3) original) + (if (= (remove_range original 4 2) original) + (if (= (remove_range original 5 9) original) + (if (= (remove_range original 2 99) (pair 10.0 20.0)) + (if (= (remove_range original 1 4) (pair 10.0 50.0)) + (= original (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + false) + false) + false) + false) + false) + false)) + +(fn imported_real_program_helpers_ok () -> bool + (let values (vec f64) (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + (if (= (original_len_after_append) 0) + (if (contains values 20.0) + (if (contains values 99.0) + false + (= (sum values) 150.0)) + false) + false)) + +(fn helpers_all_ok () -> bool + (if (= (imported_empty_length) 0) + (if (= (imported_pair_index) 41.0) + (if (imported_builder_helpers_ok) + (if (imported_query_helpers_ok) + (if (imported_option_query_helpers_ok) + (if (imported_starts_with_helper_ok) + (if (imported_ends_with_helper_ok) + (if (imported_without_suffix_helper_ok) + (if (imported_without_prefix_helper_ok) + (if (imported_transform_helpers_ok) + (if (imported_subvec_helper_ok) + (if (imported_insert_helper_ok) + (if (imported_insert_range_helper_ok) + (if (imported_replace_helper_ok) + (if (imported_replace_range_helper_ok) + (if (imported_remove_helper_ok) + (if (imported_remove_range_helper_ok) + (imported_real_program_helpers_ok) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(test "explicit std vec_f64 empty len facade" + (= (imported_empty_length) 0)) + +(test "explicit std vec_f64 direct at facade" + (= (imported_pair_index) 41.0)) + +(test "explicit std vec_f64 builder helpers" + (imported_builder_helpers_ok)) + +(test "explicit std vec_f64 query helpers" + (imported_query_helpers_ok)) + +(test "explicit std vec_f64 option query helpers" + (imported_option_query_helpers_ok)) + +(test "explicit std vec_f64 starts_with helper" + (imported_starts_with_helper_ok)) + +(test "explicit std vec_f64 ends_with helper" + (imported_ends_with_helper_ok)) + +(test "explicit std vec_f64 without_suffix helper" + (imported_without_suffix_helper_ok)) + +(test "explicit std vec_f64 without_prefix helper" + (imported_without_prefix_helper_ok)) + +(test "explicit std vec_f64 transform helpers" + (imported_transform_helpers_ok)) + +(test "explicit std vec_f64 subvec helper" + (imported_subvec_helper_ok)) + +(test "explicit std vec_f64 insert helper" + (imported_insert_helper_ok)) + +(test "explicit std vec_f64 insert range helper" + (imported_insert_range_helper_ok)) + +(test "explicit std vec_f64 replace helper" + (imported_replace_helper_ok)) + +(test "explicit std vec_f64 replace range helper" + (imported_replace_range_helper_ok)) + +(test "explicit std vec_f64 remove helper" + (imported_remove_helper_ok)) + +(test "explicit std vec_f64 remove range helper" + (imported_remove_range_helper_ok)) + +(test "explicit std vec_f64 real program helpers" + (imported_real_program_helpers_ok)) + +(test "explicit std vec_f64 helpers all" + (helpers_all_ok)) + +(fn main () -> i32 + (if (helpers_all_ok) + 42 + 1)) diff --git a/docs/language/examples/projects/std-import-vec_i32/slovo.toml b/docs/language/examples/projects/std-import-vec_i32/slovo.toml new file mode 100644 index 0000000..679ebd8 --- /dev/null +++ b/docs/language/examples/projects/std-import-vec_i32/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-import-vec-i32" +source_root = "src" +entry = "main" diff --git a/docs/language/examples/projects/std-import-vec_i32/src/main.slo b/docs/language/examples/projects/std-import-vec_i32/src/main.slo new file mode 100644 index 0000000..6546e21 --- /dev/null +++ b/docs/language/examples/projects/std-import-vec_i32/src/main.slo @@ -0,0 +1,415 @@ +(module main) + +(import std.vec_i32 (empty append len at singleton append2 append3 pair triple repeat range range_from_zero is_empty index_or first_or last_or index_option first_option last_option index_of_option last_index_of_option count_of contains sum concat take starts_with without_prefix ends_with without_suffix drop reverse subvec insert_at insert_range replace_at replace_range remove_at remove_range)) + +(fn imported_empty_length () -> i32 + (len (empty))) + +(fn imported_pair_index () -> i32 + (at (pair 40 41) 1)) + +(fn imported_builder_helpers_ok () -> bool + (if (= (len (singleton 42)) 1) + (if (= (len (append2 (empty) 10 20)) 2) + (if (= (len (append3 (singleton 10) 20 30 40)) 4) + (= (pair 5 6) (append2 (empty) 5 6)) + false) + false) + false)) + +(fn imported_constructor_helpers_ok () -> bool + (if (= (repeat 7 -4) (empty)) + (if (= (repeat 7 0) (empty)) + (if (= (repeat 7 3) (triple 7 7 7)) + (if (= (range_from_zero -1) (empty)) + (if (= (range_from_zero 0) (empty)) + (if (= (range_from_zero 4) (append (triple 0 1 2) 3)) + (= (range_from_zero 2) (pair 0 1)) + false) + false) + false) + false) + false) + false)) + +(fn imported_range_helpers_ok () -> bool + (if (= (range -3 2) (append3 (pair -3 -2) -1 0 1)) + (if (= (range 3 7) (append2 (pair 3 4) 5 6)) + (if (= (range -2 -2) (empty)) + (= (range 7 3) (empty)) + false) + false) + false)) + +(fn option_is_some_value ((value (option i32)) (expected i32)) -> bool + (if (is_some value) + (= (unwrap_some value) expected) + false)) + +(fn imported_option_query_helpers_ok () -> bool + (if (is_none (first_option (empty))) + (if (option_is_some_value (first_option (pair 40 41)) 40) + (if (is_none (last_option (empty))) + (if (option_is_some_value (last_option (triple 10 20 30)) 30) + (if (is_none (index_option (pair 40 41) -1)) + (if (is_none (index_option (pair 40 41) 9)) + (option_is_some_value (index_option (pair 40 41) 1) 41) + false) + false) + false) + false) + false) + false)) + +(fn imported_query_helpers_ok () -> bool + (if (is_empty (empty)) + (if (= (first_or (empty) 9) 9) + (if (= (first_or (pair 40 41) 9) 40) + (if (= (last_or (triple 10 20 30) 9) 30) + (if (= (index_or (pair 40 41) -1 7) 7) + (if (= (index_or (pair 40 41) 9 7) 7) + (if (= (index_or (pair 40 41) 1 7) 41) + (if (is_none (first_option (empty))) + (if (option_is_some_value (first_option (pair 40 41)) 40) + (if (option_is_some_value (last_option (triple 10 20 30)) 30) + (if (is_none (index_option (pair 40 41) -1)) + (if (is_none (index_option (pair 40 41) 9)) + (option_is_some_value (index_option (pair 40 41) 1) 41) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_starts_with_helper_ok () -> bool + (let values (vec i32) (append2 (triple 10 20 30) 40 50)) + (let prefix (vec i32) (pair 10 20)) + (let longer_prefix (vec i32) (append2 values 60 70)) + (let mismatched_prefix (vec i32) (pair 20 30)) + (if (starts_with values (empty)) + (if (starts_with values prefix) + (if (starts_with values values) + (if (starts_with values longer_prefix) + false + (if (starts_with values mismatched_prefix) + false + (if (= values (append2 (triple 10 20 30) 40 50)) + (= prefix (pair 10 20)) + false))) + false) + false) + false)) + +(fn imported_ends_with_helper_ok () -> bool + (let values (vec i32) (append2 (triple 10 20 30) 40 50)) + (let suffix (vec i32) (pair 40 50)) + (let longer_suffix (vec i32) (append2 values 60 70)) + (let mismatched_suffix (vec i32) (pair 40 51)) + (if (ends_with values (empty)) + (if (ends_with values suffix) + (if (ends_with values values) + (if (ends_with values longer_suffix) + false + (if (ends_with values mismatched_suffix) + false + (if (= values (append2 (triple 10 20 30) 40 50)) + (= suffix (pair 40 50)) + false))) + false) + false) + false)) + +(fn imported_without_suffix_helper_ok () -> bool + (let values (vec i32) (append2 (triple 10 20 30) 40 50)) + (let suffix (vec i32) (pair 40 50)) + (let longer_suffix (vec i32) (append2 values 60 70)) + (let mismatched_suffix (vec i32) (pair 40 51)) + (if (= (without_suffix values (empty)) values) + (if (= (without_suffix values values) (empty)) + (if (= (without_suffix values suffix) (triple 10 20 30)) + (if (= (without_suffix values longer_suffix) values) + (if (= (without_suffix values mismatched_suffix) values) + (if (= values (append2 (triple 10 20 30) 40 50)) + (= suffix (pair 40 50)) + false) + false) + false) + false) + false) + false)) + +(fn imported_without_prefix_helper_ok () -> bool + (let values (vec i32) (append2 (triple 10 20 30) 40 50)) + (let prefix (vec i32) (pair 10 20)) + (let longer_prefix (vec i32) (append2 values 60 70)) + (let mismatched_prefix (vec i32) (pair 10 21)) + (if (= (without_prefix values (empty)) values) + (if (= (without_prefix values values) (empty)) + (if (= (without_prefix values prefix) (triple 30 40 50)) + (if (= (without_prefix values longer_prefix) values) + (if (= (without_prefix values mismatched_prefix) values) + (if (= values (append2 (triple 10 20 30) 40 50)) + (= prefix (pair 10 20)) + false) + false) + false) + false) + false) + false)) + +(fn imported_transform_helpers_ok () -> bool + (if (= (concat (empty) (pair 7 8)) (pair 7 8)) + (if (= (concat (pair 1 2) (triple 3 4 5)) (append3 (pair 1 2) 3 4 5)) + (if (= (take (triple 10 20 30) -4) (empty)) + (if (= (take (triple 10 20 30) 2) (pair 10 20)) + (if (= (take (pair 10 20) 9) (pair 10 20)) + (if (= (drop (triple 10 20 30) -4) (triple 10 20 30)) + (if (= (drop (triple 10 20 30) 2) (singleton 30)) + (if (= (drop (pair 10 20) 9) (empty)) + (if (= (reverse (empty)) (empty)) + (= (reverse (append3 (pair 10 20) 30 40 50)) (append3 (pair 50 40) 30 20 10)) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_subvec_helper_ok () -> bool + (let values (vec i32) (append (triple 10 20 30) 40)) + (if (= (subvec values 1 3) (pair 20 30)) + (if (= (subvec values 2 9) (pair 30 40)) + (if (= (subvec values 2 2) (empty)) + (if (= (subvec values 4 9) (empty)) + (if (= (subvec values -1 2) (empty)) + (= values (append (triple 10 20 30) 40)) + false) + false) + false) + false) + false)) + +(fn imported_insert_helper_ok () -> bool + (let values (vec i32) (triple 10 20 30)) + (if (= (insert_at values 1 99) (append2 (pair 10 99) 20 30)) + (if (= (insert_at values 3 99) (append values 99)) + (if (= values (triple 10 20 30)) + (if (= (insert_at values -1 99) values) + (= (insert_at values 4 99) values) + false) + false) + false) + false)) + +(fn imported_insert_range_helper_ok () -> bool + (let values (vec i32) (triple 10 20 30)) + (let inserted (vec i32) (pair 70 80)) + (if (= (insert_range values 1 inserted) (append3 (pair 10 70) 80 20 30)) + (if (= (len (insert_range values 1 inserted)) (+ (len values) (len inserted))) + (if (= (insert_range values 3 inserted) (append2 values 70 80)) + (if (= values (triple 10 20 30)) + (if (= inserted (pair 70 80)) + (if (= (insert_range values -1 inserted) values) + (= (insert_range values 4 inserted) values) + false) + false) + false) + false) + false) + false)) + +(fn imported_replace_helper_ok () -> bool + (if (= (replace_at (triple 10 20 30) 1 99) (triple 10 99 30)) + (if (= (replace_at (triple 10 20 30) -1 99) (triple 10 20 30)) + (= (replace_at (pair 10 20) 2 99) (pair 10 20)) + false) + false)) + +(fn imported_replace_range_helper_ok () -> bool + (let values (vec i32) (append2 (triple 10 20 30) 40 50)) + (let replacement (vec i32) (pair 77 88)) + (if (= (replace_range values 1 3 replacement) (append3 (pair 10 77) 88 40 50)) + (if (= (replace_range values 3 99 replacement) (append2 (triple 10 20 30) 77 88)) + (if (= (replace_range values 2 2 replacement) values) + (if (= (replace_range values 5 7 replacement) values) + (if (= (replace_range values -1 2 replacement) values) + (if (= values (append2 (triple 10 20 30) 40 50)) + (= replacement (pair 77 88)) + false) + false) + false) + false) + false) + false)) + +(fn imported_remove_helper_ok () -> bool + (if (= (remove_at (triple 10 20 30) 1) (pair 10 30)) + (if (= (remove_at (triple 10 20 30) -1) (triple 10 20 30)) + (= (remove_at (pair 10 20) 2) (pair 10 20)) + false) + false)) + +(fn imported_remove_range_helper_ok () -> bool + (let values (vec i32) (append2 (triple 10 20 30) 40 50)) + (if (= (remove_range values 1 3) (append2 (singleton 10) 40 50)) + (if (= (remove_range values 3 5) (triple 10 20 30)) + (if (= (remove_range values 2 2) values) + (if (= (remove_range values 5 7) values) + (if (= (remove_range values -1 2) values) + (= values (append2 (triple 10 20 30) 40 50)) + false) + false) + false) + false) + false)) + +(fn imported_count_of_helper_ok () -> bool + (let values (vec i32) (append3 (pair 10 20) 10 30 10)) + (if (= (count_of (empty) 10) 0) + (if (= (count_of values 10) 3) + (if (= (count_of values 20) 1) + (if (= (count_of values 99) 0) + (= values (append3 (pair 10 20) 10 30 10)) + false) + false) + false) + false)) + +(fn imported_real_program_helpers_ok () -> bool + (if (contains (triple 10 20 30) 20) + (if (contains (triple 10 20 30) 99) + false + (if (option_is_some_value (index_of_option (append3 (pair 10 20) 30 20 10) 20) 1) + (if (option_is_some_value (last_index_of_option (append3 (pair 10 20) 30 20 10) 20) 3) + (if (is_none (index_of_option (append3 (pair 10 20) 30 20 10) 99)) + (if (is_none (last_index_of_option (append3 (pair 10 20) 30 20 10) 99)) + (= (sum (append3 (empty) 10 20 12)) 42) + false) + false) + false) + false)) + false)) + +(fn imported_vec_i32_helpers_ok () -> bool + (if (= (imported_empty_length) 0) + (if (= (imported_pair_index) 41) + (if (imported_builder_helpers_ok) + (if (imported_constructor_helpers_ok) + (if (imported_range_helpers_ok) + (if (imported_query_helpers_ok) + (if (imported_option_query_helpers_ok) + (if (imported_starts_with_helper_ok) + (if (imported_ends_with_helper_ok) + (if (imported_without_suffix_helper_ok) + (if (imported_without_prefix_helper_ok) + (if (imported_transform_helpers_ok) + (if (imported_subvec_helper_ok) + (if (imported_insert_helper_ok) + (if (imported_insert_range_helper_ok) + (if (imported_replace_helper_ok) + (if (imported_replace_range_helper_ok) + (if (imported_remove_helper_ok) + (if (imported_remove_range_helper_ok) + (if (imported_count_of_helper_ok) + (imported_real_program_helpers_ok) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn main () -> i32 + (if (imported_vec_i32_helpers_ok) + 42 + 1)) + +(test "explicit std vec_i32 empty len facade" + (= (imported_empty_length) 0)) + +(test "explicit std vec_i32 direct at facade" + (= (imported_pair_index) 41)) + +(test "explicit std vec_i32 builder helpers" + (imported_builder_helpers_ok)) + +(test "explicit std vec_i32 constructor helpers" + (imported_constructor_helpers_ok)) + +(test "explicit std vec_i32 range helper" + (imported_range_helpers_ok)) + +(test "explicit std vec_i32 query helpers" + (imported_query_helpers_ok)) + +(test "explicit std vec_i32 option query helpers" + (imported_option_query_helpers_ok)) + +(test "explicit std vec_i32 starts_with helper" + (imported_starts_with_helper_ok)) + +(test "explicit std vec_i32 ends_with helper" + (imported_ends_with_helper_ok)) + +(test "explicit std vec_i32 without_suffix helper" + (imported_without_suffix_helper_ok)) + +(test "explicit std vec_i32 without_prefix helper" + (imported_without_prefix_helper_ok)) + +(test "explicit std vec_i32 transform helpers" + (imported_transform_helpers_ok)) + +(test "explicit std vec_i32 subvec helper" + (imported_subvec_helper_ok)) + +(test "explicit std vec_i32 insert helper" + (imported_insert_helper_ok)) + +(test "explicit std vec_i32 insert range helper" + (imported_insert_range_helper_ok)) + +(test "explicit std vec_i32 replace helper" + (imported_replace_helper_ok)) + +(test "explicit std vec_i32 replace range helper" + (imported_replace_range_helper_ok)) + +(test "explicit std vec_i32 remove helper" + (imported_remove_helper_ok)) + +(test "explicit std vec_i32 remove range helper" + (imported_remove_range_helper_ok)) + +(test "explicit std vec_i32 count_of helper" + (imported_count_of_helper_ok)) + +(test "explicit std vec_i32 real program helpers" + (imported_real_program_helpers_ok)) + +(test "explicit std vec_i32 helpers all" + (= (main) 42)) diff --git a/docs/language/examples/projects/std-import-vec_i64/slovo.toml b/docs/language/examples/projects/std-import-vec_i64/slovo.toml new file mode 100644 index 0000000..4a8318b --- /dev/null +++ b/docs/language/examples/projects/std-import-vec_i64/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-import-vec-i64" +source_root = "src" +entry = "main" diff --git a/docs/language/examples/projects/std-import-vec_i64/src/main.slo b/docs/language/examples/projects/std-import-vec_i64/src/main.slo new file mode 100644 index 0000000..21e9b91 --- /dev/null +++ b/docs/language/examples/projects/std-import-vec_i64/src/main.slo @@ -0,0 +1,283 @@ +(module main) + +(import std.vec_i64 (empty append len at singleton append2 append3 pair triple is_empty index_or first_or last_or index_option first_option last_option index_of_option last_index_of_option contains sum concat take drop reverse subvec insert_at insert_range replace_at replace_range remove_at remove_range)) + +(fn imported_empty_length () -> i32 + (len (empty))) + +(fn imported_pair_index () -> i64 + (at (pair 40i64 41i64) 1)) + +(fn original_len_after_append () -> i32 + (let values (vec i64) (empty)) + (let more (vec i64) (append values 9i64)) + (len values)) + +(fn option_i64_is_some_value ((value (option i64)) (expected i64)) -> bool + (if (is_some value) + (= (unwrap_some value) expected) + false)) + +(fn option_i32_is_some_value ((value (option i32)) (expected i32)) -> bool + (if (is_some value) + (= (unwrap_some value) expected) + false)) + +(fn imported_builder_helpers_ok () -> bool + (if (= (len (singleton 42i64)) 1) + (if (= (len (append2 (empty) 10i64 20i64)) 2) + (if (= (len (append3 (singleton 10i64) 20i64 30i64 40i64)) 4) + (if (= (pair 5i64 6i64) (append2 (empty) 5i64 6i64)) + (= (triple 7i64 8i64 9i64) (append3 (empty) 7i64 8i64 9i64)) + false) + false) + false) + false)) + +(fn imported_query_helpers_ok () -> bool + (if (is_empty (empty)) + (if (= (first_or (empty) 9i64) 9i64) + (if (= (first_or (pair 40i64 41i64) 9i64) 40i64) + (if (= (last_or (triple 10i64 20i64 30i64) 9i64) 30i64) + (if (= (index_or (pair 40i64 41i64) -1 7i64) 7i64) + (if (= (index_or (pair 40i64 41i64) 9 7i64) 7i64) + (= (index_or (pair 40i64 41i64) 1 7i64) 41i64) + false) + false) + false) + false) + false) + false)) + +(fn imported_option_query_helpers_ok () -> bool + (if (is_none (first_option (empty))) + (if (option_i64_is_some_value (first_option (pair 40i64 41i64)) 40i64) + (if (is_none (last_option (empty))) + (if (option_i64_is_some_value (last_option (triple 10i64 20i64 30i64)) 30i64) + (if (is_none (index_option (pair 40i64 41i64) -1)) + (if (is_none (index_option (pair 40i64 41i64) 9)) + (if (option_i64_is_some_value (index_option (pair 40i64 41i64) 1) 41i64) + (if (is_none (index_of_option (append3 (pair 10i64 20i64) 30i64 20i64 10i64) 99i64)) + (if (option_i32_is_some_value (index_of_option (append3 (pair 10i64 20i64) 30i64 20i64 10i64) 20i64) 1) + (if (is_none (last_index_of_option (append3 (pair 10i64 20i64) 30i64 20i64 10i64) 99i64)) + (option_i32_is_some_value (last_index_of_option (append3 (pair 10i64 20i64) 30i64 20i64 10i64) 20i64) 3) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_transform_helpers_ok () -> bool + (if (= (concat (empty) (pair 7i64 8i64)) (pair 7i64 8i64)) + (if (= (concat (pair 1i64 2i64) (triple 3i64 4i64 5i64)) (append3 (pair 1i64 2i64) 3i64 4i64 5i64)) + (if (= (take (triple 10i64 20i64 30i64) -4) (empty)) + (if (= (take (triple 10i64 20i64 30i64) 2) (pair 10i64 20i64)) + (if (= (take (pair 10i64 20i64) 9) (pair 10i64 20i64)) + (if (= (drop (triple 10i64 20i64 30i64) -4) (triple 10i64 20i64 30i64)) + (if (= (drop (triple 10i64 20i64 30i64) 2) (singleton 30i64)) + (if (= (drop (pair 10i64 20i64) 9) (empty)) + (if (= (reverse (empty)) (empty)) + (= (reverse (append3 (pair 10i64 20i64) 30i64 40i64 50i64)) (append3 (pair 50i64 40i64) 30i64 20i64 10i64)) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_subvec_helper_ok () -> bool + (let original (vec i64) (append2 (triple 10i64 20i64 30i64) 40i64 50i64)) + (if (= (subvec original -1 2) (empty)) + (if (= (subvec original 3 3) (empty)) + (if (= (subvec original 9 10) (empty)) + (if (= (subvec original 2 99) (triple 30i64 40i64 50i64)) + (if (= (subvec original 1 4) (triple 20i64 30i64 40i64)) + (= original (append2 (triple 10i64 20i64 30i64) 40i64 50i64)) + false) + false) + false) + false) + false)) + +(fn imported_insert_helper_ok () -> bool + (let values (vec i64) (triple 10i64 20i64 30i64)) + (if (= (insert_at values 1 99i64) (append2 (pair 10i64 99i64) 20i64 30i64)) + (if (= (insert_at values 3 99i64) (append values 99i64)) + (if (= values (triple 10i64 20i64 30i64)) + (if (= (insert_at values -1 99i64) values) + (= (insert_at values 4 99i64) values) + false) + false) + false) + false)) + +(fn imported_insert_range_helper_ok () -> bool + (let values (vec i64) (append2 (triple 10i64 20i64 30i64) 40i64 50i64)) + (let inserted (vec i64) (pair 77i64 88i64)) + (if (= (insert_range values -1 inserted) values) + (if (= (insert_range values 2 inserted) (append3 (append2 (pair 10i64 20i64) 77i64 88i64) 30i64 40i64 50i64)) + (if (= (insert_range values 5 inserted) (append2 (append2 (triple 10i64 20i64 30i64) 40i64 50i64) 77i64 88i64)) + (if (= values (append2 (triple 10i64 20i64 30i64) 40i64 50i64)) + (if (= inserted (pair 77i64 88i64)) + (= (insert_range values 6 inserted) values) + false) + false) + false) + false) + false)) + +(fn imported_replace_helper_ok () -> bool + (if (= (replace_at (triple 10i64 20i64 30i64) 1 99i64) (triple 10i64 99i64 30i64)) + (if (= (replace_at (triple 10i64 20i64 30i64) -1 99i64) (triple 10i64 20i64 30i64)) + (= (replace_at (pair 10i64 20i64) 2 99i64) (pair 10i64 20i64)) + false) + false)) + +(fn imported_replace_range_helper_ok () -> bool + (let original (vec i64) (append2 (triple 10i64 20i64 30i64) 40i64 50i64)) + (let replacement (vec i64) (pair 77i64 88i64)) + (if (= (replace_range original -1 2 replacement) original) + (if (= (replace_range original 3 3 replacement) original) + (if (= (replace_range original 4 2 replacement) original) + (if (= (replace_range original 5 9 replacement) original) + (if (= (replace_range original 2 99 replacement) (append2 (pair 10i64 20i64) 77i64 88i64)) + (if (= (replace_range original 1 4 replacement) (append2 (pair 10i64 77i64) 88i64 50i64)) + (if (= original (append2 (triple 10i64 20i64 30i64) 40i64 50i64)) + (= replacement (pair 77i64 88i64)) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_remove_helper_ok () -> bool + (let original (vec i64) (triple 10i64 20i64 30i64)) + (let removed_middle (vec i64) (remove_at original 1)) + (if (= removed_middle (pair 10i64 30i64)) + (if (= (len removed_middle) 2) + (if (= (remove_at original 0) (pair 20i64 30i64)) + (if (= (remove_at original 2) (pair 10i64 20i64)) + (if (= original (triple 10i64 20i64 30i64)) + (if (= (remove_at original -1) original) + (= (remove_at original 3) original) + false) + false) + false) + false) + false) + false)) + +(fn imported_remove_range_helper_ok () -> bool + (let original (vec i64) (append2 (triple 10i64 20i64 30i64) 40i64 50i64)) + (if (= (remove_range original -1 2) original) + (if (= (remove_range original 3 3) original) + (if (= (remove_range original 4 2) original) + (if (= (remove_range original 5 9) original) + (if (= (remove_range original 2 99) (pair 10i64 20i64)) + (if (= (remove_range original 1 4) (pair 10i64 50i64)) + (= original (append2 (triple 10i64 20i64 30i64) 40i64 50i64)) + false) + false) + false) + false) + false) + false)) + +(fn imported_real_program_helpers_ok () -> bool + (let values (vec i64) (append2 (triple 10i64 20i64 30i64) 40i64 50i64)) + (if (= (original_len_after_append) 0) + (if (contains values 20i64) + (if (contains values 99i64) + false + (= (sum values) 150i64)) + false) + false)) + +(fn helpers_all_ok () -> bool + (if (= (imported_empty_length) 0) + (if (= (imported_pair_index) 41i64) + (if (imported_builder_helpers_ok) + (if (imported_query_helpers_ok) + (if (imported_option_query_helpers_ok) + (if (imported_transform_helpers_ok) + (if (imported_subvec_helper_ok) + (if (imported_insert_helper_ok) + (if (imported_insert_range_helper_ok) + (if (imported_replace_helper_ok) + (if (imported_replace_range_helper_ok) + (if (imported_remove_helper_ok) + (if (imported_remove_range_helper_ok) + (imported_real_program_helpers_ok) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(test "explicit std vec_i64 empty len facade" + (= (imported_empty_length) 0)) + +(test "explicit std vec_i64 direct at facade" + (= (imported_pair_index) 41i64)) + +(test "explicit std vec_i64 builder helpers" + (imported_builder_helpers_ok)) + +(test "explicit std vec_i64 query helpers" + (imported_query_helpers_ok)) + +(test "explicit std vec_i64 option query helpers" + (imported_option_query_helpers_ok)) + +(test "explicit std vec_i64 transform helpers" + (imported_transform_helpers_ok)) + +(test "explicit std vec_i64 subvec helper" + (imported_subvec_helper_ok)) + +(test "explicit std vec_i64 insert helper" + (imported_insert_helper_ok)) + +(test "explicit std vec_i64 insert range helper" + (imported_insert_range_helper_ok)) + +(test "explicit std vec_i64 replace helper" + (imported_replace_helper_ok)) + +(test "explicit std vec_i64 replace range helper" + (imported_replace_range_helper_ok)) + +(test "explicit std vec_i64 remove helper" + (imported_remove_helper_ok)) + +(test "explicit std vec_i64 remove range helper" + (imported_remove_range_helper_ok)) + +(test "explicit std vec_i64 real program helpers" + (imported_real_program_helpers_ok)) + +(test "explicit std vec_i64 helpers all" + (helpers_all_ok)) + +(fn main () -> i32 + (if (helpers_all_ok) + 0 + 1)) diff --git a/docs/language/examples/projects/std-import-vec_string/slovo.toml b/docs/language/examples/projects/std-import-vec_string/slovo.toml new file mode 100644 index 0000000..2be8303 --- /dev/null +++ b/docs/language/examples/projects/std-import-vec_string/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-import-vec-string" +source_root = "src" +entry = "main" diff --git a/docs/language/examples/projects/std-import-vec_string/src/main.slo b/docs/language/examples/projects/std-import-vec_string/src/main.slo new file mode 100644 index 0000000..7fc5011 --- /dev/null +++ b/docs/language/examples/projects/std-import-vec_string/src/main.slo @@ -0,0 +1,381 @@ +(module main) + +(import std.vec_string (empty append len at singleton append2 append3 pair triple is_empty index_or first_or last_or index_option first_option last_option index_of_option last_index_of_option contains count_of concat take starts_with without_prefix ends_with without_suffix drop reverse subvec insert_at insert_range replace_at replace_range remove_at remove_range)) + +(fn imported_empty_length () -> i32 + (len (empty))) + +(fn imported_pair_index () -> string + (at (pair "slovo" "tree") 1)) + +(fn original_len_after_append () -> i32 + (let values (vec string) (empty)) + (let more (vec string) (append values "branch")) + (len values)) + +(fn option_string_is_some_value ((value (option string)) (expected string)) -> bool + (if (is_some value) + (= (unwrap_some value) expected) + false)) + +(fn option_i32_is_some_value ((value (option i32)) (expected i32)) -> bool + (if (is_some value) + (= (unwrap_some value) expected) + false)) + +(fn imported_builder_helpers_ok () -> bool + (if (= (len (singleton "seed")) 1) + (if (= (len (append2 (empty) "alpha" "beta")) 2) + (if (= (len (append3 (singleton "alpha") "beta" "gamma" "delta")) 4) + (if (= (pair "left" "right") (append2 (empty) "left" "right")) + (= (triple "red" "green" "blue") (append3 (empty) "red" "green" "blue")) + false) + false) + false) + false)) + +(fn imported_query_helpers_ok () -> bool + (if (is_empty (empty)) + (if (= (first_or (empty) "fallback") "fallback") + (if (= (first_or (pair "alpha" "beta") "fallback") "alpha") + (if (= (last_or (triple "red" "green" "blue") "fallback") "blue") + (if (= (index_or (pair "alpha" "beta") -1 "fallback") "fallback") + (if (= (index_or (pair "alpha" "beta") 9 "fallback") "fallback") + (= (index_or (pair "alpha" "beta") 1 "fallback") "beta") + false) + false) + false) + false) + false) + false)) + +(fn imported_option_query_helpers_ok () -> bool + (if (is_none (first_option (empty))) + (if (option_string_is_some_value (first_option (pair "alpha" "beta")) "alpha") + (if (is_none (last_option (empty))) + (if (option_string_is_some_value (last_option (triple "red" "green" "blue")) "blue") + (if (is_none (index_option (pair "alpha" "beta") -1)) + (if (is_none (index_option (pair "alpha" "beta") 9)) + (if (option_string_is_some_value (index_option (pair "alpha" "beta") 1) "beta") + (if (is_none (index_of_option (append3 (pair "oak" "pine") "birch" "pine" "oak") "cedar")) + (if (option_i32_is_some_value (index_of_option (append3 (pair "oak" "pine") "birch" "pine" "oak") "pine") 1) + (if (is_none (last_index_of_option (append3 (pair "oak" "pine") "birch" "pine" "oak") "cedar")) + (option_i32_is_some_value (last_index_of_option (append3 (pair "oak" "pine") "birch" "pine" "oak") "pine") 3) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_starts_with_helper_ok () -> bool + (let values (vec string) (append2 (triple "oak" "pine" "birch") "cedar" "maple")) + (let prefix (vec string) (pair "oak" "pine")) + (let longer_prefix (vec string) (append2 values "spruce" "elm")) + (let mismatched_prefix (vec string) (pair "pine" "birch")) + (if (starts_with values (empty)) + (if (starts_with values prefix) + (if (starts_with values values) + (if (starts_with values longer_prefix) + false + (if (starts_with values mismatched_prefix) + false + (if (= values (append2 (triple "oak" "pine" "birch") "cedar" "maple")) + (= prefix (pair "oak" "pine")) + false))) + false) + false) + false)) + +(fn imported_ends_with_helper_ok () -> bool + (let values (vec string) (append2 (triple "oak" "pine" "birch") "cedar" "maple")) + (let suffix (vec string) (pair "cedar" "maple")) + (let longer_suffix (vec string) (append2 values "spruce" "elm")) + (let mismatched_suffix (vec string) (pair "cedar" "elm")) + (if (ends_with values (empty)) + (if (ends_with values suffix) + (if (ends_with values values) + (if (ends_with values longer_suffix) + false + (if (ends_with values mismatched_suffix) + false + (if (= values (append2 (triple "oak" "pine" "birch") "cedar" "maple")) + (= suffix (pair "cedar" "maple")) + false))) + false) + false) + false)) + +(fn imported_without_suffix_helper_ok () -> bool + (let values (vec string) (append2 (triple "oak" "pine" "birch") "cedar" "maple")) + (let suffix (vec string) (pair "cedar" "maple")) + (let longer_suffix (vec string) (append2 values "spruce" "elm")) + (let mismatched_suffix (vec string) (pair "cedar" "elm")) + (if (= (without_suffix values (empty)) values) + (if (= (without_suffix values values) (empty)) + (if (= (without_suffix values suffix) (triple "oak" "pine" "birch")) + (if (= (without_suffix values longer_suffix) values) + (if (= (without_suffix values mismatched_suffix) values) + (if (= values (append2 (triple "oak" "pine" "birch") "cedar" "maple")) + (= suffix (pair "cedar" "maple")) + false) + false) + false) + false) + false) + false)) + +(fn imported_without_prefix_helper_ok () -> bool + (let values (vec string) (append2 (triple "oak" "pine" "birch") "cedar" "maple")) + (let prefix (vec string) (pair "oak" "pine")) + (let longer_prefix (vec string) (append2 values "spruce" "elm")) + (let mismatched_prefix (vec string) (pair "oak" "birch")) + (if (= (without_prefix values (empty)) values) + (if (= (without_prefix values values) (empty)) + (if (= (without_prefix values prefix) (triple "birch" "cedar" "maple")) + (if (= (without_prefix values longer_prefix) values) + (if (= (without_prefix values mismatched_prefix) values) + (if (= values (append2 (triple "oak" "pine" "birch") "cedar" "maple")) + (= prefix (pair "oak" "pine")) + false) + false) + false) + false) + false) + false)) + +(fn imported_transform_helpers_ok () -> bool + (if (= (concat (empty) (pair "oak" "pine")) (pair "oak" "pine")) + (if (= (concat (pair "a" "b") (triple "c" "d" "e")) (append3 (pair "a" "b") "c" "d" "e")) + (if (= (take (triple "red" "green" "blue") -4) (empty)) + (if (= (take (triple "red" "green" "blue") 2) (pair "red" "green")) + (if (= (take (pair "red" "green") 9) (pair "red" "green")) + (if (= (drop (triple "red" "green" "blue") -4) (triple "red" "green" "blue")) + (if (= (drop (triple "red" "green" "blue") 2) (singleton "blue")) + (if (= (drop (pair "red" "green") 9) (empty)) + (if (= (reverse (empty)) (empty)) + (= (reverse (append3 (pair "oak" "pine") "birch" "cedar" "maple")) (append3 (pair "maple" "cedar") "birch" "pine" "oak")) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_subvec_helper_ok () -> bool + (let original (vec string) (append2 (triple "oak" "pine" "birch") "cedar" "maple")) + (if (= (subvec original -1 2) (empty)) + (if (= (subvec original 3 3) (empty)) + (if (= (subvec original 9 10) (empty)) + (if (= (subvec original 2 99) (triple "birch" "cedar" "maple")) + (if (= (subvec original 1 4) (triple "pine" "birch" "cedar")) + (= original (append2 (triple "oak" "pine" "birch") "cedar" "maple")) + false) + false) + false) + false) + false)) + +(fn imported_insert_helper_ok () -> bool + (let values (vec string) (triple "oak" "pine" "birch")) + (if (= (insert_at values 1 "cedar") (append2 (pair "oak" "cedar") "pine" "birch")) + (if (= (insert_at values 3 "cedar") (append values "cedar")) + (if (= values (triple "oak" "pine" "birch")) + (if (= (insert_at values -1 "cedar") values) + (= (insert_at values 4 "cedar") values) + false) + false) + false) + false)) + +(fn imported_insert_range_helper_ok () -> bool + (let values (vec string) (append2 (triple "oak" "pine" "birch") "cedar" "maple")) + (let inserted (vec string) (pair "elm" "fir")) + (if (= (insert_range values -1 inserted) values) + (if (= (insert_range values 2 inserted) (append3 (append2 (pair "oak" "pine") "elm" "fir") "birch" "cedar" "maple")) + (if (= (insert_range values 5 inserted) (append2 (append2 (triple "oak" "pine" "birch") "cedar" "maple") "elm" "fir")) + (if (= values (append2 (triple "oak" "pine" "birch") "cedar" "maple")) + (if (= inserted (pair "elm" "fir")) + (= (insert_range values 6 inserted) values) + false) + false) + false) + false) + false)) + +(fn imported_replace_helper_ok () -> bool + (if (= (replace_at (triple "oak" "pine" "birch") 1 "cedar") (triple "oak" "cedar" "birch")) + (if (= (replace_at (triple "oak" "pine" "birch") -1 "cedar") (triple "oak" "pine" "birch")) + (= (replace_at (pair "oak" "pine") 2 "cedar") (pair "oak" "pine")) + false) + false)) + +(fn imported_replace_range_helper_ok () -> bool + (let original (vec string) (append2 (triple "oak" "pine" "birch") "cedar" "maple")) + (let replacement (vec string) (pair "elm" "fir")) + (if (= (replace_range original -1 2 replacement) original) + (if (= (replace_range original 3 3 replacement) original) + (if (= (replace_range original 4 2 replacement) original) + (if (= (replace_range original 5 9 replacement) original) + (if (= (replace_range original 2 99 replacement) (append2 (pair "oak" "pine") "elm" "fir")) + (if (= (replace_range original 1 4 replacement) (append2 (pair "oak" "elm") "fir" "maple")) + (if (= original (append2 (triple "oak" "pine" "birch") "cedar" "maple")) + (= replacement (pair "elm" "fir")) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_remove_helper_ok () -> bool + (let original (vec string) (triple "oak" "pine" "birch")) + (let removed_middle (vec string) (remove_at original 1)) + (if (= removed_middle (pair "oak" "birch")) + (if (= (len removed_middle) 2) + (if (= (remove_at original 0) (pair "pine" "birch")) + (if (= (remove_at original 2) (pair "oak" "pine")) + (if (= original (triple "oak" "pine" "birch")) + (if (= (remove_at original -1) original) + (= (remove_at original 3) original) + false) + false) + false) + false) + false) + false)) + +(fn imported_remove_range_helper_ok () -> bool + (let original (vec string) (append2 (triple "oak" "pine" "birch") "cedar" "maple")) + (if (= (remove_range original -1 2) original) + (if (= (remove_range original 3 3) original) + (if (= (remove_range original 4 2) original) + (if (= (remove_range original 5 9) original) + (if (= (remove_range original 2 99) (pair "oak" "pine")) + (if (= (remove_range original 1 4) (pair "oak" "maple")) + (= original (append2 (triple "oak" "pine" "birch") "cedar" "maple")) + false) + false) + false) + false) + false) + false)) + +(fn imported_real_program_helpers_ok () -> bool + (let values (vec string) (append2 (triple "oak" "pine" "oak") "birch" "oak")) + (if (= (original_len_after_append) 0) + (if (contains values "oak") + (if (contains values "cedar") + false + (if (= (count_of values "oak") 3) + (= (count_of values "cedar") 0) + false)) + false) + false)) + +(fn helpers_all_ok () -> bool + (if (= (imported_empty_length) 0) + (if (= (imported_pair_index) "tree") + (if (imported_builder_helpers_ok) + (if (imported_query_helpers_ok) + (if (imported_option_query_helpers_ok) + (if (imported_starts_with_helper_ok) + (if (imported_ends_with_helper_ok) + (if (imported_without_suffix_helper_ok) + (if (imported_without_prefix_helper_ok) + (if (imported_transform_helpers_ok) + (if (imported_subvec_helper_ok) + (if (imported_insert_helper_ok) + (if (imported_insert_range_helper_ok) + (if (imported_replace_helper_ok) + (if (imported_replace_range_helper_ok) + (if (imported_remove_helper_ok) + (if (imported_remove_range_helper_ok) + (imported_real_program_helpers_ok) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn main () -> i32 + (if (helpers_all_ok) + 42 + 1)) + +(test "explicit std vec_string empty len facade" + (= (imported_empty_length) 0)) + +(test "explicit std vec_string direct at facade" + (= (imported_pair_index) "tree")) + +(test "explicit std vec_string builder helpers" + (imported_builder_helpers_ok)) + +(test "explicit std vec_string query helpers" + (imported_query_helpers_ok)) + +(test "explicit std vec_string option query helpers" + (imported_option_query_helpers_ok)) + +(test "explicit std vec_string starts_with helper" + (imported_starts_with_helper_ok)) + +(test "explicit std vec_string ends_with helper" + (imported_ends_with_helper_ok)) + +(test "explicit std vec_string without_suffix helper" + (imported_without_suffix_helper_ok)) + +(test "explicit std vec_string without_prefix helper" + (imported_without_prefix_helper_ok)) + +(test "explicit std vec_string transform helpers" + (imported_transform_helpers_ok)) + +(test "explicit std vec_string subvec helper" + (imported_subvec_helper_ok)) + +(test "explicit std vec_string insert helper" + (imported_insert_helper_ok)) + +(test "explicit std vec_string insert range helper" + (imported_insert_range_helper_ok)) + +(test "explicit std vec_string replace helper" + (imported_replace_helper_ok)) + +(test "explicit std vec_string replace range helper" + (imported_replace_range_helper_ok)) + +(test "explicit std vec_string remove helper" + (imported_remove_helper_ok)) + +(test "explicit std vec_string remove range helper" + (imported_remove_range_helper_ok)) + +(test "explicit std vec_string real-program helpers" + (imported_real_program_helpers_ok)) + +(test "explicit std vec_string helpers all" + (= (main) 42)) diff --git a/docs/language/examples/speculative/array-index.slo b/docs/language/examples/speculative/array-index.slo new file mode 100644 index 0000000..f9ef6c0 --- /dev/null +++ b/docs/language/examples/speculative/array-index.slo @@ -0,0 +1,17 @@ +; status: design/speculative +; Non-contract array/index sketch retained after the v1 array value-flow +; promotion. Fixed i32 array parameters, returns, calls returning arrays, and +; dynamic indexing are promoted in examples/supported/array-value-flow.slo. +; Mutation, slices, non-i32 element types, nested arrays, equality, printing, +; unchecked indexing, and ABI/layout promises remain follow-up work. + +(module array_index) + +(fn pick_dynamic ((i i32)) -> i32 + (index (array i32 10 20 30) i)) + +(fn first_arg ((values (array i32 3))) -> i32 + (index values 0)) + +(fn main () -> i32 + (pick_dynamic 1)) diff --git a/docs/language/examples/speculative/diagnostics.slo b/docs/language/examples/speculative/diagnostics.slo new file mode 100644 index 0000000..876a8b9 --- /dev/null +++ b/docs/language/examples/speculative/diagnostics.slo @@ -0,0 +1,12 @@ +; status: design/speculative +; This file intentionally contains an example error shape. +; After top-level `test` support exists, the checked test expression should +; report a structured TypeMismatch diagnostic for this form. + +(module diagnostics) + +(fn add ((a i32) (b i32)) -> i32 + (+ a b)) + +(test "bad call example" + (= (add 3 "hello") 0)) diff --git a/docs/language/examples/speculative/factorial.slo b/docs/language/examples/speculative/factorial.slo new file mode 100644 index 0000000..d72ff28 --- /dev/null +++ b/docs/language/examples/speculative/factorial.slo @@ -0,0 +1,21 @@ +; status: design/speculative +; Uses recursive calls, `<=`, `*`, and `-` beyond the current strict +; compiler-supported subset. Value-producing `if` and top-level `test` are now +; promoted, but this full example remains speculative. + +(module factorial) + +(fn factorial ((n i32)) -> i32 + (if (<= n 1) + 1 + (* n (factorial (- n 1))))) + +(test "factorial of zero" + (= (factorial 0) 1)) + +(test "factorial of five" + (= (factorial 5) 120)) + +(fn main () -> i32 + (print_i32 (factorial 5)) + 0) diff --git a/docs/language/examples/speculative/hello.slo b/docs/language/examples/speculative/hello.slo new file mode 100644 index 0000000..ab0fe7c --- /dev/null +++ b/docs/language/examples/speculative/hello.slo @@ -0,0 +1,10 @@ +; status: design/speculative +; Requires string runtime and `print` support, which are not current Glagol +; executable-fixture capabilities. + +(module hello) + +(fn main () -> i32 + (print "Hello, Slovo") + 0) + diff --git a/docs/language/examples/speculative/option-result.slo b/docs/language/examples/speculative/option-result.slo new file mode 100644 index 0000000..6bf624d --- /dev/null +++ b/docs/language/examples/speculative/option-result.slo @@ -0,0 +1,27 @@ +; status: design/speculative +; Follow-up option/result behavior beyond promoted i32 constructors, value +; flow, tag observation, and explicit trap-based payload extraction. +; Pattern matching, mapping, equality, printing, strings, non-i32 payloads, +; nested option/result values, arrays or structs containing option/results, and +; user-catchable exceptions remain speculative. + +(module option_result) + +(fn maybe_name ((enabled bool)) -> (option string) + (if enabled + (some string "Slovo") + (none string))) + +(fn describe ((value (option i32))) -> string + (match value + ((some number) "some") + ((none) "none"))) + +(fn bool_option () -> (option bool) + (some bool true)) + +(fn string_error () -> (result i32 string) + (err i32 string "not found")) + +(fn main () -> i32 + 0) diff --git a/docs/language/examples/speculative/point.slo b/docs/language/examples/speculative/point.slo new file mode 100644 index 0000000..449e40f --- /dev/null +++ b/docs/language/examples/speculative/point.slo @@ -0,0 +1,25 @@ +; status: design/speculative +; Follow-up mutable struct example. Top-level i32 structs, immediate field +; access, immutable struct locals, struct parameters, struct returns, and field +; access through stored struct values are promoted through +; `examples/supported/struct.slo` and +; `examples/supported/struct-value-flow.slo`. +; Field mutation remains speculative. + +(module point) + +(struct Point + (x i32) + (y i32)) + +(fn point_x_followup () -> i32 + (let p Point (Point (x 3) (y 4))) + ; Future design target, not current Slovo: + ; (set-field p x 7) + (. p x)) + +(test "field access reads x" + (= (point_x_followup) 3)) + +(fn main () -> i32 + (point_x_followup)) diff --git a/docs/language/examples/speculative/unsafe-memory.slo b/docs/language/examples/speculative/unsafe-memory.slo new file mode 100644 index 0000000..d6c438e --- /dev/null +++ b/docs/language/examples/speculative/unsafe-memory.slo @@ -0,0 +1,13 @@ +; status: design/speculative +; Lexical `unsafe` is promoted; this fixture tracks raw-memory follow-up. +; Pointer locals and raw operations remain outside v0. + +(module unsafe_memory) + +(fn main () -> i32 + (unsafe + (let p (ptr i32) (alloc i32)) + (store p 42) + (print_i32 (load p)) + (dealloc p)) + 0) diff --git a/docs/language/examples/supported/add.slo b/docs/language/examples/supported/add.slo new file mode 100644 index 0000000..7550b90 --- /dev/null +++ b/docs/language/examples/supported/add.slo @@ -0,0 +1,11 @@ +; status: compiler-supported +; Strict-manifest Glagol executable fixture. + +(module main) + +(fn add ((a i32) (b i32)) -> i32 + (+ a b)) + +(fn main () -> i32 + (print_i32 (add 20 22)) + 0) diff --git a/docs/language/examples/supported/array-direct-scalars-value-flow.slo b/docs/language/examples/supported/array-direct-scalars-value-flow.slo new file mode 100644 index 0000000..fcfae37 --- /dev/null +++ b/docs/language/examples/supported/array-direct-scalars-value-flow.slo @@ -0,0 +1,59 @@ +(module main) + +(fn make_i32_values ((base i32)) -> (array i32 3) + (array i32 base (+ base 1) (+ base 2))) + +(fn make_i64_values ((base i64)) -> (array i64 3) + (array i64 base (+ base 1i64) (+ base 2i64))) + +(fn make_f64_values ((base f64)) -> (array f64 3) + (array f64 base (+ base 1.0) (+ base 2.0))) + +(fn make_flags ((first bool)) -> (array bool 3) + (array bool first false true)) + +(fn i32_at ((values (array i32 3)) (i i32)) -> i32 + (index values i)) + +(fn i64_at ((values (array i64 3)) (i i32)) -> i64 + (index values i)) + +(fn f64_at ((values (array f64 3)) (i i32)) -> f64 + (index values i)) + +(fn bool_at ((values (array bool 3)) (i i32)) -> bool + (index values i)) + +(fn i64_local_array_flow ((i i32)) -> i64 + (let values (array i64 3) (make_i64_values 40i64)) + (i64_at values i)) + +(fn f64_parameter_local_copy ((values (array f64 3)) (i i32)) -> f64 + (let copy (array f64 3) values) + (index copy i)) + +(fn echo_flags ((values (array bool 3))) -> (array bool 3) + values) + +(fn bool_call_return_index ((i i32)) -> bool + (index (make_flags true) i)) + +(test "i32 array parameter value flow" + (= (i32_at (make_i32_values 7) 0) 7)) + +(test "i64 array local call value flow" + (= (i64_local_array_flow 2) 42i64)) + +(test "f64 array parameter local copy" + (= (f64_parameter_local_copy (make_f64_values 4.0) 1) 5.0)) + +(test "bool array dynamic index" + (bool_at (make_flags true) 2)) + +(test "bool array return call value flow" + (index (echo_flags (make_flags false)) 2)) + +(fn main () -> i32 + (if (bool_call_return_index 0) + 0 + 1)) diff --git a/docs/language/examples/supported/array-direct-scalars.slo b/docs/language/examples/supported/array-direct-scalars.slo new file mode 100644 index 0000000..cb2a494 --- /dev/null +++ b/docs/language/examples/supported/array-direct-scalars.slo @@ -0,0 +1,32 @@ +(module main) + +(fn i32_second () -> i32 + (index (array i32 10 20 30) 1)) + +(fn i64_local_pick () -> i64 + (let values (array i64 3) (array i64 4i64 5i64 6i64)) + (index values 2)) + +(fn f64_third () -> f64 + (index (array f64 1.5 2.5 3.5) 2)) + +(fn bool_local_pick () -> bool + (let flags (array bool 3) (array bool false true false)) + (index flags 1)) + +(test "i32 direct scalar array index" + (= (i32_second) 20)) + +(test "i64 local direct scalar array index" + (= (i64_local_pick) 6i64)) + +(test "f64 direct scalar array index" + (= (f64_third) 3.5)) + +(test "bool local direct scalar array index" + (bool_local_pick)) + +(fn main () -> i32 + (if (bool_local_pick) + (i32_second) + 0)) diff --git a/docs/language/examples/supported/array-enum.slo b/docs/language/examples/supported/array-enum.slo new file mode 100644 index 0000000..3444aa6 --- /dev/null +++ b/docs/language/examples/supported/array-enum.slo @@ -0,0 +1,34 @@ +(module main) + +(enum Color Red Blue Green) + +(fn make_palette () -> (array Color 3) + (array Color (Color.Red) (Color.Blue) (Color.Green))) + +(fn echo_palette ((colors (array Color 3))) -> (array Color 3) + colors) + +(fn at ((colors (array Color 3)) (i i32)) -> Color + (index colors i)) + +(fn local_pick () -> Color + (let colors (array Color 3) (make_palette)) + (index colors 1)) + +(test "enum array immediate index" + (= (index (array Color (Color.Red) (Color.Blue) (Color.Green)) 2) (Color.Green))) + +(test "enum array local index" + (= (local_pick) (Color.Blue))) + +(test "enum array param return dynamic index" + (= (at (echo_palette (make_palette)) 0) (Color.Red))) + +(fn main () -> i32 + (match (at (make_palette) 1) + ((Color.Blue) + 0) + ((Color.Red) + 1) + ((Color.Green) + 1))) diff --git a/docs/language/examples/supported/array-string-value-flow.slo b/docs/language/examples/supported/array-string-value-flow.slo new file mode 100644 index 0000000..75ea05c --- /dev/null +++ b/docs/language/examples/supported/array-string-value-flow.slo @@ -0,0 +1,41 @@ +(module main) + +(fn make_words ((head string)) -> (array string 3) + (array string head "middle" "tail")) + +(fn at ((values (array string 3)) (i i32)) -> string + (index values i)) + +(fn echo ((values (array string 3))) -> (array string 3) + values) + +(fn call_return_index ((i i32)) -> string + (index (make_words "call") i)) + +(fn local_array_flow ((i i32)) -> string + (let words (array string 3) (make_words "local")) + (at words i)) + +(fn parameter_local_copy ((values (array string 3)) (i i32)) -> string + (let copy (array string 3) values) + (index copy i)) + +(test "string array parameter value flow" + (= (at (make_words "alpha") 0) "alpha")) + +(test "string array dynamic index" + (= (at (make_words "alpha") 2) "tail")) + +(test "string array local call value flow" + (= (local_array_flow 1) "middle")) + +(test "string array parameter local copy" + (= (parameter_local_copy (make_words "omega") 0) "omega")) + +(test "string array return call value flow" + (= (index (echo (make_words "zeta")) 2) "tail")) + +(fn main () -> i32 + (if (= (call_return_index 1) "middle") + 0 + 1)) diff --git a/docs/language/examples/supported/array-string.slo b/docs/language/examples/supported/array-string.slo new file mode 100644 index 0000000..cbefcb3 --- /dev/null +++ b/docs/language/examples/supported/array-string.slo @@ -0,0 +1,19 @@ +(module main) + +(fn immediate_second () -> string + (index (array string "sun" "moon" "star") 1)) + +(fn local_pick () -> string + (let words (array string 3) (array string "red" "green" "blue")) + (index words 2)) + +(test "string immediate array index" + (= (immediate_second) "moon")) + +(test "string local array index" + (= (local_pick) "blue")) + +(fn main () -> i32 + (if (= (local_pick) "blue") + 0 + 1)) diff --git a/docs/language/examples/supported/array-struct-elements.slo b/docs/language/examples/supported/array-struct-elements.slo new file mode 100644 index 0000000..2f20f49 --- /dev/null +++ b/docs/language/examples/supported/array-struct-elements.slo @@ -0,0 +1,41 @@ +(module main) + +(enum Color Red Blue Green) + +(struct Pixel + (x i32) + (label string) + (color Color)) + +(fn make_pixels ((head string)) -> (array Pixel 3) + (array Pixel (Pixel (x 1) (label head) (color (Color.Red))) (Pixel (x 2) (label "mid") (color (Color.Blue))) (Pixel (x 3) (label "tail") (color (Color.Green))))) + +(fn echo_pixels ((pixels (array Pixel 3))) -> (array Pixel 3) + pixels) + +(fn at ((pixels (array Pixel 3)) (i i32)) -> Pixel + (index pixels i)) + +(fn first_label ((head string)) -> string + (. (at (make_pixels head) 0) label)) + +(fn last_color ((head string)) -> Color + (. (index (echo_pixels (make_pixels head)) 2) color)) + +(test "struct array string field access" + (= (first_label "head") "head")) + +(test "struct array nested enum field access" + (= (last_color "head") (Color.Green))) + +(test "struct array local param return call flow" + (= (. (at (echo_pixels (make_pixels "sun")) 1) x) 2)) + +(fn main () -> i32 + (match (last_color "head") + ((Color.Green) + 0) + ((Color.Red) + 1) + ((Color.Blue) + 1))) diff --git a/docs/language/examples/supported/array-struct-fields.slo b/docs/language/examples/supported/array-struct-fields.slo new file mode 100644 index 0000000..b275d6d --- /dev/null +++ b/docs/language/examples/supported/array-struct-fields.slo @@ -0,0 +1,56 @@ +(module main) + +(struct ArrayRecord + (ints (array i32 3)) + (wides (array i64 2)) + (ratios (array f64 3)) + (flags (array bool 3)) + (words (array string 3))) + +(fn make_record ((base i32) (head string)) -> ArrayRecord + (ArrayRecord (ints (array i32 base (+ base 1) (+ base 2))) (wides (array i64 40i64 41i64)) (ratios (array f64 1.5 2.5 3.5)) (flags (array bool false true true)) (words (array string head "moon" "star")))) + +(fn echo_record ((record ArrayRecord)) -> ArrayRecord + record) + +(fn local_record ((head string)) -> ArrayRecord + (let record ArrayRecord (make_record 7 head)) + (echo_record record)) + +(fn int_at ((record ArrayRecord) (i i32)) -> i32 + (index (. record ints) i)) + +(fn wide_at ((record ArrayRecord) (i i32)) -> i64 + (index (. record wides) i)) + +(fn ratio_at ((record ArrayRecord) (i i32)) -> f64 + (index (. record ratios) i)) + +(fn flag_at ((record ArrayRecord) (i i32)) -> bool + (index (. record flags) i)) + +(fn word_at ((record ArrayRecord) (i i32)) -> string + (index (. record words) i)) + +(test "struct array i32 field access" + (= (int_at (make_record 7 "sun") 2) 9)) + +(test "struct array i64 field access" + (= (wide_at (make_record 7 "sun") 1) 41i64)) + +(test "struct array f64 field access" + (= (ratio_at (make_record 7 "sun") 2) 3.5)) + +(test "struct array bool field access" + (flag_at (make_record 7 "sun") 1)) + +(test "struct array string field access" + (= (word_at (make_record 7 "sun") 1) "moon")) + +(test "struct array local param return call flow" + (= (word_at (local_record "sun") 0) "sun")) + +(fn main () -> i32 + (if (flag_at (local_record "sun") 2) + 0 + 1)) diff --git a/docs/language/examples/supported/array-value-flow.slo b/docs/language/examples/supported/array-value-flow.slo new file mode 100644 index 0000000..7774902 --- /dev/null +++ b/docs/language/examples/supported/array-value-flow.slo @@ -0,0 +1,42 @@ +(module main) + +(fn make_values ((base i32)) -> (array i32 3) + (array i32 base (+ base 1) (+ base 2))) + +(fn first ((values (array i32 3))) -> i32 + (index values 0)) + +(fn at ((values (array i32 3)) (i i32)) -> i32 + (index values i)) + +(fn echo ((values (array i32 3))) -> (array i32 3) + values) + +(fn call_return_index ((i i32)) -> i32 + (index (make_values 10) i)) + +(fn local_array_flow ((i i32)) -> i32 + (let values (array i32 3) (make_values 20)) + (at values i)) + +(fn parameter_local_copy ((values (array i32 3)) (i i32)) -> i32 + (let copy (array i32 3) values) + (index copy i)) + +(test "array parameter value flow" + (= (first (make_values 7)) 7)) + +(test "array dynamic index" + (= (at (make_values 4) 2) 6)) + +(test "array local call value flow" + (= (local_array_flow 1) 21)) + +(test "array parameter local copy" + (= (parameter_local_copy (make_values 40) 2) 42)) + +(test "array return call value flow" + (= (index (echo (make_values 30)) 2) 32)) + +(fn main () -> i32 + (call_return_index 1)) diff --git a/docs/language/examples/supported/array.slo b/docs/language/examples/supported/array.slo new file mode 100644 index 0000000..9ca3674 --- /dev/null +++ b/docs/language/examples/supported/array.slo @@ -0,0 +1,18 @@ +(module main) + +(fn immediate_second () -> i32 + (index (array i32 10 20 30) 1)) + +(fn local_sum () -> i32 + (let values (array i32 3) (array i32 4 5 6)) + (+ (index values 0) (index values 2))) + +(test "immediate array index" + (= (immediate_second) 20)) + +(test "array local index" + (let values (array i32 3) (array i32 7 8 9)) + (= (index values 2) 9)) + +(fn main () -> i32 + (+ (immediate_second) (local_sum))) diff --git a/docs/language/examples/supported/boolean-logic.slo b/docs/language/examples/supported/boolean-logic.slo new file mode 100644 index 0000000..c9dac75 --- /dev/null +++ b/docs/language/examples/supported/boolean-logic.slo @@ -0,0 +1,58 @@ +(module main) + +(fn and_true () -> bool + (and true true)) + +(fn and_false () -> bool + (and true false)) + +(fn or_true () -> bool + (or false true)) + +(fn not_false () -> bool + (not false)) + +(fn and_short_circuits () -> bool + (not (and false (= (/ 1 0) 0)))) + +(fn or_short_circuits () -> bool + (or true (= (/ 1 0) 0))) + +(fn logic_ok () -> bool + (if (and_true) + (if (not (and_false)) + (if (or_true) + (if (not_false) + (if (and_short_circuits) + (or_short_circuits) + false) + false) + false) + false) + false)) + +(fn main () -> i32 + (if (logic_ok) + 42 + 1)) + +(test "and true" + (and_true)) + +(test "and false" + (not (and_false))) + +(test "or true" + (or_true)) + +(test "not false" + (not_false)) + +(test "and short circuit" + (and_short_circuits)) + +(test "or short circuit" + (or_short_circuits)) + +(test "boolean logic summary" + (logic_ok)) diff --git a/docs/language/examples/supported/checked-i64-to-i32-conversion.slo b/docs/language/examples/supported/checked-i64-to-i32-conversion.slo new file mode 100644 index 0000000..e0905f7 --- /dev/null +++ b/docs/language/examples/supported/checked-i64-to-i32-conversion.slo @@ -0,0 +1,57 @@ +(module main) + +(fn narrow ((value i64)) -> (result i32 i32) + (std.num.i64_to_i32_result value)) + +(fn low_ok () -> (result i32 i32) + (narrow -2147483648i64)) + +(fn high_ok () -> (result i32 i32) + (narrow 2147483647i64)) + +(fn negative_ok () -> (result i32 i32) + (narrow -7i64)) + +(fn below_low_err () -> (result i32 i32) + (narrow -2147483649i64)) + +(fn above_high_err () -> (result i32 i32) + (narrow 2147483648i64)) + +(test "i64 low bound narrows to i32" + (let value (result i32 i32) (low_ok)) + (if (std.result.is_ok value) + (= (std.result.unwrap_ok value) -2147483648) + false)) + +(test "i64 high bound narrows to i32" + (let value (result i32 i32) (high_ok)) + (if (std.result.is_ok value) + (= (std.result.unwrap_ok value) 2147483647) + false)) + +(test "negative i64 narrows to i32" + (let value (result i32 i32) (negative_ok)) + (if (std.result.is_ok value) + (= (std.result.unwrap_ok value) -7) + false)) + +(test "below i32 range returns err" + (let value (result i32 i32) (below_low_err)) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "above i32 range returns err" + (let value (result i32 i32) (above_high_err)) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(fn main () -> i32 + (let value (result i32 i32) (negative_ok)) + (if (std.result.is_ok value) + (if (= (std.result.unwrap_ok value) -7) + 0 + 1) + (std.result.unwrap_err value))) diff --git a/docs/language/examples/supported/composite-locals.slo b/docs/language/examples/supported/composite-locals.slo new file mode 100644 index 0000000..41f00c3 --- /dev/null +++ b/docs/language/examples/supported/composite-locals.slo @@ -0,0 +1,294 @@ +(module main) + +(struct Point + (x i32) + (y i32)) + +(struct PrimitiveRecord + (id i32) + (active bool) + (label string)) + +(struct NumericRecord + (wide i64) + (ratio f64)) + +(enum Signal Red Yellow Green) + +(enum Reading + Missing + (Value i32)) + +(struct TaggedReading + (status Signal) + (reading Reading)) + +(fn make_point ((x i32) (y i32)) -> Point + (Point (x x) (y y))) + +(fn make_primitive_record ((id i32) (label string)) -> PrimitiveRecord + (PrimitiveRecord (id id) (active (= id 7)) (label label))) + +(fn make_numeric_record ((wide i64) (ratio f64)) -> NumericRecord + (NumericRecord (wide wide) (ratio ratio))) + +(fn make_tagged ((status Signal) (reading Reading)) -> TaggedReading + (TaggedReading (status status) (reading reading))) + +(fn vec_i32_pair ((base i32)) -> (vec i32) + (let values (vec i32) (std.vec.i32.empty)) + (let first (vec i32) (std.vec.i32.append values base)) + (std.vec.i32.append first (+ base 1))) + +(fn vec_i64_pair ((base i64)) -> (vec i64) + (let values (vec i64) (std.vec.i64.empty)) + (let first (vec i64) (std.vec.i64.append values base)) + (std.vec.i64.append first (+ base 1i64))) + +(fn vec_f64_pair ((base f64)) -> (vec f64) + (let values (vec f64) (std.vec.f64.empty)) + (let first (vec f64) (std.vec.f64.append values base)) + (std.vec.f64.append first (+ base 1.0))) + +(fn vec_bool_pair ((first bool) (second bool)) -> (vec bool) + (let values (vec bool) (std.vec.bool.empty)) + (let left (vec bool) (std.vec.bool.append values first)) + (std.vec.bool.append left second)) + +(fn vec_string_pair ((first string) (second string)) -> (vec string) + (let values (vec string) (std.vec.string.empty)) + (let left (vec string) (std.vec.string.append values first)) + (std.vec.string.append left second)) + +(fn swap_string ((first string) (second string)) -> string + (var current string first) + (set current second) + current) + +(fn vec_i32_local_value () -> i32 + (var current (vec i32) (vec_i32_pair 10)) + (set current (vec_i32_pair 40)) + (std.vec.i32.index current 1)) + +(fn vec_i64_local_value () -> i64 + (var current (vec i64) (vec_i64_pair 10i64)) + (set current (vec_i64_pair 40i64)) + (std.vec.i64.index current 1)) + +(fn vec_f64_local_value () -> f64 + (var current (vec f64) (vec_f64_pair 10.0)) + (set current (vec_f64_pair 40.0)) + (std.vec.f64.index current 1)) + +(fn vec_bool_local_value () -> bool + (var current (vec bool) (vec_bool_pair true false)) + (set current (vec_bool_pair false true)) + (std.vec.bool.index current 1)) + +(fn vec_string_local_value () -> string + (var current (vec string) (vec_string_pair "oak" "elm")) + (set current (vec_string_pair "pine" "slovo")) + (std.vec.string.index current 1)) + +(fn option_i32_local_value () -> i32 + (var current (option i32) (none i32)) + (set current (some i32 42)) + (match current + ((some payload) + payload) + ((none) + 0))) + +(fn option_i64_local_value () -> i64 + (var current (option i64) (none i64)) + (set current (some i64 42000000000i64)) + (match current + ((some payload) + payload) + ((none) + 0i64))) + +(fn option_f64_local_value () -> f64 + (var current (option f64) (none f64)) + (set current (some f64 42.5)) + (match current + ((some payload) + payload) + ((none) + 0.0))) + +(fn option_bool_local_value () -> bool + (var current (option bool) (none bool)) + (set current (some bool true)) + (match current + ((some payload) + payload) + ((none) + false))) + +(fn option_string_local_value () -> string + (var current (option string) (none string)) + (set current (some string "branch")) + (match current + ((some payload) + payload) + ((none) + "fallback"))) + +(fn result_i32_local_value () -> i32 + (var current (result i32 i32) (err i32 i32 7)) + (set current (ok i32 i32 42)) + (match current + ((ok payload) + payload) + ((err code) + code))) + +(fn result_i64_local_value () -> i64 + (var current (result i64 i32) (err i64 i32 7)) + (set current (ok i64 i32 42000000000i64)) + (match current + ((ok payload) + payload) + ((err code) + 0i64))) + +(fn result_f64_local_value () -> f64 + (var current (result f64 i32) (err f64 i32 7)) + (set current (ok f64 i32 42.5)) + (match current + ((ok payload) + payload) + ((err code) + 0.0))) + +(fn result_bool_local_value () -> bool + (var current (result bool i32) (err bool i32 7)) + (set current (ok bool i32 true)) + (match current + ((ok payload) + payload) + ((err code) + false))) + +(fn result_string_local_value () -> string + (var current (result string i32) (err string i32 7)) + (set current (ok string i32 "slovo")) + (match current + ((ok payload) + payload) + ((err code) + "fallback"))) + +(fn point_local_sum () -> i32 + (var current Point (make_point 1 2)) + (set current (make_point 20 22)) + (+ (. current x) (. current y))) + +(fn primitive_record_local_label () -> string + (var current PrimitiveRecord (make_primitive_record 3 "alpha")) + (set current (make_primitive_record 7 "beta")) + (. current label)) + +(fn numeric_record_local_ratio () -> f64 + (var current NumericRecord (make_numeric_record 10i64 1.0)) + (set current (make_numeric_record 20i64 3.5)) + (. current ratio)) + +(fn signal_local_code () -> i32 + (var current Signal (Signal.Red)) + (set current (Signal.Green)) + (match current + ((Signal.Red) + 1) + ((Signal.Yellow) + 2) + ((Signal.Green) + 3))) + +(fn reading_local_code () -> i32 + (var current Reading (Reading.Missing)) + (set current (Reading.Value 42)) + (match current + ((Reading.Missing) + 0) + ((Reading.Value payload) + payload))) + +(fn tagged_local_code () -> i32 + (var current TaggedReading (make_tagged (Signal.Yellow) (Reading.Missing))) + (set current (make_tagged (Signal.Green) (Reading.Value 42))) + (match (. current reading) + ((Reading.Missing) + 0) + ((Reading.Value payload) + payload))) + +(test "mutable string local set works" + (= (swap_string "oak" "slovo") "slovo")) + +(test "mutable vec i32 local set works" + (= (vec_i32_local_value) 41)) + +(test "mutable vec i64 local set works" + (= (vec_i64_local_value) 41i64)) + +(test "mutable vec f64 local set works" + (= (vec_f64_local_value) 41.0)) + +(test "mutable vec bool local set works" + (vec_bool_local_value)) + +(test "mutable vec string local set works" + (= (vec_string_local_value) "slovo")) + +(test "mutable option i32 local set works" + (= (option_i32_local_value) 42)) + +(test "mutable option i64 local set works" + (= (option_i64_local_value) 42000000000i64)) + +(test "mutable option f64 local set works" + (= (option_f64_local_value) 42.5)) + +(test "mutable option bool local set works" + (option_bool_local_value)) + +(test "mutable option string local set works" + (= (option_string_local_value) "branch")) + +(test "mutable result i32 local set works" + (= (result_i32_local_value) 42)) + +(test "mutable result i64 local set works" + (= (result_i64_local_value) 42000000000i64)) + +(test "mutable result f64 local set works" + (= (result_f64_local_value) 42.5)) + +(test "mutable result bool local set works" + (result_bool_local_value)) + +(test "mutable result string local set works" + (= (result_string_local_value) "slovo")) + +(test "mutable plain struct local set works" + (= (point_local_sum) 42)) + +(test "mutable primitive struct local set works" + (= (primitive_record_local_label) "beta")) + +(test "mutable numeric struct local set works" + (= (numeric_record_local_ratio) 3.5)) + +(test "mutable payloadless enum local set works" + (= (signal_local_code) 3)) + +(test "mutable payload enum local set works" + (= (reading_local_code) 42)) + +(test "mutable enum struct local set works" + (= (tagged_local_code) 42)) + +(fn main () -> i32 + (tagged_local_code)) diff --git a/docs/language/examples/supported/composite-struct-fields.slo b/docs/language/examples/supported/composite-struct-fields.slo new file mode 100644 index 0000000..fd3028a --- /dev/null +++ b/docs/language/examples/supported/composite-struct-fields.slo @@ -0,0 +1,212 @@ +(module main) + +(enum Status Ready Blocked) + +(struct Pair + (left i32) + (right i32)) + +(struct CompositeRecord + (ints (vec i32)) + (wides (vec i64)) + (ratios (vec f64)) + (flags (vec bool)) + (labels (vec string)) + (maybe_count (option i32)) + (maybe_wide (option i64)) + (maybe_ratio (option f64)) + (maybe_flag (option bool)) + (maybe_label (option string)) + (count_result (result i32 i32)) + (wide_result (result i64 i32)) + (ratio_result (result f64 i32)) + (flag_result (result bool i32)) + (label_result (result string i32)) + (pair Pair) + (status Status)) + +(fn make_pair ((left i32) (right i32)) -> Pair + (Pair (left left) (right right))) + +(fn vec_i32_pair ((base i32)) -> (vec i32) + (let values (vec i32) (std.vec.i32.empty)) + (let first (vec i32) (std.vec.i32.append values base)) + (std.vec.i32.append first (+ base 1))) + +(fn vec_i64_pair ((base i64)) -> (vec i64) + (let values (vec i64) (std.vec.i64.empty)) + (let first (vec i64) (std.vec.i64.append values base)) + (std.vec.i64.append first (+ base 1i64))) + +(fn vec_f64_pair ((base f64)) -> (vec f64) + (let values (vec f64) (std.vec.f64.empty)) + (let first (vec f64) (std.vec.f64.append values base)) + (std.vec.f64.append first (+ base 1.0))) + +(fn vec_bool_pair ((first bool) (second bool)) -> (vec bool) + (let values (vec bool) (std.vec.bool.empty)) + (let left (vec bool) (std.vec.bool.append values first)) + (std.vec.bool.append left second)) + +(fn vec_string_pair ((first string) (second string)) -> (vec string) + (let values (vec string) (std.vec.string.empty)) + (let left (vec string) (std.vec.string.append values first)) + (std.vec.string.append left second)) + +(fn make_record () -> CompositeRecord + (CompositeRecord (ints (vec_i32_pair 10)) (wides (vec_i64_pair 100i64)) (ratios (vec_f64_pair 3.5)) (flags (vec_bool_pair false true)) (labels (vec_string_pair "oak" "elm")) (maybe_count (some i32 7)) (maybe_wide (some i64 7000000000i64)) (maybe_ratio (some f64 7.5)) (maybe_flag (some bool true)) (maybe_label (some string "alpha")) (count_result (ok i32 i32 9)) (wide_result (ok i64 i32 9000000000i64)) (ratio_result (ok f64 i32 9.25)) (flag_result (ok bool i32 true)) (label_result (ok string i32 "beta")) (pair (make_pair 20 22)) (status (Status.Ready)))) + +(fn echo_record ((record CompositeRecord)) -> CompositeRecord + record) + +(fn local_record () -> CompositeRecord + (let record CompositeRecord (make_record)) + (echo_record record)) + +(fn ints_second ((record CompositeRecord)) -> i32 + (std.vec.i32.index (. record ints) 1)) + +(fn wides_second ((record CompositeRecord)) -> i64 + (std.vec.i64.index (. record wides) 1)) + +(fn ratios_second ((record CompositeRecord)) -> f64 + (std.vec.f64.index (. record ratios) 1)) + +(fn flags_second ((record CompositeRecord)) -> bool + (std.vec.bool.index (. record flags) 1)) + +(fn labels_second ((record CompositeRecord)) -> string + (std.vec.string.index (. record labels) 1)) + +(fn maybe_count_value ((record CompositeRecord)) -> i32 + (match (. record maybe_count) + ((some payload) + payload) + ((none) + 0))) + +(fn maybe_wide_value ((record CompositeRecord)) -> i64 + (match (. record maybe_wide) + ((some payload) + payload) + ((none) + 0i64))) + +(fn maybe_ratio_value ((record CompositeRecord)) -> f64 + (match (. record maybe_ratio) + ((some payload) + payload) + ((none) + 0.0))) + +(fn maybe_flag_value ((record CompositeRecord)) -> bool + (match (. record maybe_flag) + ((some payload) + payload) + ((none) + false))) + +(fn maybe_label_value ((record CompositeRecord)) -> string + (match (. record maybe_label) + ((some payload) + payload) + ((none) + "missing"))) + +(fn count_result_value ((record CompositeRecord)) -> i32 + (match (. record count_result) + ((ok payload) + payload) + ((err code) + code))) + +(fn wide_result_value ((record CompositeRecord)) -> i64 + (match (. record wide_result) + ((ok payload) + payload) + ((err code) + 0i64))) + +(fn ratio_result_value ((record CompositeRecord)) -> f64 + (match (. record ratio_result) + ((ok payload) + payload) + ((err code) + 0.0))) + +(fn flag_result_value ((record CompositeRecord)) -> bool + (match (. record flag_result) + ((ok payload) + payload) + ((err code) + false))) + +(fn label_result_value ((record CompositeRecord)) -> string + (match (. record label_result) + ((ok payload) + payload) + ((err code) + "missing"))) + +(fn pair_total ((record CompositeRecord)) -> i32 + (+ (. (. record pair) left) (. (. record pair) right))) + +(fn is_ready ((record CompositeRecord)) -> bool + (= (. record status) (Status.Ready))) + +(test "struct vec i32 field access" + (= (ints_second (make_record)) 11)) + +(test "struct vec i64 field access" + (= (wides_second (make_record)) 101i64)) + +(test "struct vec f64 field access" + (= (ratios_second (make_record)) 4.5)) + +(test "struct vec bool field access" + (flags_second (make_record))) + +(test "struct vec string field access" + (= (labels_second (make_record)) "elm")) + +(test "struct option i32 field access" + (= (maybe_count_value (make_record)) 7)) + +(test "struct option i64 field access" + (= (maybe_wide_value (make_record)) 7000000000i64)) + +(test "struct option f64 field access" + (= (maybe_ratio_value (make_record)) 7.5)) + +(test "struct option bool field access" + (maybe_flag_value (make_record))) + +(test "struct option string field access" + (= (maybe_label_value (make_record)) "alpha")) + +(test "struct result i32 field access" + (= (count_result_value (make_record)) 9)) + +(test "struct result i64 field access" + (= (wide_result_value (make_record)) 9000000000i64)) + +(test "struct result f64 field access" + (= (ratio_result_value (make_record)) 9.25)) + +(test "struct result bool field access" + (flag_result_value (make_record))) + +(test "struct result string field access" + (= (label_result_value (make_record)) "beta")) + +(test "nested struct field access" + (= (pair_total (make_record)) 42)) + +(test "nested struct local param return call flow" + (= (pair_total (local_record)) 42)) + +(test "direct enum struct field equality" + (is_ready (make_record))) + +(fn main () -> i32 + (pair_total (local_record))) diff --git a/docs/language/examples/supported/enum-basic.slo b/docs/language/examples/supported/enum-basic.slo new file mode 100644 index 0000000..b4e3799 --- /dev/null +++ b/docs/language/examples/supported/enum-basic.slo @@ -0,0 +1,53 @@ +(module main) + +(enum Signal Red Yellow Green) + +(fn red () -> Signal + (Signal.Red)) + +(fn yellow () -> Signal + (Signal.Yellow)) + +(fn echo ((signal Signal)) -> Signal + signal) + +(fn local_signal () -> Signal + (let signal Signal (Signal.Green)) + signal) + +(fn same_signal ((left Signal) (right Signal)) -> bool + (= left right)) + +(fn signal_code ((signal Signal)) -> i32 + (match signal + ((Signal.Red) + 1) + ((Signal.Yellow) + (let code i32 2) + code) + ((Signal.Green) + 3))) + +(fn call_flow () -> Signal + (echo (local_signal))) + +(test "enum constructor equality" + (= (Signal.Red) (red))) + +(test "enum local return call flow" + (= (call_flow) (Signal.Green))) + +(test "enum parameter equality" + (same_signal (yellow) (Signal.Yellow))) + +(test "enum match red" + (= (signal_code (Signal.Red)) 1)) + +(test "enum match multi expression arm" + (= (signal_code (Signal.Yellow)) 2)) + +(test "enum match green" + (= (signal_code (call_flow)) 3)) + +(fn main () -> i32 + (signal_code (call_flow))) diff --git a/docs/language/examples/supported/enum-payload-direct-scalars.slo b/docs/language/examples/supported/enum-payload-direct-scalars.slo new file mode 100644 index 0000000..eb69cc7 --- /dev/null +++ b/docs/language/examples/supported/enum-payload-direct-scalars.slo @@ -0,0 +1,202 @@ +(module main) + +(enum Reading + Missing + (Value i32) + (Offset i32)) + +(enum WideReading + Missing + (Value i64)) + +(enum RatioReading + Missing + (Value f64)) + +(enum FlagReading + Missing + (Value bool)) + +(enum LabelReading + Missing + (Value string)) + +(struct PayloadRecord + (reading Reading) + (wide WideReading) + (ratio RatioReading) + (flag FlagReading) + (label LabelReading)) + +(fn value ((payload i32)) -> Reading + (Reading.Value payload)) + +(fn echo_reading ((reading Reading)) -> Reading + reading) + +(fn local_reading ((payload i32)) -> Reading + (let reading Reading (Reading.Value payload)) + reading) + +(fn call_reading ((payload i32)) -> Reading + (echo_reading (local_reading payload))) + +(fn reading_code ((reading Reading)) -> i32 + (match reading + ((Reading.Missing) + 0) + ((Reading.Value payload) + payload) + ((Reading.Offset payload) + (+ payload 100)))) + +(fn wide_value ((payload i64)) -> WideReading + (WideReading.Value payload)) + +(fn echo_wide ((reading WideReading)) -> WideReading + reading) + +(fn local_wide ((payload i64)) -> WideReading + (let reading WideReading (WideReading.Value payload)) + reading) + +(fn call_wide ((payload i64)) -> WideReading + (echo_wide (local_wide payload))) + +(fn wide_code ((reading WideReading)) -> i64 + (match reading + ((WideReading.Missing) + 0i64) + ((WideReading.Value payload) + payload))) + +(fn ratio_value ((payload f64)) -> RatioReading + (RatioReading.Value payload)) + +(fn ratio_code ((reading RatioReading)) -> f64 + (match reading + ((RatioReading.Missing) + 0.0) + ((RatioReading.Value payload) + payload))) + +(fn flag_value ((payload bool)) -> FlagReading + (FlagReading.Value payload)) + +(fn flag_code ((reading FlagReading)) -> bool + (match reading + ((FlagReading.Missing) + false) + ((FlagReading.Value payload) + payload))) + +(fn label_value ((payload string)) -> LabelReading + (LabelReading.Value payload)) + +(fn echo_label ((reading LabelReading)) -> LabelReading + reading) + +(fn local_label ((payload string)) -> LabelReading + (let reading LabelReading (LabelReading.Value payload)) + reading) + +(fn call_label ((payload string)) -> LabelReading + (echo_label (local_label payload))) + +(fn label_text ((reading LabelReading)) -> string + (match reading + ((LabelReading.Missing) + "") + ((LabelReading.Value payload) + payload))) + +(fn pack_record ((reading Reading) (wide WideReading) (ratio RatioReading) (flag FlagReading) (label LabelReading)) -> PayloadRecord + (PayloadRecord (reading reading) (wide wide) (ratio ratio) (flag flag) (label label))) + +(fn make_record () -> PayloadRecord + (pack_record (Reading.Offset 5) (WideReading.Value 4294967296i64) (RatioReading.Value 3.5) (FlagReading.Value true) (LabelReading.Value "hello"))) + +(fn echo_record ((record PayloadRecord)) -> PayloadRecord + record) + +(fn local_record () -> PayloadRecord + (let record PayloadRecord (make_record)) + (echo_record record)) + +(fn record_reading_code ((record PayloadRecord)) -> i32 + (reading_code (. record reading))) + +(fn record_wide_code ((record PayloadRecord)) -> i64 + (wide_code (. record wide))) + +(fn record_ratio_code ((record PayloadRecord)) -> f64 + (ratio_code (. record ratio))) + +(fn record_flag_code ((record PayloadRecord)) -> bool + (flag_code (. record flag))) + +(fn record_label_text ((record PayloadRecord)) -> string + (label_text (. record label))) + +(test "i32 enum constructor equality" + (= (Reading.Value 7) (value 7))) + +(test "i32 enum equality compares payload" + (if (= (Reading.Value 7) (Reading.Value 8)) + false + true)) + +(test "i32 enum payloadless equality" + (= (Reading.Missing) (echo_reading (Reading.Missing)))) + +(test "i32 enum local return call flow" + (= (call_reading 9) (Reading.Value 9))) + +(test "i64 enum constructor equality" + (= (WideReading.Value 4294967296i64) (wide_value 4294967296i64))) + +(test "i64 enum local return call flow" + (= (call_wide 4294967297i64) (WideReading.Value 4294967297i64))) + +(test "f64 enum constructor equality" + (= (RatioReading.Value 3.5) (ratio_value 3.5))) + +(test "f64 enum equality compares payload" + (if (= (RatioReading.Value 3.5) (RatioReading.Value 4.5)) + false + true)) + +(test "bool enum constructor equality" + (= (FlagReading.Value true) (flag_value true))) + +(test "bool enum match value" + (flag_code (FlagReading.Value true))) + +(test "string enum constructor equality" + (= (LabelReading.Value "hello") (label_value "hello"))) + +(test "string enum equality compares payload" + (if (= (LabelReading.Value "left") (LabelReading.Value "right")) + false + true)) + +(test "string enum local return call flow" + (= (call_label "hello") (LabelReading.Value "hello"))) + +(test "struct field enum i32 flow" + (= (record_reading_code (local_record)) 105)) + +(test "struct field enum i64 flow" + (= (record_wide_code (local_record)) 4294967296i64)) + +(test "struct field enum f64 flow" + (= (record_ratio_code (local_record)) 3.5)) + +(test "struct field enum bool flow" + (record_flag_code (local_record))) + +(test "struct field enum string flow" + (= (record_label_text (local_record)) "hello")) + +(fn main () -> i32 + (record_reading_code (local_record))) diff --git a/docs/language/examples/supported/enum-payload-i32.slo b/docs/language/examples/supported/enum-payload-i32.slo new file mode 100644 index 0000000..39e9007 --- /dev/null +++ b/docs/language/examples/supported/enum-payload-i32.slo @@ -0,0 +1,63 @@ +(module main) + +(enum Reading + Missing + (Value i32) + (Offset i32)) + +(fn missing () -> Reading + (Reading.Missing)) + +(fn value ((payload i32)) -> Reading + (Reading.Value payload)) + +(fn echo ((reading Reading)) -> Reading + reading) + +(fn local_reading ((payload i32)) -> Reading + (let reading Reading (Reading.Value payload)) + reading) + +(fn same_reading ((left Reading) (right Reading)) -> bool + (= left right)) + +(fn reading_code ((reading Reading)) -> i32 + (match reading + ((Reading.Missing) + 0) + ((Reading.Value payload) + payload) + ((Reading.Offset payload) + (+ payload 100)))) + +(fn call_flow ((payload i32)) -> Reading + (echo (local_reading payload))) + +(test "enum payload constructor equality" + (= (Reading.Value 7) (value 7))) + +(test "enum payload equality compares payload" + (if (= (Reading.Value 7) (Reading.Value 8)) + false + true)) + +(test "enum payloadless equality" + (= (Reading.Missing) (missing))) + +(test "enum payload local return call flow" + (= (call_flow 9) (Reading.Value 9))) + +(test "enum payload parameter equality" + (same_reading (value 11) (Reading.Value 11))) + +(test "enum payload match missing" + (= (reading_code (Reading.Missing)) 0)) + +(test "enum payload match value" + (= (reading_code (Reading.Value 12)) 12)) + +(test "enum payload match offset" + (= (reading_code (Reading.Offset 5)) 105)) + +(fn main () -> i32 + (reading_code (call_flow 42))) diff --git a/docs/language/examples/supported/enum-payload-structs.slo b/docs/language/examples/supported/enum-payload-structs.slo new file mode 100644 index 0000000..3589746 --- /dev/null +++ b/docs/language/examples/supported/enum-payload-structs.slo @@ -0,0 +1,78 @@ +(module main) + +(struct Packet + (values (array i32 3)) + (labels (array string 2)) + (flags (array bool 2))) + +(enum PacketState + Missing + (Live Packet) + (Cached Packet)) + +(fn make_packet ((base i32) (head string)) -> Packet + (Packet (values (array i32 base (+ base 1) (+ base 2))) (labels (array string head "tail")) (flags (array bool false true)))) + +(fn live ((packet Packet)) -> PacketState + (PacketState.Live packet)) + +(fn cached ((packet Packet)) -> PacketState + (PacketState.Cached packet)) + +(fn echo_state ((state PacketState)) -> PacketState + state) + +(fn local_state ((base i32) (head string)) -> PacketState + (let state PacketState (PacketState.Live (make_packet base head))) + state) + +(fn call_state ((base i32) (head string)) -> PacketState + (echo_state (cached (make_packet base head)))) + +(fn state_value ((state PacketState) (i i32)) -> i32 + (match state + ((PacketState.Missing) + 0) + ((PacketState.Live payload) + (index (. payload values) i)) + ((PacketState.Cached payload) + (index (. payload values) i)))) + +(fn state_label ((state PacketState) (i i32)) -> string + (match state + ((PacketState.Missing) + "") + ((PacketState.Live payload) + (index (. payload labels) i)) + ((PacketState.Cached payload) + (index (. payload labels) i)))) + +(fn state_flag ((state PacketState) (i i32)) -> bool + (match state + ((PacketState.Missing) + false) + ((PacketState.Live payload) + (index (. payload flags) i)) + ((PacketState.Cached payload) + (index (. payload flags) i)))) + +(test "struct payload missing arm" + (= (state_value (PacketState.Missing) 0) 0)) + +(test "struct payload live constructor flow" + (= (state_value (live (make_packet 7 "sun")) 2) 9)) + +(test "struct payload cached param return call flow" + (= (state_value (call_state 7 "sun") 1) 8)) + +(test "struct payload string array field access" + (= (state_label (call_state 7 "sun") 0) "sun")) + +(test "struct payload bool array field access" + (state_flag (call_state 7 "sun") 1)) + +(test "struct payload local return flow" + (= (state_label (local_state 9 "orb") 1) "tail")) + +(fn main () -> i32 + (state_value (call_state 7 "sun") 2)) diff --git a/docs/language/examples/supported/enum-struct-fields.slo b/docs/language/examples/supported/enum-struct-fields.slo new file mode 100644 index 0000000..46ff92e --- /dev/null +++ b/docs/language/examples/supported/enum-struct-fields.slo @@ -0,0 +1,67 @@ +(module main) + +(enum Status Ready Blocked) + +(enum Reading + Missing + (Value i32)) + +(struct TaggedReading + (status Status) + (reading Reading)) + +(fn make_tagged ((status Status) (reading Reading)) -> TaggedReading + (TaggedReading (status status) (reading reading))) + +(fn ready_value ((payload i32)) -> TaggedReading + (make_tagged (Status.Ready) (Reading.Value payload))) + +(fn missing_blocked () -> TaggedReading + (make_tagged (Status.Blocked) (Reading.Missing))) + +(fn echo_tagged ((tagged TaggedReading)) -> TaggedReading + tagged) + +(fn local_tagged ((payload i32)) -> TaggedReading + (let tagged TaggedReading (ready_value payload)) + (echo_tagged tagged)) + +(fn status_of ((tagged TaggedReading)) -> Status + (. tagged status)) + +(fn reading_of ((tagged TaggedReading)) -> Reading + (. tagged reading)) + +(fn is_ready ((tagged TaggedReading)) -> bool + (= (. tagged status) (Status.Ready))) + +(fn reading_matches ((tagged TaggedReading) (reading Reading)) -> bool + (= (. tagged reading) reading)) + +(fn reading_code ((tagged TaggedReading)) -> i32 + (match (. tagged reading) + ((Reading.Missing) + 0) + ((Reading.Value payload) + payload))) + +(test "enum struct field payloadless equality" + (= (status_of (missing_blocked)) (Status.Blocked))) + +(test "enum struct field unary payload equality" + (reading_matches (ready_value 7) (Reading.Value 7))) + +(test "enum struct field access return" + (= (reading_of (missing_blocked)) (Reading.Missing))) + +(test "enum struct field local param return call flow" + (= (reading_code (local_tagged 9)) 9)) + +(test "enum struct field match missing" + (= (reading_code (missing_blocked)) 0)) + +(test "enum struct field status predicate" + (is_ready (ready_value 11))) + +(fn main () -> i32 + (reading_code (local_tagged 42))) diff --git a/docs/language/examples/supported/f64-numeric-primitive.slo b/docs/language/examples/supported/f64-numeric-primitive.slo new file mode 100644 index 0000000..edd20b5 --- /dev/null +++ b/docs/language/examples/supported/f64-numeric-primitive.slo @@ -0,0 +1,32 @@ +(module main) + +(fn half ((value f64)) -> f64 + (/ value 2.0)) + +(fn weighted ((base f64) (scale f64)) -> f64 + (+ base (* scale 2.5))) + +(fn local_total () -> f64 + (let subtotal f64 (weighted 4.0 3.0)) + (- subtotal 1.5)) + +(fn close_enough ((value f64)) -> bool + (if (> value 9.0) + (< value 11.0) + false)) + +(fn exact_literal () -> bool + (= (half 7.0) 3.5)) + +(fn main () -> i32 + (std.io.print_f64 (local_total)) + (if (close_enough (local_total)) 0 1)) + +(test "f64 arithmetic returns exact fixture value" + (= (local_total) 10.0)) + +(test "f64 comparison works in predicates" + (close_enough (local_total))) + +(test "f64 division and equality" + (exact_literal)) diff --git a/docs/language/examples/supported/f64-to-i32-result.slo b/docs/language/examples/supported/f64-to-i32-result.slo new file mode 100644 index 0000000..601c149 --- /dev/null +++ b/docs/language/examples/supported/f64-to-i32-result.slo @@ -0,0 +1,48 @@ +(module main) + +(fn narrow ((value f64)) -> (result i32 i32) + (std.num.f64_to_i32_result value)) + +(fn zero_ok () -> (result i32 i32) + (narrow (- 2.5 2.5))) + +(fn negative_ok () -> (result i32 i32) + (narrow (- 1.0 13.0))) + +(fn fractional_err () -> (result i32 i32) + (narrow (/ 7.0 2.0))) + +(fn above_high_err () -> (result i32 i32) + (narrow 2147483648.0)) + +(test "f64 zero narrows to i32" + (let value (result i32 i32) (zero_ok)) + (if (std.result.is_ok value) + (= (std.result.unwrap_ok value) 0) + false)) + +(test "negative f64 narrows to i32" + (let value (result i32 i32) (negative_ok)) + (if (std.result.is_ok value) + (= (std.result.unwrap_ok value) -12) + false)) + +(test "fractional f64 returns err" + (let value (result i32 i32) (fractional_err)) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "above i32 range f64 returns err" + (let value (result i32 i32) (above_high_err)) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(fn main () -> i32 + (let value (result i32 i32) (negative_ok)) + (if (std.result.is_ok value) + (if (= (std.result.unwrap_ok value) -12) + 0 + 1) + (std.result.unwrap_err value))) diff --git a/docs/language/examples/supported/f64-to-i64-result.slo b/docs/language/examples/supported/f64-to-i64-result.slo new file mode 100644 index 0000000..8aedccd --- /dev/null +++ b/docs/language/examples/supported/f64-to-i64-result.slo @@ -0,0 +1,48 @@ +(module main) + +(fn narrow ((value f64)) -> (result i64 i32) + (std.num.f64_to_i64_result value)) + +(fn zero_ok () -> (result i64 i32) + (narrow (- 2.5 2.5))) + +(fn negative_ok () -> (result i64 i32) + (narrow (- 1.0 13.0))) + +(fn fractional_err () -> (result i64 i32) + (narrow (/ 7.0 2.0))) + +(fn above_high_err () -> (result i64 i32) + (narrow 9223372036854776000.0)) + +(test "f64 zero narrows to i64" + (let value (result i64 i32) (zero_ok)) + (if (std.result.is_ok value) + (= (std.result.unwrap_ok value) 0i64) + false)) + +(test "negative f64 narrows to i64" + (let value (result i64 i32) (negative_ok)) + (if (std.result.is_ok value) + (= (std.result.unwrap_ok value) -12i64) + false)) + +(test "fractional f64 returns err for i64" + (let value (result i64 i32) (fractional_err)) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "above i64 range f64 returns err" + (let value (result i64 i32) (above_high_err)) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(fn main () -> i32 + (let value (result i64 i32) (negative_ok)) + (if (std.result.is_ok value) + (if (= (std.result.unwrap_ok value) -12i64) + 0 + 1) + (std.result.unwrap_err value))) diff --git a/docs/language/examples/supported/f64-to-string.slo b/docs/language/examples/supported/f64-to-string.slo new file mode 100644 index 0000000..f24c865 --- /dev/null +++ b/docs/language/examples/supported/f64-to-string.slo @@ -0,0 +1,40 @@ +(module main) + +(fn f64_zero_text () -> string + (std.num.f64_to_string (- 2.5 2.5))) + +(fn f64_fractional_text () -> string + (std.num.f64_to_string (/ 7.0 2.0))) + +(fn f64_negative_text () -> string + (std.num.f64_to_string (- 2.5 4.0))) + +(fn f64_whole_text () -> string + (std.num.f64_to_string (+ 7.0 3.0))) + +(test "f64 zero to string" + (= (f64_zero_text) "0.0")) + +(test "f64 fractional to string" + (= (f64_fractional_text) "3.5")) + +(test "f64 negative to string" + (= (f64_negative_text) "-1.5")) + +(test "f64 whole to string" + (= (f64_whole_text) "10.0")) + +(test "f64 negative string length" + (= (std.string.len (f64_negative_text)) 4)) + +(test "f64 whole string length" + (= (std.string.len (f64_whole_text)) 4)) + +(fn main () -> i32 + (std.io.print_string (f64_zero_text)) + (std.io.print_string (f64_fractional_text)) + (std.io.print_string (f64_negative_text)) + (std.io.print_string (f64_whole_text)) + (if (= (std.string.len (f64_fractional_text)) 3) + 0 + 1)) diff --git a/docs/language/examples/supported/host-io-result.slo b/docs/language/examples/supported/host-io-result.slo new file mode 100644 index 0000000..3ed43a6 --- /dev/null +++ b/docs/language/examples/supported/host-io-result.slo @@ -0,0 +1,77 @@ +(module main) + +(fn fixture_path () -> string + "slovo-exp10-host-io-result.txt") + +(fn ok_text ((value string)) -> (result string i32) + (ok string i32 value)) + +(fn err_text ((code i32)) -> (result string i32) + (err string i32 code)) + +(fn result_text_or_error ((value (result string i32))) -> string + (match value + ((ok payload) + payload) + ((err code) + (std.io.print_i32 code) + "error"))) + +(fn result_text_len_or_code ((value (result string i32))) -> i32 + (match value + ((ok payload) + (std.string.len payload)) + ((err code) + code))) + +(fn write_fixture_result () -> (result i32 i32) + (std.fs.write_text_result (fixture_path) "exp10 host io")) + +(fn read_fixture_result () -> (result string i32) + (std.fs.write_text_result (fixture_path) "exp10 host io") + (std.fs.read_text_result (fixture_path))) + +(fn missing_env_result () -> (result string i32) + (std.env.get_result "SLOVO_EXP10_MISSING_ENV")) + +(fn first_arg_ok_score () -> i32 + (if (< 0 (std.process.argc)) + (if (is_ok (std.process.arg_result 0)) + 1 + 0) + 0)) + +(fn read_unwrapped_text () -> string + (unwrap_ok (read_fixture_result))) + +(fn missing_env_code () -> i32 + (unwrap_err (missing_env_result))) + +(test "string result constructor ok" + (= (result_text_or_error (ok_text "constructed")) "constructed")) + +(test "string result constructor err" + (= (result_text_len_or_code (err_text 1)) 1)) + +(test "missing env returns err 1" + (= (missing_env_code) 1)) + +(test "arg result is ok when argc is positive" + (= (first_arg_ok_score) (if (< 0 (std.process.argc)) + 1 + 0))) + +(test "write text result returns ok zero" + (= (unwrap_ok (write_fixture_result)) 0)) + +(test "read text result returns written text" + (= (read_unwrapped_text) "exp10 host io")) + +(test "read text result match observes string payload" + (= (result_text_len_or_code (read_fixture_result)) 13)) + +(fn main () -> i32 + (std.io.eprint "exp-10 host io result") + (if (is_ok (write_fixture_result)) + (unwrap_ok (write_fixture_result)) + 1)) diff --git a/docs/language/examples/supported/host-io.slo b/docs/language/examples/supported/host-io.slo new file mode 100644 index 0000000..7d1eec5 --- /dev/null +++ b/docs/language/examples/supported/host-io.slo @@ -0,0 +1,34 @@ +(module main) + +(fn fixture_path () -> string + "slovo-exp3-host-io.txt") + +(fn write_fixture () -> i32 + (std.fs.write_text (fixture_path) "host io")) + +(fn read_fixture () -> string + (std.fs.read_text (fixture_path))) + +(fn missing_env () -> string + (std.env.get "SLOVO_EXP3_MISSING")) + +(fn arg_count_plus_one () -> i32 + (+ (std.process.argc) 1)) + +(test "missing env returns empty string" + (= (missing_env) "")) + +(test "argc is not negative" + (< 0 (arg_count_plus_one))) + +(test "write text returns success" + (= (write_fixture) 0)) + +(test "read text returns written text" + (= (read_fixture) "host io")) + +(fn main () -> i32 + (std.io.eprint "exp-3 host io") + (std.io.print_string (std.process.arg 0)) + (std.io.print_string (read_fixture)) + (std.fs.write_text (fixture_path) "host io")) diff --git a/docs/language/examples/supported/i64-numeric-primitive.slo b/docs/language/examples/supported/i64-numeric-primitive.slo new file mode 100644 index 0000000..f297f92 --- /dev/null +++ b/docs/language/examples/supported/i64-numeric-primitive.slo @@ -0,0 +1,37 @@ +(module main) + +(fn base () -> i64 + 2147483648i64) + +(fn adjust ((value i64) (delta i64)) -> i64 + (+ value delta)) + +(fn doubled ((value i64)) -> i64 + (* value 2i64)) + +(fn local_total () -> i64 + (let offset i64 -7i64) + (adjust (- (doubled (base)) 0i64) offset)) + +(fn high_enough ((value i64)) -> bool + (if (> value 4294967280i64) + (< value 4294967300i64) + false)) + +(fn exact_i64 () -> bool + (= (local_total) 4294967289i64)) + +(fn main () -> i32 + (std.io.print_i64 (local_total)) + (if (high_enough (local_total)) 0 1)) + +(test "i64 arithmetic returns exact fixture value" + (exact_i64)) + +(test "i64 comparison works in predicates" + (high_enough (local_total))) + +(test "i64 division and ordering" + (if (>= (/ (local_total) 3i64) 1431655763i64) + (<= (/ (local_total) 3i64) 1431655763i64) + false)) diff --git a/docs/language/examples/supported/if.slo b/docs/language/examples/supported/if.slo new file mode 100644 index 0000000..b94c047 --- /dev/null +++ b/docs/language/examples/supported/if.slo @@ -0,0 +1,20 @@ +(module main) + +(fn choose ((value i32)) -> i32 + (if (< value 3) + 10 + 20)) + +(test "if chooses then" + (= (choose 2) 10)) + +(test "if chooses else" + (= (choose 4) 20)) + +(test "if returns bool" + (if (< 1 2) + true + false)) + +(fn main () -> i32 + (choose 2)) diff --git a/docs/language/examples/supported/integer-bitwise.slo b/docs/language/examples/supported/integer-bitwise.slo new file mode 100644 index 0000000..30b8022 --- /dev/null +++ b/docs/language/examples/supported/integer-bitwise.slo @@ -0,0 +1,58 @@ +(module main) + +(fn i32_and () -> i32 + (bit_and 6 3)) + +(fn i32_or () -> i32 + (bit_or 4 2)) + +(fn i32_xor () -> i32 + (bit_xor 7 3)) + +(fn i64_and () -> i64 + (bit_and 6i64 3i64)) + +(fn i64_or () -> i64 + (bit_or 4i64 2i64)) + +(fn i64_xor () -> i64 + (bit_xor 7i64 3i64)) + +(fn bitwise_ok () -> bool + (if (= (i32_and) 2) + (if (= (i32_or) 6) + (if (= (i32_xor) 4) + (if (= (i64_and) 2i64) + (if (= (i64_or) 6i64) + (= (i64_xor) 4i64) + false) + false) + false) + false) + false)) + +(fn main () -> i32 + (if (bitwise_ok) + 42 + 1)) + +(test "i32 bit and" + (= (i32_and) 2)) + +(test "i32 bit or" + (= (i32_or) 6)) + +(test "i32 bit xor" + (= (i32_xor) 4)) + +(test "i64 bit and" + (= (i64_and) 2i64)) + +(test "i64 bit or" + (= (i64_or) 6i64)) + +(test "i64 bit xor" + (= (i64_xor) 4i64)) + +(test "integer bitwise summary" + (bitwise_ok)) diff --git a/docs/language/examples/supported/integer-remainder.slo b/docs/language/examples/supported/integer-remainder.slo new file mode 100644 index 0000000..94615f0 --- /dev/null +++ b/docs/language/examples/supported/integer-remainder.slo @@ -0,0 +1,42 @@ +(module main) + +(fn i32_remainder () -> i32 + (% 17 5)) + +(fn i32_signed_remainder () -> i32 + (% -17 5)) + +(fn i64_remainder () -> i64 + (% 42i64 5i64)) + +(fn i64_signed_remainder () -> i64 + (% -17i64 5i64)) + +(fn remainder_ok () -> bool + (if (= (i32_remainder) 2) + (if (= (i32_signed_remainder) -2) + (if (= (i64_remainder) 2i64) + (= (i64_signed_remainder) -2i64) + false) + false) + false)) + +(fn main () -> i32 + (if (remainder_ok) + 42 + 1)) + +(test "i32 remainder" + (= (i32_remainder) 2)) + +(test "i32 signed remainder" + (= (i32_signed_remainder) -2)) + +(test "i64 remainder" + (= (i64_remainder) 2i64)) + +(test "i64 signed remainder" + (= (i64_signed_remainder) -2i64)) + +(test "integer remainder summary" + (remainder_ok)) diff --git a/docs/language/examples/supported/integer-to-string.slo b/docs/language/examples/supported/integer-to-string.slo new file mode 100644 index 0000000..e73bd4d --- /dev/null +++ b/docs/language/examples/supported/integer-to-string.slo @@ -0,0 +1,54 @@ +(module main) + +(fn i32_zero_text () -> string + (std.num.i32_to_string 0)) + +(fn i32_negative_text () -> string + (std.num.i32_to_string -7)) + +(fn i32_high_text () -> string + (std.num.i32_to_string 2147483647)) + +(fn i64_low_text () -> string + (std.num.i64_to_string -9223372036854775808i64)) + +(fn i64_high_text () -> string + (std.num.i64_to_string 9223372036854775807i64)) + +(fn i64_beyond_i32_text () -> string + (std.num.i64_to_string 2147483648i64)) + +(test "i32 zero to string" + (= (i32_zero_text) "0")) + +(test "i32 negative to string" + (= (i32_negative_text) "-7")) + +(test "i32 high to string" + (= (i32_high_text) "2147483647")) + +(test "i32 negative string length" + (= (std.string.len (i32_negative_text)) 2)) + +(test "i64 low to string" + (= (i64_low_text) "-9223372036854775808")) + +(test "i64 high to string" + (= (i64_high_text) "9223372036854775807")) + +(test "i64 beyond i32 to string" + (= (i64_beyond_i32_text) "2147483648")) + +(test "i64 low string length" + (= (std.string.len (i64_low_text)) 20)) + +(fn main () -> i32 + (std.io.print_string (i32_zero_text)) + (std.io.print_string (i32_negative_text)) + (std.io.print_string (i32_high_text)) + (std.io.print_string (i64_low_text)) + (std.io.print_string (i64_high_text)) + (std.io.print_string (i64_beyond_i32_text)) + (if (= (std.string.len (i64_high_text)) 19) + 0 + 1)) diff --git a/docs/language/examples/supported/local-variables.slo b/docs/language/examples/supported/local-variables.slo new file mode 100644 index 0000000..441eab9 --- /dev/null +++ b/docs/language/examples/supported/local-variables.slo @@ -0,0 +1,65 @@ +(module main) + +(fn add_local ((a i32)) -> i32 + (let one i32 1) + (var total i32 (+ a one)) + (set total (+ total 1)) + total) + +(fn keep_flag ((flag bool)) -> bool + (let local_flag bool flag) + local_flag) + +(fn flip_flag ((flag bool)) -> bool + (var current bool flag) + (set current (if current + false + true)) + current) + +(fn add_wide_local ((base i64)) -> i64 + (var count i64 base) + (set count (+ count 2i64)) + count) + +(fn add_ratio_local ((base f64)) -> f64 + (var amount f64 base) + (set amount (+ amount 0.5)) + amount) + +(test "locals work" + (let base i32 2) + (var value i32 (add_local base)) + (set value (+ value 1)) + (= value 5)) + +(test "bool let locals work" + (let expected bool true) + (let actual bool (keep_flag expected)) + actual) + +(test "bool let locals preserve false" + (if (keep_flag false) + false + true)) + +(test "bool var set flips true" + (if (flip_flag true) + false + true)) + +(test "bool var set flips false" + (flip_flag false)) + +(test "i64 var set works" + (= (add_wide_local 40i64) 42i64)) + +(test "f64 var set works" + (= (add_ratio_local 41.5) 42.0)) + +(fn main () -> i32 + (var flag bool false) + (set flag (flip_flag flag)) + (if flag + (add_local 2) + 1)) diff --git a/docs/language/examples/supported/numeric-struct-fields.slo b/docs/language/examples/supported/numeric-struct-fields.slo new file mode 100644 index 0000000..6034d90 --- /dev/null +++ b/docs/language/examples/supported/numeric-struct-fields.slo @@ -0,0 +1,82 @@ +(module main) + +(struct NumericRecord + (wide i64) + (ratio f64)) + +(fn make_record ((wide i64) (ratio f64)) -> NumericRecord + (NumericRecord (wide wide) (ratio ratio))) + +(fn literal_record () -> NumericRecord + (NumericRecord (wide 2147483648i64) (ratio 3.5))) + +(fn echo_record ((record NumericRecord)) -> NumericRecord + record) + +(fn local_record ((wide i64) (ratio f64)) -> NumericRecord + (let record NumericRecord (make_record wide ratio)) + (echo_record record)) + +(fn record_wide ((record NumericRecord)) -> i64 + (. record wide)) + +(fn record_ratio ((record NumericRecord)) -> f64 + (. record ratio)) + +(fn wide_total ((record NumericRecord)) -> i64 + (+ (. record wide) 10i64)) + +(fn ratio_total ((record NumericRecord)) -> f64 + (+ (. record ratio) 1.5)) + +(fn wide_in_range ((record NumericRecord)) -> bool + (if (> (. record wide) 2147483640i64) + (< (. record wide) 2147483660i64) + false)) + +(fn ratio_in_range ((record NumericRecord)) -> bool + (if (> (. record ratio) 3.0) + (< (. record ratio) 4.0) + false)) + +(fn wide_text ((record NumericRecord)) -> string + (std.num.i64_to_string (. record wide))) + +(fn ratio_text ((record NumericRecord)) -> string + (std.num.f64_to_string (. record ratio))) + +(test "numeric struct i64 field access" + (= (record_wide (literal_record)) 2147483648i64)) + +(test "numeric struct f64 field access" + (= (record_ratio (literal_record)) 3.5)) + +(test "numeric struct i64 arithmetic after access" + (= (wide_total (literal_record)) 2147483658i64)) + +(test "numeric struct f64 arithmetic after access" + (= (ratio_total (literal_record)) 5.0)) + +(test "numeric struct i64 comparison after access" + (wide_in_range (literal_record))) + +(test "numeric struct f64 comparison after access" + (ratio_in_range (literal_record))) + +(test "numeric struct local param return call flow" + (= (wide_total (local_record 2147483648i64 3.5)) 2147483658i64)) + +(test "numeric struct i64 format after access" + (= (wide_text (literal_record)) "2147483648")) + +(test "numeric struct f64 format after access" + (= (ratio_text (literal_record)) "3.5")) + +(fn main () -> i32 + (std.io.print_i64 (record_wide (literal_record))) + (std.io.print_f64 (record_ratio (literal_record))) + (if (wide_in_range (local_record 2147483648i64 3.5)) + (if (ratio_in_range (local_record 2147483648i64 3.5)) + 0 + 1) + 1)) diff --git a/docs/language/examples/supported/numeric-widening-conversions.slo b/docs/language/examples/supported/numeric-widening-conversions.slo new file mode 100644 index 0000000..8efc10c --- /dev/null +++ b/docs/language/examples/supported/numeric-widening-conversions.slo @@ -0,0 +1,32 @@ +(module main) + +(fn base_i64 () -> i64 + (std.num.i32_to_i64 2147483647)) + +(fn widened_i64 () -> i64 + (+ (base_i64) 1i64)) + +(fn half_step () -> f64 + (+ (std.num.i32_to_f64 5) 0.5)) + +(fn wide_as_f64 () -> f64 + (std.num.i64_to_f64 (widened_i64))) + +(fn main () -> i32 + (std.io.print_i64 (widened_i64)) + (std.io.print_f64 (wide_as_f64)) + (if (= (widened_i64) 2147483648i64) + 0 + 1)) + +(test "i32 widens to i64" + (= (std.num.i32_to_i64 42) 42i64)) + +(test "i32 to i64 feeds i64 arithmetic" + (= (widened_i64) 2147483648i64)) + +(test "i32 widens to f64" + (= (std.num.i32_to_f64 42) 42.0)) + +(test "i64 widens to f64" + (= (wide_as_f64) 2147483648.0)) diff --git a/docs/language/examples/supported/option-result-flow.slo b/docs/language/examples/supported/option-result-flow.slo new file mode 100644 index 0000000..14b9ba0 --- /dev/null +++ b/docs/language/examples/supported/option-result-flow.slo @@ -0,0 +1,160 @@ +(module main) + +(fn maybe_value ((value i32)) -> (option i32) + (some i32 value)) + +(fn maybe_empty () -> (option i32) + (none i32)) + +(fn maybe_wide_value ((value i64)) -> (option i64) + (some i64 value)) + +(fn maybe_wide_empty () -> (option i64) + (none i64)) + +(fn maybe_float_value ((value f64)) -> (option f64) + (some f64 value)) + +(fn maybe_float_empty () -> (option f64) + (none f64)) + +(fn maybe_flag_value ((value bool)) -> (option bool) + (some bool value)) + +(fn maybe_flag_empty () -> (option bool) + (none bool)) + +(fn maybe_string_value ((value string)) -> (option string) + (some string value)) + +(fn maybe_string_empty () -> (option string) + (none string)) + +(fn option_score ((value (option i32))) -> i32 + (if (is_some value) + 1 + 0)) + +(fn option_empty_score ((value (option i32))) -> i32 + (if (is_none value) + 1 + 0)) + +(fn option_wide_score ((value (option i64))) -> i32 + (if (is_some value) + 1 + 0)) + +(fn option_wide_empty_score ((value (option i64))) -> i32 + (if (is_none value) + 1 + 0)) + +(fn option_float_score ((value (option f64))) -> i32 + (if (is_some value) + 1 + 0)) + +(fn option_float_empty_score ((value (option f64))) -> i32 + (if (is_none value) + 1 + 0)) + +(fn option_flag_score ((value (option bool))) -> i32 + (if (is_some value) + 1 + 0)) + +(fn option_flag_empty_score ((value (option bool))) -> i32 + (if (is_none value) + 1 + 0)) + +(fn option_string_score ((value (option string))) -> i32 + (if (is_some value) + 1 + 0)) + +(fn option_string_empty_score ((value (option string))) -> i32 + (if (is_none value) + 1 + 0)) + +(fn result_ok_value ((value i32)) -> (result i32 i32) + (ok i32 i32 value)) + +(fn result_err_value ((code i32)) -> (result i32 i32) + (err i32 i32 code)) + +(fn result_success_score ((value (result i32 i32))) -> i32 + (if (is_ok value) + 1 + 0)) + +(fn result_failure_score ((value (result i32 i32))) -> i32 + (if (is_err value) + 1 + 0)) + +(fn option_local_flow () -> i32 + (let value (option i32) (maybe_value 42)) + (option_score value)) + +(fn option_wide_local_flow () -> i32 + (let value (option i64) (maybe_wide_value 2147483648i64)) + (option_wide_score value)) + +(fn option_float_local_flow () -> i32 + (let value (option f64) (maybe_float_value 42.5)) + (option_float_score value)) + +(fn option_flag_local_flow () -> i32 + (let value (option bool) (maybe_flag_value true)) + (option_flag_score value)) + +(fn option_string_local_flow () -> i32 + (let value (option string) (maybe_string_value "slovo")) + (option_string_score value)) + +(fn result_local_flow () -> i32 + (let value (result i32 i32) (result_err_value 7)) + (result_failure_score value)) + +(test "option local value flow" + (= (option_local_flow) 1)) + +(test "option call observation" + (= (option_empty_score (maybe_empty)) 1)) + +(test "option i64 local value flow" + (= (option_wide_local_flow) 1)) + +(test "option i64 call observation" + (= (option_wide_empty_score (maybe_wide_empty)) 1)) + +(test "option f64 local value flow" + (= (option_float_local_flow) 1)) + +(test "option f64 call observation" + (= (option_float_empty_score (maybe_float_empty)) 1)) + +(test "option bool local value flow" + (= (option_flag_local_flow) 1)) + +(test "option bool call observation" + (= (option_flag_empty_score (maybe_flag_empty)) 1)) + +(test "option string local value flow" + (= (option_string_local_flow) 1)) + +(test "option string call observation" + (= (option_string_empty_score (maybe_string_empty)) 1)) + +(test "result call observation" + (= (result_success_score (result_ok_value 42)) 1)) + +(test "result local value flow" + (= (result_local_flow) 1)) + +(fn main () -> i32 + 0) diff --git a/docs/language/examples/supported/option-result-match.slo b/docs/language/examples/supported/option-result-match.slo new file mode 100644 index 0000000..827d6a3 --- /dev/null +++ b/docs/language/examples/supported/option-result-match.slo @@ -0,0 +1,185 @@ +(module main) + +(fn maybe_value ((value i32)) -> (option i32) + (some i32 value)) + +(fn maybe_empty () -> (option i32) + (none i32)) + +(fn maybe_wide_value ((value i64)) -> (option i64) + (some i64 value)) + +(fn maybe_wide_empty () -> (option i64) + (none i64)) + +(fn maybe_float_value ((value f64)) -> (option f64) + (some f64 value)) + +(fn maybe_float_empty () -> (option f64) + (none f64)) + +(fn maybe_flag_value ((value bool)) -> (option bool) + (some bool value)) + +(fn maybe_flag_empty () -> (option bool) + (none bool)) + +(fn maybe_string_value ((value string)) -> (option string) + (some string value)) + +(fn maybe_string_empty () -> (option string) + (none string)) + +(fn result_ok_value ((value i32)) -> (result i32 i32) + (ok i32 i32 value)) + +(fn result_err_value ((code i32)) -> (result i32 i32) + (err i32 i32 code)) + +(fn option_value_or ((value (option i32)) (fallback i32)) -> i32 + (match value + ((some payload) + payload) + ((none) + fallback))) + +(fn option_bump_or_zero ((value (option i32))) -> i32 + (match value + ((some payload) + (let bumped i32 (+ payload 1)) + bumped) + ((none) + 0))) + +(fn option_wide_value_or ((value (option i64)) (fallback i64)) -> i64 + (match value + ((some payload) + payload) + ((none) + fallback))) + +(fn option_wide_bump_or_zero ((value (option i64))) -> i64 + (match value + ((some payload) + (let bumped i64 (+ payload 1i64)) + bumped) + ((none) + 0i64))) + +(fn option_float_value_or ((value (option f64)) (fallback f64)) -> f64 + (match value + ((some payload) + payload) + ((none) + fallback))) + +(fn option_float_bump_or_zero ((value (option f64))) -> f64 + (match value + ((some payload) + (let bumped f64 (+ payload 1.0)) + bumped) + ((none) + 0.0))) + +(fn option_flag_value_or ((value (option bool)) (fallback bool)) -> bool + (match value + ((some payload) + payload) + ((none) + fallback))) + +(fn option_flag_selected_or ((value (option bool)) (fallback bool)) -> bool + (match value + ((some payload) + (if payload + true + false) + payload) + ((none) + fallback))) + +(fn option_string_value_or ((value (option string)) (fallback string)) -> string + (match value + ((some payload) + payload) + ((none) + fallback))) + +(fn option_string_selected_or ((value (option string)) (fallback string)) -> string + (match value + ((some payload) + (let chosen string payload) + chosen) + ((none) + fallback))) + +(fn result_value_or_code ((value (result i32 i32))) -> i32 + (match value + ((ok payload) + payload) + ((err code) + code))) + +(fn result_score ((value (result i32 i32))) -> i32 + (match value + ((ok payload) + (+ payload 10)) + ((err code) + code))) + +(test "option match some payload" + (= (option_value_or (maybe_value 42) 0) 42)) + +(test "option match none fallback" + (= (option_value_or (maybe_empty) 7) 7)) + +(test "option match multi expression arm" + (= (option_bump_or_zero (maybe_value 8)) 9)) + +(test "option i64 match some payload" + (= (option_wide_value_or (maybe_wide_value 2147483648i64) 0i64) 2147483648i64)) + +(test "option i64 match none fallback" + (= (option_wide_value_or (maybe_wide_empty) 7i64) 7i64)) + +(test "option i64 match multi expression arm" + (= (option_wide_bump_or_zero (maybe_wide_value 8i64)) 9i64)) + +(test "option f64 match some payload" + (= (option_float_value_or (maybe_float_value 42.5) 0.0) 42.5)) + +(test "option f64 match none fallback" + (= (option_float_value_or (maybe_float_empty) 7.0) 7.0)) + +(test "option f64 match multi expression arm" + (= (option_float_bump_or_zero (maybe_float_value 8.5)) 9.5)) + +(test "option bool match some payload" + (option_flag_value_or (maybe_flag_value true) false)) + +(test "option bool match none fallback" + (option_flag_value_or (maybe_flag_empty) true)) + +(test "option bool match multi expression arm" + (option_flag_selected_or (maybe_flag_value true) false)) + +(test "option string match some payload" + (= (option_string_value_or (maybe_string_value "slovo") "fallback") "slovo")) + +(test "option string match none fallback" + (= (option_string_value_or (maybe_string_empty) "fallback") "fallback")) + +(test "option string match multi expression arm" + (= (option_string_selected_or (maybe_string_value "oak") "fallback") "oak")) + +(test "result match ok payload" + (= (result_value_or_code (result_ok_value 30)) 30)) + +(test "result match err payload" + (= (result_value_or_code (result_err_value 5)) 5)) + +(test "result match computed arm" + (= (result_score (result_ok_value 2)) 12)) + +(fn main () -> i32 + 0) diff --git a/docs/language/examples/supported/option-result-payload.slo b/docs/language/examples/supported/option-result-payload.slo new file mode 100644 index 0000000..80af456 --- /dev/null +++ b/docs/language/examples/supported/option-result-payload.slo @@ -0,0 +1,259 @@ +(module main) + +(fn maybe_value ((value i32)) -> (option i32) + (some i32 value)) + +(fn maybe_empty () -> (option i32) + (none i32)) + +(fn maybe_wide_value ((value i64)) -> (option i64) + (some i64 value)) + +(fn maybe_wide_empty () -> (option i64) + (none i64)) + +(fn maybe_float_value ((value f64)) -> (option f64) + (some f64 value)) + +(fn maybe_float_empty () -> (option f64) + (none f64)) + +(fn maybe_flag_value ((value bool)) -> (option bool) + (some bool value)) + +(fn maybe_flag_empty () -> (option bool) + (none bool)) + +(fn maybe_string_value ((value string)) -> (option string) + (some string value)) + +(fn maybe_string_empty () -> (option string) + (none string)) + +(fn result_ok_value ((value i32)) -> (result i32 i32) + (ok i32 i32 value)) + +(fn result_err_value ((code i32)) -> (result i32 i32) + (err i32 i32 code)) + +(fn option_direct_payload () -> i32 + (unwrap_some (some i32 42))) + +(fn option_param_payload ((value (option i32))) -> i32 + (unwrap_some value)) + +(fn option_local_payload () -> i32 + (let value (option i32) (maybe_value 21)) + (unwrap_some value)) + +(fn option_call_payload () -> i32 + (unwrap_some (maybe_value 22))) + +(fn option_guarded_payload ((value (option i32))) -> i32 + (if (is_some value) + (unwrap_some value) + 0)) + +(fn option_wide_direct_payload () -> i64 + (unwrap_some (some i64 2147483648i64))) + +(fn option_wide_param_payload ((value (option i64))) -> i64 + (unwrap_some value)) + +(fn option_wide_local_payload () -> i64 + (let value (option i64) (maybe_wide_value 21i64)) + (unwrap_some value)) + +(fn option_wide_call_payload () -> i64 + (unwrap_some (maybe_wide_value 22i64))) + +(fn option_wide_guarded_payload ((value (option i64))) -> i64 + (if (is_some value) + (unwrap_some value) + 0i64)) + +(fn option_float_direct_payload () -> f64 + (unwrap_some (some f64 42.5))) + +(fn option_float_param_payload ((value (option f64))) -> f64 + (unwrap_some value)) + +(fn option_float_local_payload () -> f64 + (let value (option f64) (maybe_float_value 21.5)) + (unwrap_some value)) + +(fn option_float_call_payload () -> f64 + (unwrap_some (maybe_float_value 22.5))) + +(fn option_float_guarded_payload ((value (option f64))) -> f64 + (if (is_some value) + (unwrap_some value) + 0.0)) + +(fn option_flag_direct_payload () -> bool + (unwrap_some (some bool true))) + +(fn option_flag_param_payload ((value (option bool))) -> bool + (unwrap_some value)) + +(fn option_flag_local_payload () -> bool + (let value (option bool) (maybe_flag_value true)) + (unwrap_some value)) + +(fn option_flag_call_payload () -> bool + (unwrap_some (maybe_flag_value true))) + +(fn option_flag_guarded_payload ((value (option bool))) -> bool + (if (is_some value) + (unwrap_some value) + false)) + +(fn option_string_direct_payload () -> string + (unwrap_some (some string "slovo"))) + +(fn option_string_param_payload ((value (option string))) -> string + (unwrap_some value)) + +(fn option_string_local_payload () -> string + (let value (option string) (maybe_string_value "oak")) + (unwrap_some value)) + +(fn option_string_call_payload () -> string + (unwrap_some (maybe_string_value "pine"))) + +(fn option_string_guarded_payload ((value (option string))) -> string + (if (is_some value) + (unwrap_some value) + "fallback")) + +(fn result_ok_direct_payload () -> i32 + (unwrap_ok (ok i32 i32 30))) + +(fn result_err_direct_payload () -> i32 + (unwrap_err (err i32 i32 7))) + +(fn result_ok_param_payload ((value (result i32 i32))) -> i32 + (unwrap_ok value)) + +(fn result_err_param_payload ((value (result i32 i32))) -> i32 + (unwrap_err value)) + +(fn result_ok_local_payload () -> i32 + (let value (result i32 i32) (result_ok_value 31)) + (unwrap_ok value)) + +(fn result_err_call_payload () -> i32 + (unwrap_err (result_err_value 9))) + +(test "unwrap some direct constructor" + (= (option_direct_payload) 42)) + +(test "unwrap some local" + (= (option_local_payload) 21)) + +(test "unwrap some call" + (= (option_call_payload) 22)) + +(test "unwrap some parameter" + (= (option_param_payload (maybe_value 23)) 23)) + +(test "guarded unwrap some present" + (= (option_guarded_payload (maybe_value 24)) 24)) + +(test "guarded unwrap some absent" + (= (option_guarded_payload (maybe_empty)) 0)) + +(test "unwrap some i64 direct constructor" + (= (option_wide_direct_payload) 2147483648i64)) + +(test "unwrap some i64 local" + (= (option_wide_local_payload) 21i64)) + +(test "unwrap some i64 call" + (= (option_wide_call_payload) 22i64)) + +(test "unwrap some i64 parameter" + (= (option_wide_param_payload (maybe_wide_value 23i64)) 23i64)) + +(test "guarded unwrap some i64 present" + (= (option_wide_guarded_payload (maybe_wide_value 24i64)) 24i64)) + +(test "guarded unwrap some i64 absent" + (= (option_wide_guarded_payload (maybe_wide_empty)) 0i64)) + +(test "unwrap some f64 direct constructor" + (= (option_float_direct_payload) 42.5)) + +(test "unwrap some f64 local" + (= (option_float_local_payload) 21.5)) + +(test "unwrap some f64 call" + (= (option_float_call_payload) 22.5)) + +(test "unwrap some f64 parameter" + (= (option_float_param_payload (maybe_float_value 23.5)) 23.5)) + +(test "guarded unwrap some f64 present" + (= (option_float_guarded_payload (maybe_float_value 24.5)) 24.5)) + +(test "guarded unwrap some f64 absent" + (= (option_float_guarded_payload (maybe_float_empty)) 0.0)) + +(test "unwrap some bool direct constructor" + (option_flag_direct_payload)) + +(test "unwrap some bool local" + (option_flag_local_payload)) + +(test "unwrap some bool call" + (option_flag_call_payload)) + +(test "unwrap some bool parameter" + (option_flag_param_payload (maybe_flag_value true))) + +(test "guarded unwrap some bool present" + (option_flag_guarded_payload (maybe_flag_value true))) + +(test "guarded unwrap some bool absent" + (if (option_flag_guarded_payload (maybe_flag_empty)) + false + true)) + +(test "unwrap some string direct constructor" + (= (option_string_direct_payload) "slovo")) + +(test "unwrap some string local" + (= (option_string_local_payload) "oak")) + +(test "unwrap some string call" + (= (option_string_call_payload) "pine")) + +(test "unwrap some string parameter" + (= (option_string_param_payload (maybe_string_value "birch")) "birch")) + +(test "guarded unwrap some string present" + (= (option_string_guarded_payload (maybe_string_value "cedar")) "cedar")) + +(test "guarded unwrap some string absent" + (= (option_string_guarded_payload (maybe_string_empty)) "fallback")) + +(test "unwrap ok direct constructor" + (= (result_ok_direct_payload) 30)) + +(test "unwrap err direct constructor" + (= (result_err_direct_payload) 7)) + +(test "unwrap ok parameter" + (= (result_ok_param_payload (result_ok_value 32)) 32)) + +(test "unwrap err parameter" + (= (result_err_param_payload (result_err_value 8)) 8)) + +(test "unwrap ok local" + (= (result_ok_local_payload) 31)) + +(test "unwrap err call" + (= (result_err_call_payload) 9)) + +(fn main () -> i32 + 0) diff --git a/docs/language/examples/supported/option-result.slo b/docs/language/examples/supported/option-result.slo new file mode 100644 index 0000000..d54a5fe --- /dev/null +++ b/docs/language/examples/supported/option-result.slo @@ -0,0 +1,40 @@ +(module main) + +(fn maybe_value () -> (option i32) + (some i32 42)) + +(fn maybe_empty () -> (option i32) + (none i32)) + +(fn maybe_wide_value () -> (option i64) + (some i64 2147483648i64)) + +(fn maybe_wide_empty () -> (option i64) + (none i64)) + +(fn maybe_float_value () -> (option f64) + (some f64 42.5)) + +(fn maybe_float_empty () -> (option f64) + (none f64)) + +(fn maybe_flag_value () -> (option bool) + (some bool true)) + +(fn maybe_flag_empty () -> (option bool) + (none bool)) + +(fn maybe_string_value () -> (option string) + (some string "slovo")) + +(fn maybe_string_empty () -> (option string) + (none string)) + +(fn result_ok () -> (result i32 i32) + (ok i32 i32 42)) + +(fn result_err () -> (result i32 i32) + (err i32 i32 7)) + +(fn main () -> i32 + 0) diff --git a/docs/language/examples/supported/owned-string-concat.slo b/docs/language/examples/supported/owned-string-concat.slo new file mode 100644 index 0000000..808c345 --- /dev/null +++ b/docs/language/examples/supported/owned-string-concat.slo @@ -0,0 +1,21 @@ +(module main) + +(fn join ((left string) (right string)) -> string + (std.string.concat left right)) + +(fn greeting () -> string + (std.string.concat "hello, " "slovo")) + +(fn greeting_len () -> i32 + (std.string.len (greeting))) + +(test "owned string concat equality" + (= (greeting) "hello, slovo")) + +(test "owned string concat length" + (= (greeting_len) 12)) + +(fn main () -> i32 + (std.io.print_string (join "hello, " "slovo")) + (std.io.print_i32 (std.string.len (join "ab" "cd"))) + 0) diff --git a/docs/language/examples/supported/primitive-struct-fields.slo b/docs/language/examples/supported/primitive-struct-fields.slo new file mode 100644 index 0000000..5083c6b --- /dev/null +++ b/docs/language/examples/supported/primitive-struct-fields.slo @@ -0,0 +1,66 @@ +(module main) + +(struct PrimitiveRecord + (id i32) + (active bool) + (label string)) + +(fn make_record ((id i32) (label string)) -> PrimitiveRecord + (PrimitiveRecord (id id) (active (= id 7)) (label label))) + +(fn expected_record () -> PrimitiveRecord + (make_record 7 "alpha")) + +(fn inactive_record () -> PrimitiveRecord + (PrimitiveRecord (id 3) (active false) (label "beta"))) + +(fn echo_record ((record PrimitiveRecord)) -> PrimitiveRecord + record) + +(fn local_record ((label string)) -> PrimitiveRecord + (let record PrimitiveRecord (make_record 7 label)) + (echo_record record)) + +(fn record_id ((record PrimitiveRecord)) -> i32 + (. record id)) + +(fn record_active ((record PrimitiveRecord)) -> bool + (. record active)) + +(fn record_label ((record PrimitiveRecord)) -> string + (. record label)) + +(fn label_matches ((record PrimitiveRecord) (label string)) -> bool + (= (. record label) label)) + +(fn active_score ((record PrimitiveRecord)) -> i32 + (if (. record active) + (. record id) + 0)) + +(fn label_len ((record PrimitiveRecord)) -> i32 + (std.string.len (. record label))) + +(test "primitive struct i32 field access" + (= (record_id (expected_record)) 7)) + +(test "primitive struct bool field predicate" + (record_active (expected_record))) + +(test "primitive struct bool field false branch" + (= (active_score (inactive_record)) 0)) + +(test "primitive struct string field equality" + (label_matches (expected_record) "alpha")) + +(test "primitive struct string field length" + (= (label_len (expected_record)) 5)) + +(test "primitive struct local param return call flow" + (= (active_score (local_record "alpha")) 7)) + +(test "primitive struct string field access return" + (= (record_label (local_record "alpha")) "alpha")) + +(fn main () -> i32 + (active_score (local_record "alpha"))) diff --git a/docs/language/examples/supported/print-bool.slo b/docs/language/examples/supported/print-bool.slo new file mode 100644 index 0000000..04d8439 --- /dev/null +++ b/docs/language/examples/supported/print-bool.slo @@ -0,0 +1,7 @@ +(module main) + +(fn main () -> i32 + (print_bool true) + (print_bool false) + (print_bool (= "slovo" "slovo")) + 0) diff --git a/docs/language/examples/supported/random.slo b/docs/language/examples/supported/random.slo new file mode 100644 index 0000000..fb387ce --- /dev/null +++ b/docs/language/examples/supported/random.slo @@ -0,0 +1,16 @@ +(module main) + +(fn random_i32 () -> i32 + (std.random.i32)) + +(fn random_is_non_negative () -> bool + (if (< (random_i32) 0) + false + true)) + +(test "random i32 is non-negative" + (random_is_non_negative)) + +(fn main () -> i32 + (std.io.print_bool (random_is_non_negative)) + 0) diff --git a/docs/language/examples/supported/result-f64-bool-match.slo b/docs/language/examples/supported/result-f64-bool-match.slo new file mode 100644 index 0000000..9f9801c --- /dev/null +++ b/docs/language/examples/supported/result-f64-bool-match.slo @@ -0,0 +1,62 @@ +(module main) + +(fn f64_ok () -> (result f64 i32) + (ok f64 i32 1.5)) + +(fn f64_err () -> (result f64 i32) + (err f64 i32 7)) + +(fn bool_ok () -> (result bool i32) + (ok bool i32 true)) + +(fn bool_err () -> (result bool i32) + (err bool i32 9)) + +(fn f64_ok_value () -> f64 + (match (f64_ok) + ((ok value) + value) + ((err code) + 0.0))) + +(fn f64_err_code () -> i32 + (match (f64_err) + ((ok value) + 0) + ((err code) + code))) + +(fn bool_ok_value () -> bool + (match (bool_ok) + ((ok value) + value) + ((err code) + false))) + +(fn bool_err_code () -> i32 + (match (bool_err) + ((ok value) + 0) + ((err code) + code))) + +(fn main () -> i32 + (if (= (f64_ok_value) 1.5) + (if (= (f64_err_code) 7) + (if (bool_ok_value) + (bool_err_code) + 1) + 1) + 1)) + +(test "result f64 ok constructor and match" + (= (f64_ok_value) 1.5)) + +(test "result f64 err constructor and match" + (= (f64_err_code) 7)) + +(test "result bool ok constructor and match" + (bool_ok_value)) + +(test "result bool err constructor and match" + (= (bool_err_code) 9)) diff --git a/docs/language/examples/supported/result-helpers.slo b/docs/language/examples/supported/result-helpers.slo new file mode 100644 index 0000000..70ace9d --- /dev/null +++ b/docs/language/examples/supported/result-helpers.slo @@ -0,0 +1,71 @@ +(module main) + +(fn i32_ok () -> (result i32 i32) + (ok i32 i32 42)) + +(fn i32_err () -> (result i32 i32) + (err i32 i32 7)) + +(fn text_ok () -> (result string i32) + (ok string i32 "value")) + +(fn text_err () -> (result string i32) + (err string i32 9)) + +(fn observe_i32_ok () -> bool + (std.result.is_ok (i32_ok))) + +(fn observe_i32_err () -> bool + (std.result.is_err (i32_err))) + +(fn unwrap_i32_ok () -> i32 + (std.result.unwrap_ok (i32_ok))) + +(fn unwrap_i32_err () -> i32 + (std.result.unwrap_err (i32_err))) + +(fn observe_text_ok () -> bool + (std.result.is_ok (text_ok))) + +(fn observe_text_err () -> bool + (std.result.is_err (text_err))) + +(fn unwrap_text_ok () -> string + (std.result.unwrap_ok (text_ok))) + +(fn unwrap_text_err () -> i32 + (std.result.unwrap_err (text_err))) + +(fn legacy_i32_ok () -> bool + (is_ok (i32_ok))) + +(fn legacy_text_err () -> i32 + (unwrap_err (text_err))) + +(test "std result i32 observers" + (if (std.result.is_ok (i32_ok)) + (std.result.is_err (i32_err)) + false)) + +(test "std result i32 unwraps" + (= (+ (std.result.unwrap_ok (i32_ok)) (std.result.unwrap_err (i32_err))) 49)) + +(test "std result string observers" + (if (std.result.is_ok (text_ok)) + (std.result.is_err (text_err)) + false)) + +(test "std result string unwraps" + (if (= (std.result.unwrap_ok (text_ok)) "value") + (= (std.result.unwrap_err (text_err)) 9) + false)) + +(test "legacy result helpers still work" + (if (is_ok (i32_ok)) + (= (unwrap_err (text_err)) 9) + false)) + +(fn main () -> i32 + (if (std.result.is_ok (i32_ok)) + (std.result.unwrap_ok (i32_ok)) + (std.result.unwrap_err (i32_err)))) diff --git a/docs/language/examples/supported/standard-runtime.slo b/docs/language/examples/supported/standard-runtime.slo new file mode 100644 index 0000000..607fd52 --- /dev/null +++ b/docs/language/examples/supported/standard-runtime.slo @@ -0,0 +1,22 @@ +(module main) + +(fn label () -> string + "standard") + +(fn echo ((value string)) -> string + value) + +(fn label_len () -> i32 + (std.string.len (echo "standard"))) + +(test "std string equality" + (= (echo "standard") "standard")) + +(test "std string byte length" + (= (label_len) 8)) + +(fn main () -> i32 + (std.io.print_string (echo "standard")) + (std.io.print_bool (= (echo "standard") "standard")) + (std.io.print_i32 (std.string.len "standard")) + 0) diff --git a/docs/language/examples/supported/stdin-result.slo b/docs/language/examples/supported/stdin-result.slo new file mode 100644 index 0000000..2a55836 --- /dev/null +++ b/docs/language/examples/supported/stdin-result.slo @@ -0,0 +1,38 @@ +(module main) + +(fn stdin_result () -> (result string i32) + (std.io.read_stdin_result)) + +(fn stdin_text_or_empty ((value (result string i32))) -> string + (match value + ((ok text) + text) + ((err code) + ""))) + +(fn stdin_len_or_code ((value (result string i32))) -> i32 + (match value + ((ok text) + (std.string.len text)) + ((err code) + code))) + +(fn stdin_ok_len () -> i32 + (std.string.len (unwrap_ok (stdin_result)))) + +(test "stdin result test runner returns ok" + (is_ok (stdin_result))) + +(test "stdin result payload length matches match" + (let value (result string i32) (stdin_result)) + (= (std.string.len (unwrap_ok value)) (stdin_len_or_code value))) + +(test "stdin result match observes ok payload" + (let value (result string i32) (stdin_result)) + (= (std.string.len (stdin_text_or_empty value)) (stdin_len_or_code value))) + +(fn main () -> i32 + (let value (result string i32) (stdin_result)) + (if (is_ok value) + (std.string.len (unwrap_ok value)) + 1)) diff --git a/docs/language/examples/supported/string-parse-bool-result.slo b/docs/language/examples/supported/string-parse-bool-result.slo new file mode 100644 index 0000000..881f083 --- /dev/null +++ b/docs/language/examples/supported/string-parse-bool-result.slo @@ -0,0 +1,55 @@ +(module main) + +(fn parse_bool ((text string)) -> (result bool i32) + (std.string.parse_bool_result text)) + +(fn bool_score ((text string)) -> i32 + (let value (result bool i32) (parse_bool text)) + (if (std.result.is_ok value) + (if (std.result.unwrap_ok value) + 1 + 0) + (std.result.unwrap_err value))) + +(test "parse bool true ok" + (let value (result bool i32) (parse_bool "true")) + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + false)) + +(test "parse bool false ok" + (let value (result bool i32) (parse_bool "false")) + (if (std.result.is_ok value) + (if (std.result.unwrap_ok value) + false + true) + false)) + +(test "parse bool uppercase err" + (let value (result bool i32) (parse_bool "TRUE")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "parse bool empty err" + (let value (result bool i32) (parse_bool "")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "parse bool whitespace err" + (let value (result bool i32) (parse_bool " true")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "parse bool helper flow" + (= (+ (bool_score "true") (bool_score "false")) 1)) + +(fn main () -> i32 + (let value (result bool i32) (parse_bool "true")) + (if (std.result.is_ok value) + (if (std.result.unwrap_ok value) + 0 + 1) + (std.result.unwrap_err value))) diff --git a/docs/language/examples/supported/string-parse-f64-result.slo b/docs/language/examples/supported/string-parse-f64-result.slo new file mode 100644 index 0000000..6d48b57 --- /dev/null +++ b/docs/language/examples/supported/string-parse-f64-result.slo @@ -0,0 +1,36 @@ +(module main) + +(fn parse_f64 ((text string)) -> (result f64 i32) + (std.string.parse_f64_result text)) + +(test "parse f64 decimal ok" + (let value (result f64 i32) (parse_f64 "12.5")) + (if (std.result.is_ok value) + (= (std.result.unwrap_ok value) 12.5) + false)) + +(test "parse f64 negative decimal ok" + (let value (result f64 i32) (parse_f64 "-0.25")) + (if (std.result.is_ok value) + (= (std.result.unwrap_ok value) (- 0.0 0.25)) + false)) + +(test "parse f64 text err" + (let value (result f64 i32) (parse_f64 "abc")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "parse f64 nan err" + (let value (result f64 i32) (parse_f64 "nan")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(fn main () -> i32 + (let value (result f64 i32) (parse_f64 "-0.25")) + (if (std.result.is_ok value) + (if (= (std.result.unwrap_ok value) (- 0.0 0.25)) + 0 + 1) + (std.result.unwrap_err value))) diff --git a/docs/language/examples/supported/string-parse-i32-result.slo b/docs/language/examples/supported/string-parse-i32-result.slo new file mode 100644 index 0000000..6f02489 --- /dev/null +++ b/docs/language/examples/supported/string-parse-i32-result.slo @@ -0,0 +1,41 @@ +(module main) + +(fn parse_text ((text string)) -> (result i32 i32) + (std.string.parse_i32_result text)) + +(fn parse_stdin_text () -> (result i32 i32) + (let input (result string i32) (std.io.read_stdin_result)) + (match input + ((ok text) + (std.string.parse_i32_result text)) + ((err code) + (err i32 i32 code)))) + +(test "parse 42 ok" + (= (unwrap_ok (parse_text "42")) 42)) + +(test "parse negative 7 ok" + (= (unwrap_ok (parse_text "-7")) -7)) + +(test "parse empty err" + (= (unwrap_err (parse_text "")) 1)) + +(test "parse trailing byte err" + (= (unwrap_err (parse_text "12x")) 1)) + +(test "parse overflow err" + (= (unwrap_err (parse_text "2147483648")) 1)) + +(test "parse stdin text structurally" + (let value (result i32 i32) (parse_stdin_text)) + (match value + ((ok parsed) + (= parsed parsed)) + ((err code) + (= code 1)))) + +(fn main () -> i32 + (let value (result i32 i32) (parse_stdin_text)) + (if (is_ok value) + (unwrap_ok value) + (unwrap_err value))) diff --git a/docs/language/examples/supported/string-parse-i64-result.slo b/docs/language/examples/supported/string-parse-i64-result.slo new file mode 100644 index 0000000..2b35533 --- /dev/null +++ b/docs/language/examples/supported/string-parse-i64-result.slo @@ -0,0 +1,45 @@ +(module main) + +(fn parse_i64 ((text string)) -> (result i64 i32) + (std.string.parse_i64_result text)) + +(fn parsed_text ((text string)) -> string + (std.num.i64_to_string (std.result.unwrap_ok (parse_i64 text)))) + +(test "parse i64 zero ok" + (= (parsed_text "0") "0")) + +(test "parse i64 negative ok" + (= (parsed_text "-7") "-7")) + +(test "parse i64 low ok" + (= (parsed_text "-9223372036854775808") "-9223372036854775808")) + +(test "parse i64 high ok" + (= (parsed_text "9223372036854775807") "9223372036854775807")) + +(test "parse i64 empty err" + (let value (result i64 i32) (parse_i64 "")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "parse i64 plus err" + (let value (result i64 i32) (parse_i64 "+1")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "parse i64 above range err" + (let value (result i64 i32) (parse_i64 "9223372036854775808")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(fn main () -> i32 + (let value (result i64 i32) (parse_i64 "-7")) + (if (std.result.is_ok value) + (if (= (std.num.i64_to_string (std.result.unwrap_ok value)) "-7") + 0 + 1) + (std.result.unwrap_err value))) diff --git a/docs/language/examples/supported/string-parse-u32-result.slo b/docs/language/examples/supported/string-parse-u32-result.slo new file mode 100644 index 0000000..76fad8d --- /dev/null +++ b/docs/language/examples/supported/string-parse-u32-result.slo @@ -0,0 +1,45 @@ +(module main) + +(fn parse_u32 ((text string)) -> (result u32 i32) + (std.string.parse_u32_result text)) + +(fn parsed_text ((text string)) -> string + (std.num.u32_to_string (std.result.unwrap_ok (parse_u32 text)))) + +(test "parse u32 zero ok" + (= (parsed_text "0") "0")) + +(test "parse u32 high ok" + (= (parsed_text "4294967295") "4294967295")) + +(test "parse u32 empty err" + (let value (result u32 i32) (parse_u32 "")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "parse u32 plus err" + (let value (result u32 i32) (parse_u32 "+1")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "parse u32 negative err" + (let value (result u32 i32) (parse_u32 "-1")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "parse u32 above range err" + (let value (result u32 i32) (parse_u32 "4294967296")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(fn main () -> i32 + (let value (result u32 i32) (parse_u32 "42")) + (if (std.result.is_ok value) + (if (= (std.num.u32_to_string (std.result.unwrap_ok value)) "42") + 0 + 1) + (std.result.unwrap_err value))) diff --git a/docs/language/examples/supported/string-parse-u64-result.slo b/docs/language/examples/supported/string-parse-u64-result.slo new file mode 100644 index 0000000..362ac5f --- /dev/null +++ b/docs/language/examples/supported/string-parse-u64-result.slo @@ -0,0 +1,45 @@ +(module main) + +(fn parse_u64 ((text string)) -> (result u64 i32) + (std.string.parse_u64_result text)) + +(fn parsed_text ((text string)) -> string + (std.num.u64_to_string (std.result.unwrap_ok (parse_u64 text)))) + +(test "parse u64 zero ok" + (= (parsed_text "0") "0")) + +(test "parse u64 high ok" + (= (parsed_text "18446744073709551615") "18446744073709551615")) + +(test "parse u64 empty err" + (let value (result u64 i32) (parse_u64 "")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "parse u64 plus err" + (let value (result u64 i32) (parse_u64 "+1")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "parse u64 negative err" + (let value (result u64 i32) (parse_u64 "-1")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "parse u64 above range err" + (let value (result u64 i32) (parse_u64 "18446744073709551616")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(fn main () -> i32 + (let value (result u64 i32) (parse_u64 "42")) + (if (std.result.is_ok value) + (if (= (std.num.u64_to_string (std.result.unwrap_ok value)) "42") + 0 + 1) + (std.result.unwrap_err value))) diff --git a/docs/language/examples/supported/string-print.slo b/docs/language/examples/supported/string-print.slo new file mode 100644 index 0000000..abd8fa5 --- /dev/null +++ b/docs/language/examples/supported/string-print.slo @@ -0,0 +1,6 @@ +(module main) + +(fn main () -> i32 + (print_string "hello") + (print_string "line\nquote\"slash\\tab\t") + 0) diff --git a/docs/language/examples/supported/string-value-flow.slo b/docs/language/examples/supported/string-value-flow.slo new file mode 100644 index 0000000..8780425 --- /dev/null +++ b/docs/language/examples/supported/string-value-flow.slo @@ -0,0 +1,30 @@ +(module main) + +(fn label () -> string + "slovo") + +(fn echo ((value string)) -> string + value) + +(fn local_label () -> string + (let value string (label)) + value) + +(fn label_len () -> i32 + (string_len (local_label))) + +(test "string literal equality" + (= "slovo" "slovo")) + +(test "string parameter equality" + (= (echo "runtime") "runtime")) + +(test "string call return equality" + (= (local_label) "slovo")) + +(test "string byte length" + (= (label_len) 5)) + +(fn main () -> i32 + (print_string (local_label)) + (label_len)) diff --git a/docs/language/examples/supported/struct-value-flow.slo b/docs/language/examples/supported/struct-value-flow.slo new file mode 100644 index 0000000..6b73912 --- /dev/null +++ b/docs/language/examples/supported/struct-value-flow.slo @@ -0,0 +1,31 @@ +(module main) + +(struct Point + (x i32) + (y i32)) + +(fn make_point ((x i32) (y i32)) -> Point + (Point (x x) (y y))) + +(fn point_x ((p Point)) -> i32 + (. p x)) + +(fn point_sum ((p Point)) -> i32 + (+ (. p x) (. p y))) + +(fn local_point_sum () -> i32 + (let p Point (make_point 20 22)) + (point_sum p)) + +(test "struct local value flow" + (= (local_point_sum) 42)) + +(test "struct parameter value flow" + (= (point_x (make_point 7 9)) 7)) + +(test "stored struct field access" + (let p Point (make_point 3 4)) + (= (. p y) 4)) + +(fn main () -> i32 + (local_point_sum)) diff --git a/docs/language/examples/supported/struct.slo b/docs/language/examples/supported/struct.slo new file mode 100644 index 0000000..1cecdcb --- /dev/null +++ b/docs/language/examples/supported/struct.slo @@ -0,0 +1,17 @@ +(module main) + +(struct Point + (x i32) + (y i32)) + +(fn point_sum () -> i32 + (+ (. (Point (x 20) (y 22)) x) (. (Point (x 20) (y 22)) y))) + +(test "struct field access" + (= (point_sum) 42)) + +(test "struct field compares" + (= (. (Point (x 7) (y 9)) y) 9)) + +(fn main () -> i32 + (point_sum)) diff --git a/docs/language/examples/supported/time-sleep.slo b/docs/language/examples/supported/time-sleep.slo new file mode 100644 index 0000000..f01577c --- /dev/null +++ b/docs/language/examples/supported/time-sleep.slo @@ -0,0 +1,21 @@ +(module main) + +(fn monotonic_self_equal () -> bool + (let now i32 (std.time.monotonic_ms)) + (= now now)) + +(fn sleep_zero_then_self_equal () -> bool + (std.time.sleep_ms 0) + (monotonic_self_equal)) + +(test "monotonic value is self equal" + (monotonic_self_equal)) + +(test "sleep zero returns" + (sleep_zero_then_self_equal)) + +(fn main () -> i32 + (std.time.sleep_ms 0) + (if (monotonic_self_equal) + 0 + 1)) diff --git a/docs/language/examples/supported/top-level-test.slo b/docs/language/examples/supported/top-level-test.slo new file mode 100644 index 0000000..284e49e --- /dev/null +++ b/docs/language/examples/supported/top-level-test.slo @@ -0,0 +1,10 @@ +(module tests) + +(fn add ((a i32) (b i32)) -> i32 + (+ a b)) + +(test "add works" + (= (add 2 3) 5)) + +(fn main () -> i32 + 0) diff --git a/docs/language/examples/supported/u32-numeric-primitive.slo b/docs/language/examples/supported/u32-numeric-primitive.slo new file mode 100644 index 0000000..672e636 --- /dev/null +++ b/docs/language/examples/supported/u32-numeric-primitive.slo @@ -0,0 +1,39 @@ +(module main) + +(fn base () -> u32 + 1073741824u32) + +(fn adjust ((value u32) (delta u32)) -> u32 + (+ value delta)) + +(fn doubled ((value u32)) -> u32 + (* value 2u32)) + +(fn local_total () -> u32 + (let offset u32 15u32) + (adjust (doubled (base)) offset)) + +(fn high_enough ((value u32)) -> bool + (if (> value 2147483660u32) + (< value 2147483670u32) + false)) + +(fn exact_u32 () -> bool + (= (local_total) 2147483663u32)) + +(fn main () -> i32 + (std.io.print_u32 (local_total)) + (if (high_enough (local_total)) + 0 + 1)) + +(test "u32 arithmetic returns exact fixture value" + (exact_u32)) + +(test "u32 comparison works in predicates" + (high_enough (local_total))) + +(test "u32 division and ordering" + (if (>= (/ (local_total) 3u32) 715827887u32) + (<= (/ (local_total) 3u32) 715827887u32) + false)) diff --git a/docs/language/examples/supported/u64-numeric-primitive.slo b/docs/language/examples/supported/u64-numeric-primitive.slo new file mode 100644 index 0000000..6d3441d --- /dev/null +++ b/docs/language/examples/supported/u64-numeric-primitive.slo @@ -0,0 +1,39 @@ +(module main) + +(fn base () -> u64 + 4294967296u64) + +(fn adjust ((value u64) (delta u64)) -> u64 + (+ value delta)) + +(fn doubled ((value u64)) -> u64 + (* value 2u64)) + +(fn local_total () -> u64 + (let offset u64 19u64) + (adjust (/ (doubled (base)) 2u64) offset)) + +(fn high_enough ((value u64)) -> bool + (if (> value 4294967300u64) + (< value 4294967320u64) + false)) + +(fn exact_u64 () -> bool + (= (local_total) 4294967315u64)) + +(fn main () -> i32 + (std.io.print_u64 (local_total)) + (if (high_enough (local_total)) + 0 + 1)) + +(test "u64 arithmetic returns exact fixture value" + (exact_u64)) + +(test "u64 comparison works in predicates" + (high_enough (local_total))) + +(test "u64 division and ordering" + (if (>= (/ (local_total) 5u64) 858993463u64) + (<= (/ (local_total) 5u64) 858993463u64) + false)) diff --git a/docs/language/examples/supported/unsafe.slo b/docs/language/examples/supported/unsafe.slo new file mode 100644 index 0000000..ab8651c --- /dev/null +++ b/docs/language/examples/supported/unsafe.slo @@ -0,0 +1,16 @@ +(module main) + +(fn add_one_in_unsafe ((value i32)) -> i32 + (unsafe + (let one i32 1) + (+ value one))) + +(test "unsafe block returns final value" + (= (add_one_in_unsafe 4) 5)) + +(test "unsafe block can return bool" + (unsafe + (= (add_one_in_unsafe 1) 2))) + +(fn main () -> i32 + (add_one_in_unsafe 41)) diff --git a/docs/language/examples/supported/unsigned-integer-to-string.slo b/docs/language/examples/supported/unsigned-integer-to-string.slo new file mode 100644 index 0000000..1ef0efe --- /dev/null +++ b/docs/language/examples/supported/unsigned-integer-to-string.slo @@ -0,0 +1,47 @@ +(module main) + +(fn u32_zero_text () -> string + (std.num.u32_to_string 0u32)) + +(fn u32_high_text () -> string + (std.num.u32_to_string 4294967295u32)) + +(fn u64_zero_text () -> string + (std.num.u64_to_string 0u64)) + +(fn u64_high_text () -> string + (std.num.u64_to_string 18446744073709551615u64)) + +(fn u64_beyond_u32_text () -> string + (std.num.u64_to_string 4294967296u64)) + +(test "u32 zero to string" + (= (u32_zero_text) "0")) + +(test "u32 high to string" + (= (u32_high_text) "4294967295")) + +(test "u32 high string length" + (= (std.string.len (u32_high_text)) 10)) + +(test "u64 zero to string" + (= (u64_zero_text) "0")) + +(test "u64 high to string" + (= (u64_high_text) "18446744073709551615")) + +(test "u64 beyond u32 to string" + (= (u64_beyond_u32_text) "4294967296")) + +(test "u64 high string length" + (= (std.string.len (u64_high_text)) 20)) + +(fn main () -> i32 + (std.io.print_string (u32_zero_text)) + (std.io.print_string (u32_high_text)) + (std.io.print_string (u64_zero_text)) + (std.io.print_string (u64_high_text)) + (std.io.print_string (u64_beyond_u32_text)) + (if (= (std.string.len (u64_beyond_u32_text)) 10) + 0 + 1)) diff --git a/docs/language/examples/supported/vec-bool.slo b/docs/language/examples/supported/vec-bool.slo new file mode 100644 index 0000000..4d327b3 --- /dev/null +++ b/docs/language/examples/supported/vec-bool.slo @@ -0,0 +1,45 @@ +(module main) + +(fn empty_values () -> (vec bool) + (std.vec.bool.empty)) + +(fn pair () -> (vec bool) + (let values (vec bool) (std.vec.bool.empty)) + (let first (vec bool) (std.vec.bool.append values true)) + (std.vec.bool.append first false)) + +(fn echo ((values (vec bool))) -> (vec bool) + values) + +(fn length ((values (vec bool))) -> i32 + (std.vec.bool.len values)) + +(fn at ((values (vec bool)) (i i32)) -> bool + (std.vec.bool.index values i)) + +(fn call_return_value () -> bool + (at (echo (pair)) 1)) + +(fn original_len_after_append () -> i32 + (let values (vec bool) (std.vec.bool.empty)) + (let appended (vec bool) (std.vec.bool.append values true)) + (std.vec.bool.len values)) + +(test "vec bool empty length" + (= (std.vec.bool.len (empty_values)) 0)) + +(test "vec bool append length" + (= (length (pair)) 2)) + +(test "vec bool index" + (= (at (pair) 1) false)) + +(test "vec bool append is immutable" + (= (original_len_after_append) 0)) + +(test "vec bool equality" + (= (pair) (std.vec.bool.append (std.vec.bool.append (std.vec.bool.empty) true) false))) + +(fn main () -> i32 + (std.io.print_bool (call_return_value)) + 0) diff --git a/docs/language/examples/supported/vec-f64.slo b/docs/language/examples/supported/vec-f64.slo new file mode 100644 index 0000000..d5e6d62 --- /dev/null +++ b/docs/language/examples/supported/vec-f64.slo @@ -0,0 +1,45 @@ +(module main) + +(fn empty_values () -> (vec f64) + (std.vec.f64.empty)) + +(fn pair ((base f64)) -> (vec f64) + (let values (vec f64) (std.vec.f64.empty)) + (let first (vec f64) (std.vec.f64.append values base)) + (std.vec.f64.append first (+ base 1.0))) + +(fn echo ((values (vec f64))) -> (vec f64) + values) + +(fn length ((values (vec f64))) -> i32 + (std.vec.f64.len values)) + +(fn at ((values (vec f64)) (i i32)) -> f64 + (std.vec.f64.index values i)) + +(fn call_return_value () -> f64 + (at (echo (pair 2147483648.0)) 1)) + +(fn original_len_after_append () -> i32 + (let values (vec f64) (std.vec.f64.empty)) + (let appended (vec f64) (std.vec.f64.append values 1.0)) + (std.vec.f64.len values)) + +(test "vec f64 empty length" + (= (std.vec.f64.len (empty_values)) 0)) + +(test "vec f64 append length" + (= (length (pair 40.0)) 2)) + +(test "vec f64 index" + (= (at (pair 40.0) 1) 41.0)) + +(test "vec f64 append is immutable" + (= (original_len_after_append) 0)) + +(test "vec f64 equality" + (= (pair 5.0) (std.vec.f64.append (std.vec.f64.append (std.vec.f64.empty) 5.0) 6.0))) + +(fn main () -> i32 + (std.io.print_f64 (call_return_value)) + 0) diff --git a/docs/language/examples/supported/vec-i32.slo b/docs/language/examples/supported/vec-i32.slo new file mode 100644 index 0000000..ce3cda0 --- /dev/null +++ b/docs/language/examples/supported/vec-i32.slo @@ -0,0 +1,45 @@ +(module main) + +(fn empty_values () -> (vec i32) + (std.vec.i32.empty)) + +(fn pair ((base i32)) -> (vec i32) + (let values (vec i32) (std.vec.i32.empty)) + (let first (vec i32) (std.vec.i32.append values base)) + (std.vec.i32.append first (+ base 1))) + +(fn echo ((values (vec i32))) -> (vec i32) + values) + +(fn length ((values (vec i32))) -> i32 + (std.vec.i32.len values)) + +(fn at ((values (vec i32)) (i i32)) -> i32 + (std.vec.i32.index values i)) + +(fn call_return_len () -> i32 + (std.vec.i32.len (echo (pair 20)))) + +(fn original_len_after_append () -> i32 + (let values (vec i32) (std.vec.i32.empty)) + (let appended (vec i32) (std.vec.i32.append values 1)) + (std.vec.i32.len values)) + +(test "vec i32 empty length" + (= (std.vec.i32.len (empty_values)) 0)) + +(test "vec i32 append length" + (= (length (pair 40)) 2)) + +(test "vec i32 index" + (= (at (pair 40) 1) 41)) + +(test "vec i32 append is immutable" + (= (original_len_after_append) 0)) + +(test "vec i32 equality" + (= (pair 5) (std.vec.i32.append (std.vec.i32.append (std.vec.i32.empty) 5) 6))) + +(fn main () -> i32 + (std.io.print_i32 (call_return_len)) + (at (pair 40) 1)) diff --git a/docs/language/examples/supported/vec-i64.slo b/docs/language/examples/supported/vec-i64.slo new file mode 100644 index 0000000..dba0e81 --- /dev/null +++ b/docs/language/examples/supported/vec-i64.slo @@ -0,0 +1,45 @@ +(module main) + +(fn empty_values () -> (vec i64) + (std.vec.i64.empty)) + +(fn pair ((base i64)) -> (vec i64) + (let values (vec i64) (std.vec.i64.empty)) + (let first (vec i64) (std.vec.i64.append values base)) + (std.vec.i64.append first (+ base 1i64))) + +(fn echo ((values (vec i64))) -> (vec i64) + values) + +(fn length ((values (vec i64))) -> i32 + (std.vec.i64.len values)) + +(fn at ((values (vec i64)) (i i32)) -> i64 + (std.vec.i64.index values i)) + +(fn call_return_value () -> i64 + (at (echo (pair 2147483648i64)) 1)) + +(fn original_len_after_append () -> i32 + (let values (vec i64) (std.vec.i64.empty)) + (let appended (vec i64) (std.vec.i64.append values 1i64)) + (std.vec.i64.len values)) + +(test "vec i64 empty length" + (= (std.vec.i64.len (empty_values)) 0)) + +(test "vec i64 append length" + (= (length (pair 40i64)) 2)) + +(test "vec i64 index" + (= (at (pair 40i64) 1) 41i64)) + +(test "vec i64 append is immutable" + (= (original_len_after_append) 0)) + +(test "vec i64 equality" + (= (pair 5i64) (std.vec.i64.append (std.vec.i64.append (std.vec.i64.empty) 5i64) 6i64))) + +(fn main () -> i32 + (std.io.print_i64 (call_return_value)) + 0) diff --git a/docs/language/examples/supported/vec-string.slo b/docs/language/examples/supported/vec-string.slo new file mode 100644 index 0000000..f7fcf42 --- /dev/null +++ b/docs/language/examples/supported/vec-string.slo @@ -0,0 +1,45 @@ +(module main) + +(fn empty_values () -> (vec string) + (std.vec.string.empty)) + +(fn pair ((first string) (second string)) -> (vec string) + (let values (vec string) (std.vec.string.empty)) + (let first_values (vec string) (std.vec.string.append values first)) + (std.vec.string.append first_values second)) + +(fn echo ((values (vec string))) -> (vec string) + values) + +(fn length ((values (vec string))) -> i32 + (std.vec.string.len values)) + +(fn at ((values (vec string)) (i i32)) -> string + (std.vec.string.index values i)) + +(fn call_return_value () -> string + (at (echo (pair "slovo" "tree")) 1)) + +(fn original_len_after_append () -> i32 + (let values (vec string) (std.vec.string.empty)) + (let appended (vec string) (std.vec.string.append values "branch")) + (std.vec.string.len values)) + +(test "vec string empty length" + (= (std.vec.string.len (empty_values)) 0)) + +(test "vec string append length" + (= (length (pair "alpha" "beta")) 2)) + +(test "vec string index" + (= (at (pair "slovo" "tree") 1) "tree")) + +(test "vec string append is immutable" + (= (original_len_after_append) 0)) + +(test "vec string equality" + (= (pair "leaf" "root") (std.vec.string.append (std.vec.string.append (std.vec.string.empty) "leaf") "root"))) + +(fn main () -> i32 + (std.io.print_string (call_return_value)) + 0) diff --git a/docs/language/examples/supported/while.slo b/docs/language/examples/supported/while.slo new file mode 100644 index 0000000..6c60d5b --- /dev/null +++ b/docs/language/examples/supported/while.slo @@ -0,0 +1,22 @@ +(module main) + +(fn count_to ((limit i32)) -> i32 + (var i i32 0) + (while (< i limit) + (set i (+ i 1))) + i) + +(test "while counts" + (var i i32 0) + (while (< i 3) + (set i (+ i 1))) + (= i 3)) + +(test "while false skips" + (var i i32 0) + (while false + (set i (+ i 1))) + (= i 0)) + +(fn main () -> i32 + (count_to 4)) diff --git a/docs/language/examples/workspaces/exp-5-local/packages/app/slovo.toml b/docs/language/examples/workspaces/exp-5-local/packages/app/slovo.toml new file mode 100644 index 0000000..1f1d13d --- /dev/null +++ b/docs/language/examples/workspaces/exp-5-local/packages/app/slovo.toml @@ -0,0 +1,8 @@ +[package] +name = "app" +version = "0.1.0" +source_root = "src" +entry = "main" + +[dependencies] +mathlib = { path = "../mathlib" } diff --git a/docs/language/examples/workspaces/exp-5-local/packages/app/src/main.slo b/docs/language/examples/workspaces/exp-5-local/packages/app/src/main.slo new file mode 100644 index 0000000..69f19ed --- /dev/null +++ b/docs/language/examples/workspaces/exp-5-local/packages/app/src/main.slo @@ -0,0 +1,10 @@ +(module main) + +(import mathlib.math (add_one)) + +(fn main () -> i32 + (print_i32 (add_one 41)) + 0) + +(test "package import add one" + (= (add_one 41) 42)) diff --git a/docs/language/examples/workspaces/exp-5-local/packages/mathlib/slovo.toml b/docs/language/examples/workspaces/exp-5-local/packages/mathlib/slovo.toml new file mode 100644 index 0000000..1db226b --- /dev/null +++ b/docs/language/examples/workspaces/exp-5-local/packages/mathlib/slovo.toml @@ -0,0 +1,4 @@ +[package] +name = "mathlib" +version = "0.1.0" +source_root = "src" diff --git a/docs/language/examples/workspaces/exp-5-local/packages/mathlib/src/math.slo b/docs/language/examples/workspaces/exp-5-local/packages/mathlib/src/math.slo new file mode 100644 index 0000000..9933d58 --- /dev/null +++ b/docs/language/examples/workspaces/exp-5-local/packages/mathlib/src/math.slo @@ -0,0 +1,7 @@ +(module math (export add_one)) + +(fn add_one ((value i32)) -> i32 + (+ value 1)) + +(test "mathlib add one" + (= (add_one 41) 42)) diff --git a/docs/language/examples/workspaces/exp-5-local/slovo.toml b/docs/language/examples/workspaces/exp-5-local/slovo.toml new file mode 100644 index 0000000..13f7ff8 --- /dev/null +++ b/docs/language/examples/workspaces/exp-5-local/slovo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["packages/app", "packages/mathlib"] diff --git a/docs/language/examples/workspaces/std-import-option/packages/app/slovo.toml b/docs/language/examples/workspaces/std-import-option/packages/app/slovo.toml new file mode 100644 index 0000000..46096d0 --- /dev/null +++ b/docs/language/examples/workspaces/std-import-option/packages/app/slovo.toml @@ -0,0 +1,5 @@ +[package] +name = "app" +version = "0.1.0" +source_root = "src" +entry = "main" diff --git a/docs/language/examples/workspaces/std-import-option/packages/app/src/main.slo b/docs/language/examples/workspaces/std-import-option/packages/app/src/main.slo new file mode 100644 index 0000000..69229cc --- /dev/null +++ b/docs/language/examples/workspaces/std-import-option/packages/app/src/main.slo @@ -0,0 +1,111 @@ +(module main) + +(import std.option (is_some_i32 is_none_i32 unwrap_or_i32 some_or_err_i32 some_i64 none_i64 unwrap_or_i64 some_or_err_i64 some_f64 none_f64 unwrap_or_f64 some_or_err_f64 some_bool none_bool unwrap_or_bool some_or_err_bool some_string none_string unwrap_or_string some_or_err_string)) + +(fn some_value ((value i32)) -> (option i32) + (some i32 value)) + +(fn none_value () -> (option i32) + (none i32)) + +(fn workspace_i32_bridge_ok () -> bool + (if (match (some_or_err_i32 (some_value 42) 5) + ((ok payload) + (= payload 42)) + ((err code) + false)) + (match (some_or_err_i32 (none_value) 5) + ((ok payload) + false) + ((err code) + (= code 5))) + false)) + +(fn workspace_i64_bridge_ok () -> bool + (if (= (+ (unwrap_or_i64 (some_i64 40i64) 0i64) (unwrap_or_i64 (none_i64) 2i64)) 42i64) + (if (match (some_or_err_i64 (some_i64 42i64) 6) + ((ok payload) + (= payload 42i64)) + ((err code) + false)) + (match (some_or_err_i64 (none_i64) 6) + ((ok payload) + false) + ((err code) + (= code 6))) + false) + false)) + +(fn workspace_f64_bridge_ok () -> bool + (if (= (+ (unwrap_or_f64 (some_f64 40.0) 0.0) (unwrap_or_f64 (none_f64) 2.0)) 42.0) + (if (match (some_or_err_f64 (some_f64 42.5) 7) + ((ok payload) + (= payload 42.5)) + ((err code) + false)) + (match (some_or_err_f64 (none_f64) 7) + ((ok payload) + false) + ((err code) + (= code 7))) + false) + false)) + +(fn workspace_bool_bridge_ok () -> bool + (if (unwrap_or_bool (some_bool true) false) + (if (unwrap_or_bool (none_bool) true) + (if (match (some_or_err_bool (some_bool true) 8) + ((ok payload) + payload) + ((err code) + false)) + (match (some_or_err_bool (none_bool) 8) + ((ok payload) + false) + ((err code) + (= code 8))) + false) + false) + false)) + +(fn workspace_string_bridge_ok () -> bool + (if (= (unwrap_or_string (some_string "slovo") "fallback") "slovo") + (if (= (unwrap_or_string (none_string) "fallback") "fallback") + (if (match (some_or_err_string (some_string "slovo") 9) + ((ok payload) + (= payload "slovo")) + ((err code) + false)) + (match (some_or_err_string (none_string) 9) + ((ok payload) + false) + ((err code) + (= code 9))) + false) + false) + false)) + +(fn workspace_std_option_ok () -> bool + (if (is_some_i32 (some_value 40)) + (if (is_none_i32 (none_value)) + (if (= (+ (unwrap_or_i32 (some_value 40) 0) (unwrap_or_i32 (none_value) 2)) 42) + (if (workspace_i32_bridge_ok) + (if (workspace_i64_bridge_ok) + (if (workspace_f64_bridge_ok) + (if (workspace_bool_bridge_ok) + (workspace_string_bridge_ok) + false) + false) + false) + false) + false) + false) + false)) + +(fn main () -> i32 + (if (workspace_std_option_ok) + 42 + 1)) + +(test "workspace std option import" + (workspace_std_option_ok)) diff --git a/docs/language/examples/workspaces/std-import-option/slovo.toml b/docs/language/examples/workspaces/std-import-option/slovo.toml new file mode 100644 index 0000000..3bd58ef --- /dev/null +++ b/docs/language/examples/workspaces/std-import-option/slovo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["packages/app"] diff --git a/docs/papers/GLAGOL_COMPILER_MANIFEST.pdf b/docs/papers/GLAGOL_COMPILER_MANIFEST.pdf new file mode 100644 index 0000000..5516321 Binary files /dev/null and b/docs/papers/GLAGOL_COMPILER_MANIFEST.pdf differ diff --git a/docs/papers/GLAGOL_WHITEPAPER.md b/docs/papers/GLAGOL_WHITEPAPER.md new file mode 100644 index 0000000..7110f7e --- /dev/null +++ b/docs/papers/GLAGOL_WHITEPAPER.md @@ -0,0 +1,389 @@ +# Glagol: A Manifest-First Compiler Architecture for Slovo + +

ⰃⰎⰀⰃⰑⰎ

+ +Sanjin Gumbarevic
+hermeticum_lab@protonmail.com + +Publication release: `1.0.0-beta` + +Technical behavior baseline: compiler and language support through +`1.0.0-beta` + +Date: 2026-05-21 + +Evidence source: paired local Slovo/Glagol monorepo verification and benchmark +reruns from a local checkout + +Maturity: beta + +## Abstract + +Glagol (ⰃⰎⰀⰃⰑⰎ) is the first compiler for +Slovo. It exists to make the language support boundary inspectable: tokens, +S-expression tree, AST, typed AST, LLVM IR, hosted native executable, tests, +diagnostics, and release documents should agree. + +The current publication release, `1.0.0-beta`, records the first real +general-purpose beta toolchain for Slovo. It includes the completed `u32` / +`u64` unsigned compiler and stdlib breadth scope alongside the current +nine-kernel benchmark suite. This paper records the current beta +implementation surface, the benchmark method and results, the distinction +between Glagol and Lisp-family implementations, and the compiler path from +beta to stable. + +## 1. Compiler Thesis + +The name Glagol is rendered in Glagolitic as +ⰃⰎⰀⰃⰑⰎ. The publication pipeline embeds a +Glagolitic-capable font so this identity marker survives PDF rendering. + +Glagol's compiler motto is: + +```text +make the tree visible +``` + +The current pipeline is: + +```text +.slo source +-> tokens +-> S-expression tree +-> AST +-> typed AST +-> LLVM IR text +-> Clang + runtime/runtime.c +-> native executable +``` + +The engineering point is not only native output. It is traceability. Source +structure, types, spans, diagnostics, formatter behavior, and generated code +should stay connected enough that a support claim can be audited. + +## 2. Relationship To Lisp Implementations + +Glagol compiles a Lisp-shaped language, but it is not a Lisp implementation in +the usual technical sense. + +Common Lisp and Scheme implementations typically center a runtime evaluation +model, symbolic data, macro expansion, and language-defined execution +semantics. Clojure centers hosted execution on the JVM, namespaces, immutable +persistent data structures, dynamic vars, and runtime sequence abstractions. + +Glagol instead centers: + +- manifest-first language contracts +- explicit AST and typed AST stages before backend emission +- static checking before native code generation +- canonical formatting and structured diagnostics as release artifacts +- explicit `option` and `result` flow instead of exception-driven ordinary + failure +- lexical `unsafe` as the reserved low-level boundary +- hosted native executables through LLVM IR and Clang +- release gates that separate supported, compatibility, formatter-only, and + speculative examples + +The parenthesized syntax is therefore a structural source format, not evidence +that Glagol is a macro-first Lisp VM or a generic list runtime. + +## 3. Current Implementation Surface + +At the current technical behavior beta baseline, Glagol supports: + +- `check`, `fmt`, `fmt --check`, `fmt --write`, `test`, `build`, and `doc` +- JSON diagnostics, textual artifact manifests, and lowering inspection +- hosted native executable generation through emitted LLVM IR, host + `clang -O2`, and `runtime/runtime.c` +- flat local module projects, explicit import/export lists, local packages, + and workspace membership +- installed `share/slovo/std` discovery and ordered `SLOVO_STD_PATH` search +- direct scalar types `i32`, `i64`, `u32`, `u64`, finite `f64`, `bool`, + immutable `string`, and internal `unit` +- functions, top-level tests, immutable locals, current mutable whole-value + locals, `if`, and `while` +- current direct enum payload families, current known struct field families, + concrete option/result families, fixed immutable arrays over direct scalars + and `string`, and concrete runtime-owned vector families over `i32`, `i64`, + `f64`, `bool`, and `string` +- compiler-known standard-runtime calls through the promoted catalog plus + staged source-authored `std/*.slo` gates +- scalar C FFI imports +- benchmark scaffolds for Slovo, C, Rust, Python, Clojure, and Common + Lisp/SBCL, with `cold-process` and `hot-loop` timing modes + +The current release, `1.0.0-beta`, is the first release that may honestly use +beta maturity language for this toolchain. + +## 4. Diagnostics And Support Discipline + +Glagol's quality boundary is not "the parser accepted a form." The required +support path is: + +1. parse the source +2. lower to AST with spans +3. type-check names and value flow +4. reject unsupported forms before backend panic +5. emit LLVM only from checked representation +6. cover behavior or diagnostics with tests +7. update release docs and fixtures together + +This matters because Slovo syntax is intentionally regular. A permissive parser +can make unsupported forms look almost supported. Glagol therefore treats +backend panics, invalid LLVM from user source, and stale docs that overclaim +support as release-blocking defects. + +## 5. Runtime And Standard Library Strategy + +Glagol currently exposes two related but distinct library surfaces: + +- compiler-known standard-runtime calls such as `std.io.print_i32`, + `std.string.len`, selected parse/format/conversion calls, host IO, + process/environment/file helpers, randomness, time, and stdin +- source-authored beta modules in `lib/std/*.slo`, loaded through explicit + imports, installed std discovery, checkout discovery, or `SLOVO_STD_PATH` + +This split is deliberate. It lets library design move forward without claiming +that the final stable import, compatibility, or package story already exists. +Source-authored modules are useful now because they exercise language design, +fixtures, and examples. They are beta explicit-import APIs, but not yet a frozen +stable `1.0` standard library. + +## 6. Benchmark Method + +The benchmark suite measures local-machine behavior only. It is a regression +and comparison harness, not a public performance claim. + +Environment: + +| Field | Value | +| --- | --- | +| Host | `Linux 6.17.10-100.fc41.x86_64 x86_64 GNU/Linux` | +| Glagol | `glagol 1.0.0-beta` | +| Python | `Python 3.13.9` | +| C compiler | `clang version 19.1.7 (Fedora 19.1.7-5.fc41)` | +| Rust | `rustc 1.77.2 (25ef9e3d8 2024-04-09)` | +| Clojure | `1.11.2` | +| Common Lisp | `SBCL 2.5.9-1.fc41` | + +Build and runtime paths compared: + +| Implementation | Build/runtime path | +| --- | --- | +| Slovo | `glagol build ` -> generated LLVM -> host `clang -O2` linking `runtime/runtime.c` | +| C | `clang -O2 -std=c11` on the local scaffold | +| Rust | `rustc -C opt-level=3 -C debuginfo=0` on the local scaffold | +| Python | `python3` running the local scaffold | +| Clojure | `clojure` running the local scaffold; timings include JVM and Clojure startup | +| Common Lisp | `sbcl --script` running the local scaffold; timings include SBCL startup | + +Timing semantics: + +- The runner builds each implementation once before timing. The reported + numbers are execution timings, not compile-time timings. +- `cold-process` launches a fresh process per sample with the base loop count. + It measures process startup plus one benchmark run. +- `hot-loop` also launches a fresh process per sample, but with the amplified + loop count `10000000`; the reported normalized median divides the timed total + by `10` to compare with the base `1000000` loop count. + +Benchmark kernels: + +- `math-loop`: scalar arithmetic accumulation +- `branch-loop`: scalar branching and accumulation +- `parse-loop`: repeated decimal parsing with checksum validation +- `array-index-loop`: checked fixed-array indexing and scalar accumulation +- `string-eq-loop`: exact string content equality reduced to an `i32` checksum +- `array-struct-field-loop`: immutable struct-field access over a fixed `i32` + array plus scalar accumulation +- `enum-struct-payload-loop`: repeated enum `match` payload extraction over an + immutable struct payload carrying a fixed `i32` array +- `vec-i32-index-loop`: runtime-owned `i32` vector indexing and scalar + accumulation +- `vec-string-eq-loop`: runtime-owned string vector indexing plus exact string + equality reduced to an `i32` checksum + +Comparison boundaries: + +- `math-loop` and `branch-loop` compare structurally similar loop bodies across + all implementations. +- `parse-loop` keeps the same input text and checksum, but not the same parser + implementation. Slovo uses `std.string.parse_i32_result`, C uses `strtol`, + Rust uses `text.parse::()`, Python uses `int`, Clojure uses + `Integer/parseInt`, and Common Lisp uses `parse-integer`. +- `array-index-loop` keeps the same eight-element integer corpus and `% 8` + dynamic index-selection pattern across all implementations. It stays on + immutable fixed-array indexing and scalar accumulation only. +- `string-eq-loop` keeps the same five-word ASCII corpus and runtime-supplied + target string across all implementations. It measures exact content equality + only. It does not compare regex engines, normalization, locale handling, or + pointer identity. +- `array-struct-field-loop` keeps the same eight-element integer corpus and + `% 8` dynamic index-selection pattern, but moves the array through one + immutable struct field. It is a narrow benchmark for the promoted + `exp-120` direct struct-field lane, not a broad claim about every struct + layout. +- `enum-struct-payload-loop` keeps the same eight-element integer corpus inside + an immutable struct payload, matches one enum value on every iteration, and + indexes the bound struct field. It is a narrow benchmark for the promoted + `exp-121` struct-payload enum lane, not a broad tagged-union or ADT claim. +- `vec-i32-index-loop` keeps the same eight-element integer corpus and `% 8` + dynamic index-selection pattern as `array-index-loop`, but routes that + access through the promoted runtime-owned `(vec i32)` lane instead of fixed + arrays. +- `vec-string-eq-loop` keeps the same five-word ASCII corpus and + runtime-supplied target as `string-eq-loop`, but routes selection through + the promoted runtime-owned `(vec string)` lane instead of fixed arrays. +- Because Rust is timed at `opt-level=3` while Slovo and C are timed through + `clang -O2`, the suite is a useful local regression/comparison harness, not a + strict same-flags compiler shootout. + +Hot-loop commands: + +```bash +python3 benchmarks/math-loop/run.py --mode hot-loop --repeats 5 --warmups 1 --glagol compiler/target/debug/glagol +python3 benchmarks/branch-loop/run.py --mode hot-loop --repeats 5 --warmups 1 --glagol compiler/target/debug/glagol +python3 benchmarks/parse-loop/run.py --mode hot-loop --repeats 5 --warmups 1 --glagol compiler/target/debug/glagol +python3 benchmarks/array-index-loop/run.py --mode hot-loop --repeats 5 --warmups 1 --glagol compiler/target/debug/glagol +python3 benchmarks/string-eq-loop/run.py --mode hot-loop --repeats 5 --warmups 1 --glagol compiler/target/debug/glagol +python3 benchmarks/array-struct-field-loop/run.py --mode hot-loop --repeats 5 --warmups 1 --glagol compiler/target/debug/glagol +python3 benchmarks/enum-struct-payload-loop/run.py --mode hot-loop --repeats 5 --warmups 1 --glagol compiler/target/debug/glagol +python3 benchmarks/vec-i32-index-loop/run.py --mode hot-loop --repeats 5 --warmups 1 --glagol compiler/target/debug/glagol +python3 benchmarks/vec-string-eq-loop/run.py --mode hot-loop --repeats 5 --warmups 1 --glagol compiler/target/debug/glagol +``` + +Cold-process commands: + +```bash +python3 benchmarks/math-loop/run.py --mode cold-process --repeats 3 --warmups 1 --glagol compiler/target/debug/glagol +python3 benchmarks/branch-loop/run.py --mode cold-process --repeats 3 --warmups 1 --glagol compiler/target/debug/glagol +python3 benchmarks/parse-loop/run.py --mode cold-process --repeats 3 --warmups 1 --glagol compiler/target/debug/glagol +python3 benchmarks/array-index-loop/run.py --mode cold-process --repeats 3 --warmups 1 --glagol compiler/target/debug/glagol +python3 benchmarks/string-eq-loop/run.py --mode cold-process --repeats 3 --warmups 1 --glagol compiler/target/debug/glagol +python3 benchmarks/array-struct-field-loop/run.py --mode cold-process --repeats 3 --warmups 1 --glagol compiler/target/debug/glagol +python3 benchmarks/enum-struct-payload-loop/run.py --mode cold-process --repeats 3 --warmups 1 --glagol compiler/target/debug/glagol +python3 benchmarks/vec-i32-index-loop/run.py --mode cold-process --repeats 3 --warmups 1 --glagol compiler/target/debug/glagol +python3 benchmarks/vec-string-eq-loop/run.py --mode cold-process --repeats 3 --warmups 1 --glagol compiler/target/debug/glagol +``` + +## 7. Benchmark Results + +The exp-123 publication baseline widens the paired same-machine result set +from seven rows to nine by adding two owned-vector kernels: + +- `vec-i32-index-loop` +- `vec-string-eq-loop` + +Hot-loop normalized median time, in milliseconds per one million iterations: + +| Benchmark | Slovo | C | Rust | Python | Clojure | Common Lisp/SBCL | +| --- | ---: | ---: | ---: | ---: | ---: | ---: | +| math-loop | 1.121 | 1.121 | 1.138 | 111.092 | 241.220 | 1.753 | +| branch-loop | 2.014 | 2.012 | 2.032 | 114.016 | 241.469 | 4.624 | +| parse-loop | 6.456 | 16.233 | 7.169 | 134.465 | 264.669 | 20.108 | +| array-index-loop | 1.103 | 1.109 | 1.128 | 96.649 | 298.388 | 3.379 | +| string-eq-loop | 4.332 | 4.092 | 2.279 | 120.453 | 288.617 | 11.128 | +| array-struct-field-loop | 1.139 | 1.116 | 1.129 | 110.854 | 277.466 | 3.663 | +| enum-struct-payload-loop | 4.304 | 1.512 | 1.880 | 302.252 | 310.066 | 5.297 | +| vec-i32-index-loop | 1.328 | 1.103 | 1.131 | 111.153 | 272.914 | 2.231 | +| vec-string-eq-loop | 5.210 | 4.122 | 3.471 | 122.826 | 302.817 | 10.431 | + +Cold-process median time, in milliseconds per benchmark run: + +| Benchmark | Slovo | C | Rust | Python | Clojure | Common Lisp/SBCL | +| --- | ---: | ---: | ---: | ---: | ---: | ---: | +| math-loop | 1.625 | 1.675 | 1.765 | 121.014 | 2808.812 | 9.435 | +| branch-loop | 2.563 | 2.517 | 2.682 | 130.790 | 2674.146 | 15.027 | +| parse-loop | 6.942 | 16.749 | 7.857 | 149.594 | 2835.421 | 27.750 | +| array-index-loop | 1.599 | 1.606 | 1.807 | 107.150 | 2812.157 | 17.589 | +| string-eq-loop | 4.826 | 4.756 | 2.938 | 135.748 | 2892.359 | 21.504 | +| array-struct-field-loop | 1.670 | 1.612 | 1.837 | 115.371 | 2823.026 | 13.411 | +| enum-struct-payload-loop | 4.934 | 2.000 | 1.783 | 291.850 | 2516.815 | 27.555 | +| vec-i32-index-loop | 3.047 | 2.851 | 1.776 | 112.950 | 2911.603 | 10.017 | +| vec-string-eq-loop | 5.427 | 4.575 | 4.081 | 134.914 | 2567.482 | 18.950 | + +Compiler interpretation: + +- The current hosted build path keeps Slovo essentially on the local native + baseline for the scalar and fixed-array kernels. In hot-loop mode, + `math-loop`, `branch-loop`, and `array-index-loop` all land very close to the + C scaffold and within a narrow distance of the Rust scaffold. +- `parse-loop` is now more than a backend-loop benchmark. It compares end-to-end + parser and runtime choices. On this machine, the current Slovo decimal parse + path outperforms the C scaffold built around `strtol` and stays close to the + Rust scaffold. +- `string-eq-loop` exposes a different boundary: exact content equality is + clearly efficient enough for native-code use, but the current Slovo runtime + path is still behind the Rust scaffold and slightly behind the C scaffold on + this machine. +- `vec-i32-index-loop` shows the cost of routing the same integer corpus + through the promoted owned-vector lane instead of fixed arrays. On this + machine the Slovo lane remains practical native code, but it is visibly more + expensive than the fixed-array kernel. +- `vec-string-eq-loop` shows the same tradeoff for owned string vectors. It + stays in the same broad range as the fixed-array string kernel, but it is a + more allocation- and indirection-heavy path than direct fixed-array access. +- `array-struct-field-loop` stays close to the direct fixed-array kernel. On + this machine, routing the same `% 8` indexing pattern through one immutable + struct field keeps Slovo, C, and Rust tightly grouped in hot-loop mode. +- `enum-struct-payload-loop` exposes a current composite-data boundary. The + Slovo lane remains practical native code, but repeated struct-payload enum + matching is still materially slower than the C and Rust scaffolds on this + machine. +- Cold-process timings show native executable startup plus one benchmark run. + They are not compile-time numbers and are more sensitive to launcher/runtime + initialization effects than hot-loop mode. +- Clojure is dramatically slower in this process-per-run harness because each + sample includes JVM and hosted runtime startup, and the benchmark bodies stay + on high-level runtime paths. The effect is still strongest in the more + allocation- and dispatch-heavy composite kernels. +- Common Lisp/SBCL remains much closer to native baselines than Clojure in the + same harness. That is why both Lisp-family comparison points are useful. + +## 8. Current Technical Risks + +The main risks in beta are not syntax parsing. They are engineering +coverage and compatibility: + +- source forms reaching backend paths without clear diagnostics +- standard-library source helpers drifting from compiler-known runtime calls +- feature claims appearing in docs before fixtures and tests exist +- collection and ADT breadth growing faster than the compatibility story +- benchmark breadth growing faster than the language contract can stabilize +- benchmark numbers being misread as public thresholds or cross-machine claims +- package behavior becoming stable before dependency, manifest, and versioning + rules are precise + +## 9. Path Beyond `1.0.0-beta` + +Glagol now implements the first real beta Slovo contract and passes the +required beta workflow proof plus release gate. The remaining path is from +beta to stable. + +Recommended compiler sequence: + +1. Complete the next blocked post-beta language-breadth slices from the Slovo + roadmap without regressing the beta baseline. +2. Broaden runtime-owned strings, collections, and composite value flow + without exposing unstable ABI details as stable contracts. +3. Refine `f32` policy, additional integer families, explicit conversion + behavior, and remaining library/runtime gaps. +4. Harden package, workspace, and standard-library import/search behavior into + a compatibility-governed stable toolchain story. +5. Strengthen diagnostics, generated docs, conformance fixtures, and release + gates as first-class compiler interfaces. +6. Keep benchmark publication local and repeatable while deferring public + performance claims until methodology is stronger. +7. Freeze formatter output, diagnostics schema, package behavior, stdlib + compatibility, migration policy, and toolchain contracts for `1.0.0`. + +## 10. Conclusion + +Glagol has moved Slovo from a manifesto into a working beta native compiler +track. The important result is not only that programs compile. It is that the +support boundary is visible enough to review: source contracts, diagnostics, +tests, lowering, benchmarks, and publication artifacts can be kept in sync. + +The compiler is now useful enough for ordinary local tools and libraries +within the documented beta contract. The path forward remains disciplined +breadth and compatibility hardening, not unsupported feature claims. diff --git a/docs/papers/GLAGOL_WHITEPAPER.pdf b/docs/papers/GLAGOL_WHITEPAPER.pdf new file mode 100644 index 0000000..0d6bcfe Binary files /dev/null and b/docs/papers/GLAGOL_WHITEPAPER.pdf differ diff --git a/docs/papers/SLOVO_MANIFEST.pdf b/docs/papers/SLOVO_MANIFEST.pdf new file mode 100644 index 0000000..263fb33 Binary files /dev/null and b/docs/papers/SLOVO_MANIFEST.pdf differ diff --git a/docs/papers/SLOVO_WHITEPAPER.md b/docs/papers/SLOVO_WHITEPAPER.md new file mode 100644 index 0000000..0aba036 --- /dev/null +++ b/docs/papers/SLOVO_WHITEPAPER.md @@ -0,0 +1,501 @@ +# Slovo: A Typed Structural Language for Tool-Visible Native Programs + +

ⰔⰎⰑⰂⰑ

+ +Sanjin Gumbarevic
+hermeticum_lab@protonmail.com + +Publication release: `1.0.0-beta` + +Technical behavior baseline: language surface through `1.0.0-beta` + +Date: 2026-05-21 + +Evidence source: paired local Slovo/Glagol monorepo verification and benchmark +reruns from a local checkout + +Maturity: beta + +## Abstract + +Slovo (ⰔⰎⰑⰂⰑ) is a typed structural +programming language whose source form is already a tree. It borrows the +visual regularity of Lisp-family syntax, but it is not a Lisp dialect. Slovo +is designed around a statically checked source tree, canonical formatting, +explicit types, explicit failure through `option` and `result`, lexical +`unsafe`, and native compilation through the Glagol compiler to LLVM IR and +hosted executables. + +The current publication release, `1.0.0-beta`, records the first real +general-purpose beta baseline for Slovo. It includes the completed `u32` / +`u64` unsigned scope, the staged stdlib breadth that makes ordinary +command-line programs practical, and the current nine-kernel benchmark suite. +This paper records the current beta technical state, the difference between +Slovo and Lisp-family languages, the current benchmark methodology, and the +remaining path from beta to stable. + +## 1. Scope + +This document is a technical state paper for the current beta baseline. It +summarizes the behavior represented by the paired local Slovo and Glagol +workspaces, with `1.0.0-beta` as both the current language-surface and +publication baseline. + +The support rule remains strict: + +- a feature is supported only when Slovo specifies it +- Glagol must parse it, lower it, type-check it, and either emit behavior or + reject it with a structured diagnostic +- formatter behavior, examples, tests, release notes, and review documents + must agree +- partial parser recognition or speculative examples do not count as support + +Historical `exp-*` releases remain experimental alpha maturity. The current +publication accompanies `1.0.0-beta`. + +## 2. Design Thesis + +The name Slovo is rendered in Glagolitic as +ⰔⰎⰑⰂⰑ. This spelling is part of the language +identity and should render correctly in both web and PDF publications. + +Slovo's core claim is: + +```text +written tree +-> parsed tree +-> checked tree +-> lowered tree +-> LLVM IR +-> native executable +``` + +Most mainstream languages hide structure behind precedence, grammar +exceptions, and multiple stylistic spellings. Slovo exposes structure directly +with parenthesized prefix forms: + +```slo +(fn add ((a i32) (b i32)) -> i32 + (+ a b)) +``` + +The goal is not minimalism for its own sake. The goal is continuity between +the written program, the compiler's typed representation, diagnostics, +formatter output, and generated code. + +## 3. How Slovo Differs From Lisp + +Slovo is Lisp-shaped, but not a Lisp in the usual technical sense. + +Similarities: + +- source is parenthesized and prefix-oriented +- programs are visually tree-shaped +- tools can inspect forms without complex precedence parsing +- the syntax is regular enough for reliable generation and repair + +Important differences: + +- Slovo is statically typed at the language boundary. Current source uses + explicit signatures such as `i32`, `i64`, `f64`, `bool`, `string`, + concrete `option` / `result` families, fixed arrays, and concrete vector + families. +- Slovo does not promote a macro system as the center of the language. + Convenience must lower into one specified typed core and formatter behavior. +- Slovo source is not presented as ordinary runtime list data. The source tree + is a compiler contract, not a promise that programs manipulate themselves as + lists. +- Slovo has no reader-conditionals, package-time dynamic namespace semantics, + or hosted REPL-first execution model. +- Slovo rejects hidden numeric conversion. Numeric widening and narrowing are + explicit standard-runtime calls or result-returning conversions. +- Slovo uses `option` and `result` instead of null and exceptions as the + intended ordinary absence/failure model. +- Slovo reserves raw memory and low-level operations behind lexical `unsafe`. +- Slovo is compiled by Glagol through a visible pipeline to LLVM IR and a + hosted native executable path. +- Slovo treats canonical formatting, diagnostics, test metadata, artifact + manifests, and benchmark publication discipline as part of the language + contract. + +Compared with Common Lisp, Slovo is much less dynamic and does not have CLOS, +conditions, a mature macro system, or an interactive image-centered +tradition. Compared with Clojure, Slovo is not hosted on the JVM, does not +center persistent data structures today, and does not rely on dynamic vars or +runtime sequence abstractions. Compared with Scheme, Slovo is not aiming at a +small untyped lambda-calculus core. Slovo's current direction is closer to a +tree-shaped systems language with a small checked core. + +## 4. Current Language State + +At the current published beta baseline, Slovo can +express and Glagol can compile small local projects using: + +- modules, explicit imports and exports, local packages, and workspace + membership +- functions and top-level tests +- `let`, `var`, `set`, `if`, and `while` +- current concrete `match` behavior over the promoted option/result/enum lanes +- `i32`, `i64`, `u32`, `u64`, finite `f64`, `bool`, immutable `string`, and + internal `unit` +- explicit numeric widening plus selected checked narrowing conversions +- integer remainder and integer bitwise operations on promoted integer lanes +- payloadless enums plus unary direct payload variants over `i32`, `i64`, + `f64`, `bool`, and `string`, plus unary payload variants over current known + non-recursive struct types +- structs with direct scalar, immutable string, current enum, numeric, current + concrete option/result, current concrete vector, current known + non-recursive struct fields, and direct fixed immutable array fields +- fixed immutable arrays over `i32`, `i64`, `f64`, `bool`, and `string` with + positive literal lengths and checked `i32` indexing +- concrete runtime-owned vector families `(vec i32)`, `(vec i64)`, + `(vec f64)`, `(vec bool)`, and `(vec string)` with source-authored standard + facades +- string concatenation, string length, string equality, and concrete parse and + format helpers +- basic IO, stderr output, stdin result flow, process arguments, CLI facades, + environment lookup, text file read/write, randomness, monotonic time, sleep, + and scalar C FFI +- lexical `unsafe` boundaries for reserved raw operations +- staged source-authored standard-library modules under `std/` + +The staged source library currently includes: + +```text +std/cli.slo +std/env.slo +std/fs.slo +std/io.slo +std/math.slo +std/num.slo +std/option.slo +std/process.slo +std/random.slo +std/result.slo +std/string.slo +std/time.slo +std/vec_bool.slo +std/vec_f64.slo +std/vec_i32.slo +std/vec_i64.slo +std/vec_string.slo +``` + +These modules are beta standard-library source facades. They are explicit-import +APIs with compatibility discipline, but they are not yet frozen stable `1.0` +standard-library APIs. Glagol supports explicit standard-source imports, +installed `share/slovo/std` discovery, `lib/std` discovery from a checkout, and +`SLOVO_STD_PATH`; Slovo does not yet claim stable implicit standard imports or a +stable package ecosystem. + +## 5. Current Toolchain State + +Glagol is the first compiler for Slovo. The supported compiler path is: + +```text +.slo source +-> tokens +-> S-expression tree +-> AST +-> typed AST +-> LLVM IR text +-> Clang + runtime/runtime.c +-> native executable +``` + +The toolchain currently provides: + +- `glagol check` +- `glagol fmt`, `fmt --check`, and `fmt --write` +- `glagol test` +- `glagol build` +- `glagol doc` +- JSON diagnostics +- textual artifact manifests +- lowering inspection +- native executable output through hosted LLVM/Clang integration +- benchmark scaffolds for Slovo, C, Rust, Python, Clojure, and Common + Lisp/SBCL comparisons +- repo-local release-gate and document-render scripts for publication + artifacts + +The compiler implementation is intentionally conservative. When a source form +is not specified, the desired behavior is a structured diagnostic rather than +a panic or invalid LLVM. + +## 6. Benchmark Method + +The benchmark suite measures local-machine behavior only. It is a regression +and comparison harness, not a public performance claim. + +Environment: + +| Field | Value | +| --- | --- | +| Host | `Linux 6.17.10-100.fc41.x86_64 x86_64 GNU/Linux` | +| Glagol | `glagol 1.0.0-beta` | +| Python | `Python 3.13.9` | +| C compiler | `clang version 19.1.7 (Fedora 19.1.7-5.fc41)` | +| Rust | `rustc 1.77.2 (25ef9e3d8 2024-04-09)` | +| Clojure | `1.11.2` | +| Common Lisp | `SBCL 2.5.9-1.fc41` | + +Build and runtime paths compared: + +| Implementation | Build/runtime path | +| --- | --- | +| Slovo | `glagol build ` -> generated LLVM -> host `clang -O2` linking `runtime/runtime.c` | +| C | `clang -O2 -std=c11` on the local scaffold | +| Rust | `rustc -C opt-level=3 -C debuginfo=0` on the local scaffold | +| Python | `python3` running the local scaffold | +| Clojure | `clojure` running the local scaffold; timings include JVM and Clojure startup | +| Common Lisp | `sbcl --script` running the local scaffold; timings include SBCL startup | + +Timing semantics: + +- The runner builds each implementation once before timing. The reported + benchmark numbers are execution timings, not compile-time timings. +- `cold-process` launches a fresh process per sample with the base loop count. + It measures process startup plus one normal benchmark run. +- `hot-loop` also launches a fresh process per sample, but with the amplified + loop count `10000000`; the reported normalized median divides the timed total + by `10` to compare with the base `1000000` loop count. + +Benchmark kernels: + +- `math-loop`: scalar arithmetic accumulation +- `branch-loop`: scalar branching and accumulation +- `parse-loop`: repeated decimal parsing with checksum validation +- `array-index-loop`: checked fixed-array indexing and scalar accumulation +- `string-eq-loop`: exact string content equality reduced to an `i32` checksum +- `array-struct-field-loop`: checked fixed-array access through direct struct + fields plus scalar accumulation +- `enum-struct-payload-loop`: unary enum payload `match` plus bound + non-recursive struct field access reduced to a deterministic checksum +- `vec-i32-index-loop`: runtime-owned `i32` vector indexing plus scalar + accumulation +- `vec-string-eq-loop`: runtime-owned string vector indexing plus exact string + equality reduced to an `i32` checksum + +Comparison boundaries: + +- `math-loop` and `branch-loop` compare structurally similar loop bodies across + all implementations. +- `parse-loop` keeps the same input text and checksum, but not the same parser + implementation. Slovo uses `std.string.parse_i32_result`, C uses `strtol`, + Rust uses `text.parse::()`, Python uses `int`, Clojure uses + `Integer/parseInt`, and Common Lisp uses `parse-integer`. +- `array-index-loop` should stay on immutable fixed-array indexing plus scalar + accumulation only. The Slovo lane should use the promoted fixed-array + surface, not vector helpers or newer container experiments. +- `string-eq-loop` measures exact content equality only. It should use fixed + ASCII data and an explicit checksum reduction, not pointer identity, + normalization, locale handling, regex engines, or concat-heavy allocation. +- `array-struct-field-loop` should stay on the exp-120 surface only: direct + fixed-array struct fields, existing immutable struct flow, checked indexing, + and scalar accumulation. It should not widen into array mutation, nested + arrays, or unrelated container experiments. +- `enum-struct-payload-loop` should stay on the exp-121 surface only: unary + enum payload variants over current known non-recursive struct types, + existing immutable enum flow, `match` payload binding, and bound-field + access. It should not widen into direct array/vec/option/result payloads, + recursive payload graphs, or mutation. +- `vec-i32-index-loop` should stay on the current promoted runtime-owned + `(vec i32)` lane only: immutable vector creation, checked vector indexing, + and scalar accumulation. It should not widen into mutation or unrelated + collection experiments. +- `vec-string-eq-loop` should stay on the current promoted runtime-owned + `(vec string)` lane only: immutable vector creation, checked vector + indexing, and exact string equality. It should not widen into regex, + normalization, locale handling, or concat-heavy allocation experiments. +- Because Rust is timed at `opt-level=3` while Slovo and C are timed through + `clang -O2`, the suite is a useful local regression/comparison harness, not + a strict same-flags compiler shootout. + +Hot-loop commands: + +```bash +python3 benchmarks/math-loop/run.py --mode hot-loop --repeats 5 --warmups 1 --glagol compiler/target/debug/glagol +python3 benchmarks/branch-loop/run.py --mode hot-loop --repeats 5 --warmups 1 --glagol compiler/target/debug/glagol +python3 benchmarks/parse-loop/run.py --mode hot-loop --repeats 5 --warmups 1 --glagol compiler/target/debug/glagol +python3 benchmarks/array-index-loop/run.py --mode hot-loop --repeats 5 --warmups 1 --glagol compiler/target/debug/glagol +python3 benchmarks/string-eq-loop/run.py --mode hot-loop --repeats 5 --warmups 1 --glagol compiler/target/debug/glagol +python3 benchmarks/array-struct-field-loop/run.py --mode hot-loop --repeats 5 --warmups 1 --glagol compiler/target/debug/glagol +python3 benchmarks/enum-struct-payload-loop/run.py --mode hot-loop --repeats 5 --warmups 1 --glagol compiler/target/debug/glagol +python3 benchmarks/vec-i32-index-loop/run.py --mode hot-loop --repeats 5 --warmups 1 --glagol compiler/target/debug/glagol +python3 benchmarks/vec-string-eq-loop/run.py --mode hot-loop --repeats 5 --warmups 1 --glagol compiler/target/debug/glagol +``` + +Cold-process commands: + +```bash +python3 benchmarks/math-loop/run.py --mode cold-process --repeats 3 --warmups 1 --glagol compiler/target/debug/glagol +python3 benchmarks/branch-loop/run.py --mode cold-process --repeats 3 --warmups 1 --glagol compiler/target/debug/glagol +python3 benchmarks/parse-loop/run.py --mode cold-process --repeats 3 --warmups 1 --glagol compiler/target/debug/glagol +python3 benchmarks/array-index-loop/run.py --mode cold-process --repeats 3 --warmups 1 --glagol compiler/target/debug/glagol +python3 benchmarks/string-eq-loop/run.py --mode cold-process --repeats 3 --warmups 1 --glagol compiler/target/debug/glagol +python3 benchmarks/array-struct-field-loop/run.py --mode cold-process --repeats 3 --warmups 1 --glagol compiler/target/debug/glagol +python3 benchmarks/enum-struct-payload-loop/run.py --mode cold-process --repeats 3 --warmups 1 --glagol compiler/target/debug/glagol +python3 benchmarks/vec-i32-index-loop/run.py --mode cold-process --repeats 3 --warmups 1 --glagol compiler/target/debug/glagol +python3 benchmarks/vec-string-eq-loop/run.py --mode cold-process --repeats 3 --warmups 1 --glagol compiler/target/debug/glagol +``` + +## 7. Benchmark Results + +The exp-123 publication baseline widens the paired same-machine result set +from seven rows to nine by adding two owned-vector kernels: + +- `vec-i32-index-loop` +- `vec-string-eq-loop` + +Hot-loop normalized median time, in milliseconds per one million iterations: + +| Benchmark | Slovo | C | Rust | Python | Clojure | Common Lisp/SBCL | +| --- | ---: | ---: | ---: | ---: | ---: | ---: | +| math-loop | 1.121 | 1.121 | 1.138 | 111.092 | 241.220 | 1.753 | +| branch-loop | 2.014 | 2.012 | 2.032 | 114.016 | 241.469 | 4.624 | +| parse-loop | 6.456 | 16.233 | 7.169 | 134.465 | 264.669 | 20.108 | +| array-index-loop | 1.103 | 1.109 | 1.128 | 96.649 | 298.388 | 3.379 | +| string-eq-loop | 4.332 | 4.092 | 2.279 | 120.453 | 288.617 | 11.128 | +| array-struct-field-loop | 1.139 | 1.116 | 1.129 | 110.854 | 277.466 | 3.663 | +| enum-struct-payload-loop | 4.304 | 1.512 | 1.880 | 302.252 | 310.066 | 5.297 | +| vec-i32-index-loop | 1.328 | 1.103 | 1.131 | 111.153 | 272.914 | 2.231 | +| vec-string-eq-loop | 5.210 | 4.122 | 3.471 | 122.826 | 302.817 | 10.431 | + +Cold-process median time, in milliseconds per benchmark run: + +| Benchmark | Slovo | C | Rust | Python | Clojure | Common Lisp/SBCL | +| --- | ---: | ---: | ---: | ---: | ---: | ---: | +| math-loop | 1.625 | 1.675 | 1.765 | 121.014 | 2808.812 | 9.435 | +| branch-loop | 2.563 | 2.517 | 2.682 | 130.790 | 2674.146 | 15.027 | +| parse-loop | 6.942 | 16.749 | 7.857 | 149.594 | 2835.421 | 27.750 | +| array-index-loop | 1.599 | 1.606 | 1.807 | 107.150 | 2812.157 | 17.589 | +| string-eq-loop | 4.826 | 4.756 | 2.938 | 135.748 | 2892.359 | 21.504 | +| array-struct-field-loop | 1.670 | 1.612 | 1.837 | 115.371 | 2823.026 | 13.411 | +| enum-struct-payload-loop | 4.934 | 2.000 | 1.783 | 291.850 | 2516.815 | 27.555 | +| vec-i32-index-loop | 3.047 | 2.851 | 1.776 | 112.950 | 2911.603 | 10.017 | +| vec-string-eq-loop | 5.427 | 4.575 | 4.081 | 134.914 | 2567.482 | 18.950 | + +Interpretation boundaries: + +- `math-loop`, `branch-loop`, and `array-index-loop` isolate different parts of + the current lowered execution path: arithmetic, branching, and checked array + indexing. +- `array-struct-field-loop` is the composite-data extension of that array lane. + It isolates exp-120 direct fixed-array struct fields plus checked field-path + indexing without claiming new array semantics. +- `enum-struct-payload-loop` is the composite-data extension of the current + enum lane. It isolates exp-121 unary non-recursive struct payloads plus + existing `match` payload binding and bound-field access without claiming new + ADT semantics. +- `vec-i32-index-loop` is the owned-collection extension of the integer array + lane. It isolates the promoted runtime-owned `(vec i32)` surface without + claiming generic collections, mutation, or broader container semantics. +- `vec-string-eq-loop` is the owned-collection extension of the string array + lane. It isolates the promoted runtime-owned `(vec string)` surface without + claiming broader string-library or collection semantics. +- `parse-loop` and `string-eq-loop` exercise richer library/runtime behavior + than the pure scalar kernels and should be interpreted as end-to-end local + implementation comparisons, not only backend loop-lowering comparisons. +- Cold-process timings include executable or host startup plus one benchmark + run; they are not compile-time numbers. +- Hot-loop timings are startup-amortized process timings, not an in-process + high-resolution benchmark harness. +- Clojure and Common Lisp remain useful as two distinct Lisp-family comparison + points rather than one generic "Lisp" bucket. +- Clojure startup dominates the cold-process mode and still heavily influences + the hot-loop process timing, which is why its results are much larger than + the native lanes in this harness. + +Threats to validity: + +- all timings are from one local machine on one date +- implementations are intentionally small benchmark equivalents, not idiomatic + full applications +- Slovo, C, and Rust compile to native executables while Clojure is JVM-hosted + and Python is interpreter-hosted +- hot-loop mode is still process timing, not a stable in-process microbenchmark + protocol +- `string-eq-loop` is only valid when every lane uses explicit content + equality rather than identity shortcuts +- the two composite-data kernels are valid only when they stay within the + already promoted exp-120 and exp-121 surfaces rather than broadening + semantics in benchmark-only ways +- no benchmark threshold is part of the language contract + +## 8. Why The Benchmark Matters + +The useful result is not a blanket claim that Slovo is faster than mature +native languages. The narrower result is: + +- the benchmark suite now exercises arithmetic, branching, parsing, checked + fixed-array indexing, exact string equality, direct fixed-array struct-field + access, and unary enum payload matching over non-recursive struct payloads +- the current hosted native path is now measured across both scalar and newer + promoted fixed-array, composite-data, and string behaviors rather than only + older scalar kernels +- deterministic checksums prevent accidental benchmark invalidation +- the suite separates startup-inclusive and startup-amortized questions +- future optimization work can be measured without changing the language + contract + +## 9. What Is Missing Before Stable + +Slovo is now beta, but it should not be called stable until beta feedback, +compatibility policy, and conformance coverage have hardened the public +contract. + +Major remaining gaps before `1.0.0`: + +- richer strings and collections beyond the current concrete fixed-array and + concrete vector families +- broader data modeling, especially container composition and more general ADT + ergonomics +- reusable abstractions and generics +- unsigned and narrower integer families, `f32`, broader casts, and mixed + numeric policy +- stable standard-library APIs and compatibility guarantees +- clearer package dependency behavior and version policy +- richer host errors and broader file/process/time APIs +- editor integration, conformance suites, and compatibility gates +- semantic versioning and deprecation policy +- a clear separation between stable and experimental features + +## 10. Path Beyond `1.0.0-beta` + +The beta threshold is now real. The next work should treat `1.0.0-beta` as +the compatibility-governed baseline and move deliberately toward stable +general-purpose status. + +Recommended sequence: + +1. Collection and container breadth: improve strings, vectors, arrays, and + current container composition without losing beta compatibility discipline. +2. Data modeling breadth: expand ADTs, error modeling, match behavior, and + composite value ergonomics. +3. Numeric completeness: refine `f32` policy, additional integer families, and + broader explicit parse/format/conversion coverage. +4. Standard-library stabilization: move from staged source facades to clearer + compatibility-governed APIs and documentation. +5. Package discipline: harden local packages, path dependencies, and artifact + manifests before any registry work. +6. Tooling hardening: strengthen diagnostics, generated docs, conformance + fixtures, benchmark publication discipline, and release-gate automation. +7. Stable freeze: publish `1.0.0` only after beta feedback, compatibility + policy, and migration/deprecation rules are proven across multiple releases. + +Benchmark expansion is useful because it exercises more of the promoted +surface. It is not itself a beta claim. + +## 11. Conclusion + +Slovo is now a serious beta language with a native compiler and a broad enough +source-authored standard-library surface to support ordinary local +command-line tools and libraries. Its strongest technical identity is not that +it looks like Lisp. Its identity is that the source tree remains visible and +accountable from writing through diagnostics, formatting, checking, lowering, +benchmarking, and native execution. + +The path ahead is no longer “reach beta.” It is to keep the beta contract +honest while hardening compatibility, libraries, packages, and tooling toward +`1.0.0`. diff --git a/docs/papers/SLOVO_WHITEPAPER.pdf b/docs/papers/SLOVO_WHITEPAPER.pdf new file mode 100644 index 0000000..8a35c40 Binary files /dev/null and b/docs/papers/SLOVO_WHITEPAPER.pdf differ diff --git a/docs/pdf.css b/docs/pdf.css new file mode 100644 index 0000000..f830b74 --- /dev/null +++ b/docs/pdf.css @@ -0,0 +1,15 @@ +@font-face { + font-family: "Noto Sans Glagolitic PDF"; + src: url("assets/fonts/NotoSansGlagolitic-Regular.ttf") format("truetype"); + font-weight: 400; + font-style: normal; +} + +body { + font-family: "Noto Sans", "DejaVu Sans", Arial, "Noto Sans Glagolitic PDF", sans-serif; +} + +.glagolitic { + font-family: "Noto Sans Glagolitic PDF", "Noto Sans", sans-serif; + font-weight: 400; +} diff --git a/examples/add.slo b/examples/add.slo new file mode 100644 index 0000000..7550b90 --- /dev/null +++ b/examples/add.slo @@ -0,0 +1,11 @@ +; status: compiler-supported +; Strict-manifest Glagol executable fixture. + +(module main) + +(fn add ((a i32) (b i32)) -> i32 + (+ a b)) + +(fn main () -> i32 + (print_i32 (add 20 22)) + 0) diff --git a/examples/array-direct-scalars-value-flow.slo b/examples/array-direct-scalars-value-flow.slo new file mode 100644 index 0000000..fcfae37 --- /dev/null +++ b/examples/array-direct-scalars-value-flow.slo @@ -0,0 +1,59 @@ +(module main) + +(fn make_i32_values ((base i32)) -> (array i32 3) + (array i32 base (+ base 1) (+ base 2))) + +(fn make_i64_values ((base i64)) -> (array i64 3) + (array i64 base (+ base 1i64) (+ base 2i64))) + +(fn make_f64_values ((base f64)) -> (array f64 3) + (array f64 base (+ base 1.0) (+ base 2.0))) + +(fn make_flags ((first bool)) -> (array bool 3) + (array bool first false true)) + +(fn i32_at ((values (array i32 3)) (i i32)) -> i32 + (index values i)) + +(fn i64_at ((values (array i64 3)) (i i32)) -> i64 + (index values i)) + +(fn f64_at ((values (array f64 3)) (i i32)) -> f64 + (index values i)) + +(fn bool_at ((values (array bool 3)) (i i32)) -> bool + (index values i)) + +(fn i64_local_array_flow ((i i32)) -> i64 + (let values (array i64 3) (make_i64_values 40i64)) + (i64_at values i)) + +(fn f64_parameter_local_copy ((values (array f64 3)) (i i32)) -> f64 + (let copy (array f64 3) values) + (index copy i)) + +(fn echo_flags ((values (array bool 3))) -> (array bool 3) + values) + +(fn bool_call_return_index ((i i32)) -> bool + (index (make_flags true) i)) + +(test "i32 array parameter value flow" + (= (i32_at (make_i32_values 7) 0) 7)) + +(test "i64 array local call value flow" + (= (i64_local_array_flow 2) 42i64)) + +(test "f64 array parameter local copy" + (= (f64_parameter_local_copy (make_f64_values 4.0) 1) 5.0)) + +(test "bool array dynamic index" + (bool_at (make_flags true) 2)) + +(test "bool array return call value flow" + (index (echo_flags (make_flags false)) 2)) + +(fn main () -> i32 + (if (bool_call_return_index 0) + 0 + 1)) diff --git a/examples/array-direct-scalars.slo b/examples/array-direct-scalars.slo new file mode 100644 index 0000000..cb2a494 --- /dev/null +++ b/examples/array-direct-scalars.slo @@ -0,0 +1,32 @@ +(module main) + +(fn i32_second () -> i32 + (index (array i32 10 20 30) 1)) + +(fn i64_local_pick () -> i64 + (let values (array i64 3) (array i64 4i64 5i64 6i64)) + (index values 2)) + +(fn f64_third () -> f64 + (index (array f64 1.5 2.5 3.5) 2)) + +(fn bool_local_pick () -> bool + (let flags (array bool 3) (array bool false true false)) + (index flags 1)) + +(test "i32 direct scalar array index" + (= (i32_second) 20)) + +(test "i64 local direct scalar array index" + (= (i64_local_pick) 6i64)) + +(test "f64 direct scalar array index" + (= (f64_third) 3.5)) + +(test "bool local direct scalar array index" + (bool_local_pick)) + +(fn main () -> i32 + (if (bool_local_pick) + (i32_second) + 0)) diff --git a/examples/array-enum.slo b/examples/array-enum.slo new file mode 100644 index 0000000..3444aa6 --- /dev/null +++ b/examples/array-enum.slo @@ -0,0 +1,34 @@ +(module main) + +(enum Color Red Blue Green) + +(fn make_palette () -> (array Color 3) + (array Color (Color.Red) (Color.Blue) (Color.Green))) + +(fn echo_palette ((colors (array Color 3))) -> (array Color 3) + colors) + +(fn at ((colors (array Color 3)) (i i32)) -> Color + (index colors i)) + +(fn local_pick () -> Color + (let colors (array Color 3) (make_palette)) + (index colors 1)) + +(test "enum array immediate index" + (= (index (array Color (Color.Red) (Color.Blue) (Color.Green)) 2) (Color.Green))) + +(test "enum array local index" + (= (local_pick) (Color.Blue))) + +(test "enum array param return dynamic index" + (= (at (echo_palette (make_palette)) 0) (Color.Red))) + +(fn main () -> i32 + (match (at (make_palette) 1) + ((Color.Blue) + 0) + ((Color.Red) + 1) + ((Color.Green) + 1))) diff --git a/examples/array-string-value-flow.slo b/examples/array-string-value-flow.slo new file mode 100644 index 0000000..75ea05c --- /dev/null +++ b/examples/array-string-value-flow.slo @@ -0,0 +1,41 @@ +(module main) + +(fn make_words ((head string)) -> (array string 3) + (array string head "middle" "tail")) + +(fn at ((values (array string 3)) (i i32)) -> string + (index values i)) + +(fn echo ((values (array string 3))) -> (array string 3) + values) + +(fn call_return_index ((i i32)) -> string + (index (make_words "call") i)) + +(fn local_array_flow ((i i32)) -> string + (let words (array string 3) (make_words "local")) + (at words i)) + +(fn parameter_local_copy ((values (array string 3)) (i i32)) -> string + (let copy (array string 3) values) + (index copy i)) + +(test "string array parameter value flow" + (= (at (make_words "alpha") 0) "alpha")) + +(test "string array dynamic index" + (= (at (make_words "alpha") 2) "tail")) + +(test "string array local call value flow" + (= (local_array_flow 1) "middle")) + +(test "string array parameter local copy" + (= (parameter_local_copy (make_words "omega") 0) "omega")) + +(test "string array return call value flow" + (= (index (echo (make_words "zeta")) 2) "tail")) + +(fn main () -> i32 + (if (= (call_return_index 1) "middle") + 0 + 1)) diff --git a/examples/array-string.slo b/examples/array-string.slo new file mode 100644 index 0000000..cbefcb3 --- /dev/null +++ b/examples/array-string.slo @@ -0,0 +1,19 @@ +(module main) + +(fn immediate_second () -> string + (index (array string "sun" "moon" "star") 1)) + +(fn local_pick () -> string + (let words (array string 3) (array string "red" "green" "blue")) + (index words 2)) + +(test "string immediate array index" + (= (immediate_second) "moon")) + +(test "string local array index" + (= (local_pick) "blue")) + +(fn main () -> i32 + (if (= (local_pick) "blue") + 0 + 1)) diff --git a/examples/array-struct-elements.slo b/examples/array-struct-elements.slo new file mode 100644 index 0000000..2f20f49 --- /dev/null +++ b/examples/array-struct-elements.slo @@ -0,0 +1,41 @@ +(module main) + +(enum Color Red Blue Green) + +(struct Pixel + (x i32) + (label string) + (color Color)) + +(fn make_pixels ((head string)) -> (array Pixel 3) + (array Pixel (Pixel (x 1) (label head) (color (Color.Red))) (Pixel (x 2) (label "mid") (color (Color.Blue))) (Pixel (x 3) (label "tail") (color (Color.Green))))) + +(fn echo_pixels ((pixels (array Pixel 3))) -> (array Pixel 3) + pixels) + +(fn at ((pixels (array Pixel 3)) (i i32)) -> Pixel + (index pixels i)) + +(fn first_label ((head string)) -> string + (. (at (make_pixels head) 0) label)) + +(fn last_color ((head string)) -> Color + (. (index (echo_pixels (make_pixels head)) 2) color)) + +(test "struct array string field access" + (= (first_label "head") "head")) + +(test "struct array nested enum field access" + (= (last_color "head") (Color.Green))) + +(test "struct array local param return call flow" + (= (. (at (echo_pixels (make_pixels "sun")) 1) x) 2)) + +(fn main () -> i32 + (match (last_color "head") + ((Color.Green) + 0) + ((Color.Red) + 1) + ((Color.Blue) + 1))) diff --git a/examples/array-struct-fields.slo b/examples/array-struct-fields.slo new file mode 100644 index 0000000..b275d6d --- /dev/null +++ b/examples/array-struct-fields.slo @@ -0,0 +1,56 @@ +(module main) + +(struct ArrayRecord + (ints (array i32 3)) + (wides (array i64 2)) + (ratios (array f64 3)) + (flags (array bool 3)) + (words (array string 3))) + +(fn make_record ((base i32) (head string)) -> ArrayRecord + (ArrayRecord (ints (array i32 base (+ base 1) (+ base 2))) (wides (array i64 40i64 41i64)) (ratios (array f64 1.5 2.5 3.5)) (flags (array bool false true true)) (words (array string head "moon" "star")))) + +(fn echo_record ((record ArrayRecord)) -> ArrayRecord + record) + +(fn local_record ((head string)) -> ArrayRecord + (let record ArrayRecord (make_record 7 head)) + (echo_record record)) + +(fn int_at ((record ArrayRecord) (i i32)) -> i32 + (index (. record ints) i)) + +(fn wide_at ((record ArrayRecord) (i i32)) -> i64 + (index (. record wides) i)) + +(fn ratio_at ((record ArrayRecord) (i i32)) -> f64 + (index (. record ratios) i)) + +(fn flag_at ((record ArrayRecord) (i i32)) -> bool + (index (. record flags) i)) + +(fn word_at ((record ArrayRecord) (i i32)) -> string + (index (. record words) i)) + +(test "struct array i32 field access" + (= (int_at (make_record 7 "sun") 2) 9)) + +(test "struct array i64 field access" + (= (wide_at (make_record 7 "sun") 1) 41i64)) + +(test "struct array f64 field access" + (= (ratio_at (make_record 7 "sun") 2) 3.5)) + +(test "struct array bool field access" + (flag_at (make_record 7 "sun") 1)) + +(test "struct array string field access" + (= (word_at (make_record 7 "sun") 1) "moon")) + +(test "struct array local param return call flow" + (= (word_at (local_record "sun") 0) "sun")) + +(fn main () -> i32 + (if (flag_at (local_record "sun") 2) + 0 + 1)) diff --git a/examples/array-value-flow.slo b/examples/array-value-flow.slo new file mode 100644 index 0000000..7774902 --- /dev/null +++ b/examples/array-value-flow.slo @@ -0,0 +1,42 @@ +(module main) + +(fn make_values ((base i32)) -> (array i32 3) + (array i32 base (+ base 1) (+ base 2))) + +(fn first ((values (array i32 3))) -> i32 + (index values 0)) + +(fn at ((values (array i32 3)) (i i32)) -> i32 + (index values i)) + +(fn echo ((values (array i32 3))) -> (array i32 3) + values) + +(fn call_return_index ((i i32)) -> i32 + (index (make_values 10) i)) + +(fn local_array_flow ((i i32)) -> i32 + (let values (array i32 3) (make_values 20)) + (at values i)) + +(fn parameter_local_copy ((values (array i32 3)) (i i32)) -> i32 + (let copy (array i32 3) values) + (index copy i)) + +(test "array parameter value flow" + (= (first (make_values 7)) 7)) + +(test "array dynamic index" + (= (at (make_values 4) 2) 6)) + +(test "array local call value flow" + (= (local_array_flow 1) 21)) + +(test "array parameter local copy" + (= (parameter_local_copy (make_values 40) 2) 42)) + +(test "array return call value flow" + (= (index (echo (make_values 30)) 2) 32)) + +(fn main () -> i32 + (call_return_index 1)) diff --git a/examples/array.slo b/examples/array.slo new file mode 100644 index 0000000..9ca3674 --- /dev/null +++ b/examples/array.slo @@ -0,0 +1,18 @@ +(module main) + +(fn immediate_second () -> i32 + (index (array i32 10 20 30) 1)) + +(fn local_sum () -> i32 + (let values (array i32 3) (array i32 4 5 6)) + (+ (index values 0) (index values 2))) + +(test "immediate array index" + (= (immediate_second) 20)) + +(test "array local index" + (let values (array i32 3) (array i32 7 8 9)) + (= (index values 2) 9)) + +(fn main () -> i32 + (+ (immediate_second) (local_sum))) diff --git a/examples/boolean-logic.slo b/examples/boolean-logic.slo new file mode 100644 index 0000000..c9dac75 --- /dev/null +++ b/examples/boolean-logic.slo @@ -0,0 +1,58 @@ +(module main) + +(fn and_true () -> bool + (and true true)) + +(fn and_false () -> bool + (and true false)) + +(fn or_true () -> bool + (or false true)) + +(fn not_false () -> bool + (not false)) + +(fn and_short_circuits () -> bool + (not (and false (= (/ 1 0) 0)))) + +(fn or_short_circuits () -> bool + (or true (= (/ 1 0) 0))) + +(fn logic_ok () -> bool + (if (and_true) + (if (not (and_false)) + (if (or_true) + (if (not_false) + (if (and_short_circuits) + (or_short_circuits) + false) + false) + false) + false) + false)) + +(fn main () -> i32 + (if (logic_ok) + 42 + 1)) + +(test "and true" + (and_true)) + +(test "and false" + (not (and_false))) + +(test "or true" + (or_true)) + +(test "not false" + (not_false)) + +(test "and short circuit" + (and_short_circuits)) + +(test "or short circuit" + (or_short_circuits)) + +(test "boolean logic summary" + (logic_ok)) diff --git a/examples/checked-i64-to-i32-conversion.slo b/examples/checked-i64-to-i32-conversion.slo new file mode 100644 index 0000000..e0905f7 --- /dev/null +++ b/examples/checked-i64-to-i32-conversion.slo @@ -0,0 +1,57 @@ +(module main) + +(fn narrow ((value i64)) -> (result i32 i32) + (std.num.i64_to_i32_result value)) + +(fn low_ok () -> (result i32 i32) + (narrow -2147483648i64)) + +(fn high_ok () -> (result i32 i32) + (narrow 2147483647i64)) + +(fn negative_ok () -> (result i32 i32) + (narrow -7i64)) + +(fn below_low_err () -> (result i32 i32) + (narrow -2147483649i64)) + +(fn above_high_err () -> (result i32 i32) + (narrow 2147483648i64)) + +(test "i64 low bound narrows to i32" + (let value (result i32 i32) (low_ok)) + (if (std.result.is_ok value) + (= (std.result.unwrap_ok value) -2147483648) + false)) + +(test "i64 high bound narrows to i32" + (let value (result i32 i32) (high_ok)) + (if (std.result.is_ok value) + (= (std.result.unwrap_ok value) 2147483647) + false)) + +(test "negative i64 narrows to i32" + (let value (result i32 i32) (negative_ok)) + (if (std.result.is_ok value) + (= (std.result.unwrap_ok value) -7) + false)) + +(test "below i32 range returns err" + (let value (result i32 i32) (below_low_err)) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "above i32 range returns err" + (let value (result i32 i32) (above_high_err)) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(fn main () -> i32 + (let value (result i32 i32) (negative_ok)) + (if (std.result.is_ok value) + (if (= (std.result.unwrap_ok value) -7) + 0 + 1) + (std.result.unwrap_err value))) diff --git a/examples/composite-locals.slo b/examples/composite-locals.slo new file mode 100644 index 0000000..41f00c3 --- /dev/null +++ b/examples/composite-locals.slo @@ -0,0 +1,294 @@ +(module main) + +(struct Point + (x i32) + (y i32)) + +(struct PrimitiveRecord + (id i32) + (active bool) + (label string)) + +(struct NumericRecord + (wide i64) + (ratio f64)) + +(enum Signal Red Yellow Green) + +(enum Reading + Missing + (Value i32)) + +(struct TaggedReading + (status Signal) + (reading Reading)) + +(fn make_point ((x i32) (y i32)) -> Point + (Point (x x) (y y))) + +(fn make_primitive_record ((id i32) (label string)) -> PrimitiveRecord + (PrimitiveRecord (id id) (active (= id 7)) (label label))) + +(fn make_numeric_record ((wide i64) (ratio f64)) -> NumericRecord + (NumericRecord (wide wide) (ratio ratio))) + +(fn make_tagged ((status Signal) (reading Reading)) -> TaggedReading + (TaggedReading (status status) (reading reading))) + +(fn vec_i32_pair ((base i32)) -> (vec i32) + (let values (vec i32) (std.vec.i32.empty)) + (let first (vec i32) (std.vec.i32.append values base)) + (std.vec.i32.append first (+ base 1))) + +(fn vec_i64_pair ((base i64)) -> (vec i64) + (let values (vec i64) (std.vec.i64.empty)) + (let first (vec i64) (std.vec.i64.append values base)) + (std.vec.i64.append first (+ base 1i64))) + +(fn vec_f64_pair ((base f64)) -> (vec f64) + (let values (vec f64) (std.vec.f64.empty)) + (let first (vec f64) (std.vec.f64.append values base)) + (std.vec.f64.append first (+ base 1.0))) + +(fn vec_bool_pair ((first bool) (second bool)) -> (vec bool) + (let values (vec bool) (std.vec.bool.empty)) + (let left (vec bool) (std.vec.bool.append values first)) + (std.vec.bool.append left second)) + +(fn vec_string_pair ((first string) (second string)) -> (vec string) + (let values (vec string) (std.vec.string.empty)) + (let left (vec string) (std.vec.string.append values first)) + (std.vec.string.append left second)) + +(fn swap_string ((first string) (second string)) -> string + (var current string first) + (set current second) + current) + +(fn vec_i32_local_value () -> i32 + (var current (vec i32) (vec_i32_pair 10)) + (set current (vec_i32_pair 40)) + (std.vec.i32.index current 1)) + +(fn vec_i64_local_value () -> i64 + (var current (vec i64) (vec_i64_pair 10i64)) + (set current (vec_i64_pair 40i64)) + (std.vec.i64.index current 1)) + +(fn vec_f64_local_value () -> f64 + (var current (vec f64) (vec_f64_pair 10.0)) + (set current (vec_f64_pair 40.0)) + (std.vec.f64.index current 1)) + +(fn vec_bool_local_value () -> bool + (var current (vec bool) (vec_bool_pair true false)) + (set current (vec_bool_pair false true)) + (std.vec.bool.index current 1)) + +(fn vec_string_local_value () -> string + (var current (vec string) (vec_string_pair "oak" "elm")) + (set current (vec_string_pair "pine" "slovo")) + (std.vec.string.index current 1)) + +(fn option_i32_local_value () -> i32 + (var current (option i32) (none i32)) + (set current (some i32 42)) + (match current + ((some payload) + payload) + ((none) + 0))) + +(fn option_i64_local_value () -> i64 + (var current (option i64) (none i64)) + (set current (some i64 42000000000i64)) + (match current + ((some payload) + payload) + ((none) + 0i64))) + +(fn option_f64_local_value () -> f64 + (var current (option f64) (none f64)) + (set current (some f64 42.5)) + (match current + ((some payload) + payload) + ((none) + 0.0))) + +(fn option_bool_local_value () -> bool + (var current (option bool) (none bool)) + (set current (some bool true)) + (match current + ((some payload) + payload) + ((none) + false))) + +(fn option_string_local_value () -> string + (var current (option string) (none string)) + (set current (some string "branch")) + (match current + ((some payload) + payload) + ((none) + "fallback"))) + +(fn result_i32_local_value () -> i32 + (var current (result i32 i32) (err i32 i32 7)) + (set current (ok i32 i32 42)) + (match current + ((ok payload) + payload) + ((err code) + code))) + +(fn result_i64_local_value () -> i64 + (var current (result i64 i32) (err i64 i32 7)) + (set current (ok i64 i32 42000000000i64)) + (match current + ((ok payload) + payload) + ((err code) + 0i64))) + +(fn result_f64_local_value () -> f64 + (var current (result f64 i32) (err f64 i32 7)) + (set current (ok f64 i32 42.5)) + (match current + ((ok payload) + payload) + ((err code) + 0.0))) + +(fn result_bool_local_value () -> bool + (var current (result bool i32) (err bool i32 7)) + (set current (ok bool i32 true)) + (match current + ((ok payload) + payload) + ((err code) + false))) + +(fn result_string_local_value () -> string + (var current (result string i32) (err string i32 7)) + (set current (ok string i32 "slovo")) + (match current + ((ok payload) + payload) + ((err code) + "fallback"))) + +(fn point_local_sum () -> i32 + (var current Point (make_point 1 2)) + (set current (make_point 20 22)) + (+ (. current x) (. current y))) + +(fn primitive_record_local_label () -> string + (var current PrimitiveRecord (make_primitive_record 3 "alpha")) + (set current (make_primitive_record 7 "beta")) + (. current label)) + +(fn numeric_record_local_ratio () -> f64 + (var current NumericRecord (make_numeric_record 10i64 1.0)) + (set current (make_numeric_record 20i64 3.5)) + (. current ratio)) + +(fn signal_local_code () -> i32 + (var current Signal (Signal.Red)) + (set current (Signal.Green)) + (match current + ((Signal.Red) + 1) + ((Signal.Yellow) + 2) + ((Signal.Green) + 3))) + +(fn reading_local_code () -> i32 + (var current Reading (Reading.Missing)) + (set current (Reading.Value 42)) + (match current + ((Reading.Missing) + 0) + ((Reading.Value payload) + payload))) + +(fn tagged_local_code () -> i32 + (var current TaggedReading (make_tagged (Signal.Yellow) (Reading.Missing))) + (set current (make_tagged (Signal.Green) (Reading.Value 42))) + (match (. current reading) + ((Reading.Missing) + 0) + ((Reading.Value payload) + payload))) + +(test "mutable string local set works" + (= (swap_string "oak" "slovo") "slovo")) + +(test "mutable vec i32 local set works" + (= (vec_i32_local_value) 41)) + +(test "mutable vec i64 local set works" + (= (vec_i64_local_value) 41i64)) + +(test "mutable vec f64 local set works" + (= (vec_f64_local_value) 41.0)) + +(test "mutable vec bool local set works" + (vec_bool_local_value)) + +(test "mutable vec string local set works" + (= (vec_string_local_value) "slovo")) + +(test "mutable option i32 local set works" + (= (option_i32_local_value) 42)) + +(test "mutable option i64 local set works" + (= (option_i64_local_value) 42000000000i64)) + +(test "mutable option f64 local set works" + (= (option_f64_local_value) 42.5)) + +(test "mutable option bool local set works" + (option_bool_local_value)) + +(test "mutable option string local set works" + (= (option_string_local_value) "branch")) + +(test "mutable result i32 local set works" + (= (result_i32_local_value) 42)) + +(test "mutable result i64 local set works" + (= (result_i64_local_value) 42000000000i64)) + +(test "mutable result f64 local set works" + (= (result_f64_local_value) 42.5)) + +(test "mutable result bool local set works" + (result_bool_local_value)) + +(test "mutable result string local set works" + (= (result_string_local_value) "slovo")) + +(test "mutable plain struct local set works" + (= (point_local_sum) 42)) + +(test "mutable primitive struct local set works" + (= (primitive_record_local_label) "beta")) + +(test "mutable numeric struct local set works" + (= (numeric_record_local_ratio) 3.5)) + +(test "mutable payloadless enum local set works" + (= (signal_local_code) 3)) + +(test "mutable payload enum local set works" + (= (reading_local_code) 42)) + +(test "mutable enum struct local set works" + (= (tagged_local_code) 42)) + +(fn main () -> i32 + (tagged_local_code)) diff --git a/examples/composite-struct-fields.slo b/examples/composite-struct-fields.slo new file mode 100644 index 0000000..fd3028a --- /dev/null +++ b/examples/composite-struct-fields.slo @@ -0,0 +1,212 @@ +(module main) + +(enum Status Ready Blocked) + +(struct Pair + (left i32) + (right i32)) + +(struct CompositeRecord + (ints (vec i32)) + (wides (vec i64)) + (ratios (vec f64)) + (flags (vec bool)) + (labels (vec string)) + (maybe_count (option i32)) + (maybe_wide (option i64)) + (maybe_ratio (option f64)) + (maybe_flag (option bool)) + (maybe_label (option string)) + (count_result (result i32 i32)) + (wide_result (result i64 i32)) + (ratio_result (result f64 i32)) + (flag_result (result bool i32)) + (label_result (result string i32)) + (pair Pair) + (status Status)) + +(fn make_pair ((left i32) (right i32)) -> Pair + (Pair (left left) (right right))) + +(fn vec_i32_pair ((base i32)) -> (vec i32) + (let values (vec i32) (std.vec.i32.empty)) + (let first (vec i32) (std.vec.i32.append values base)) + (std.vec.i32.append first (+ base 1))) + +(fn vec_i64_pair ((base i64)) -> (vec i64) + (let values (vec i64) (std.vec.i64.empty)) + (let first (vec i64) (std.vec.i64.append values base)) + (std.vec.i64.append first (+ base 1i64))) + +(fn vec_f64_pair ((base f64)) -> (vec f64) + (let values (vec f64) (std.vec.f64.empty)) + (let first (vec f64) (std.vec.f64.append values base)) + (std.vec.f64.append first (+ base 1.0))) + +(fn vec_bool_pair ((first bool) (second bool)) -> (vec bool) + (let values (vec bool) (std.vec.bool.empty)) + (let left (vec bool) (std.vec.bool.append values first)) + (std.vec.bool.append left second)) + +(fn vec_string_pair ((first string) (second string)) -> (vec string) + (let values (vec string) (std.vec.string.empty)) + (let left (vec string) (std.vec.string.append values first)) + (std.vec.string.append left second)) + +(fn make_record () -> CompositeRecord + (CompositeRecord (ints (vec_i32_pair 10)) (wides (vec_i64_pair 100i64)) (ratios (vec_f64_pair 3.5)) (flags (vec_bool_pair false true)) (labels (vec_string_pair "oak" "elm")) (maybe_count (some i32 7)) (maybe_wide (some i64 7000000000i64)) (maybe_ratio (some f64 7.5)) (maybe_flag (some bool true)) (maybe_label (some string "alpha")) (count_result (ok i32 i32 9)) (wide_result (ok i64 i32 9000000000i64)) (ratio_result (ok f64 i32 9.25)) (flag_result (ok bool i32 true)) (label_result (ok string i32 "beta")) (pair (make_pair 20 22)) (status (Status.Ready)))) + +(fn echo_record ((record CompositeRecord)) -> CompositeRecord + record) + +(fn local_record () -> CompositeRecord + (let record CompositeRecord (make_record)) + (echo_record record)) + +(fn ints_second ((record CompositeRecord)) -> i32 + (std.vec.i32.index (. record ints) 1)) + +(fn wides_second ((record CompositeRecord)) -> i64 + (std.vec.i64.index (. record wides) 1)) + +(fn ratios_second ((record CompositeRecord)) -> f64 + (std.vec.f64.index (. record ratios) 1)) + +(fn flags_second ((record CompositeRecord)) -> bool + (std.vec.bool.index (. record flags) 1)) + +(fn labels_second ((record CompositeRecord)) -> string + (std.vec.string.index (. record labels) 1)) + +(fn maybe_count_value ((record CompositeRecord)) -> i32 + (match (. record maybe_count) + ((some payload) + payload) + ((none) + 0))) + +(fn maybe_wide_value ((record CompositeRecord)) -> i64 + (match (. record maybe_wide) + ((some payload) + payload) + ((none) + 0i64))) + +(fn maybe_ratio_value ((record CompositeRecord)) -> f64 + (match (. record maybe_ratio) + ((some payload) + payload) + ((none) + 0.0))) + +(fn maybe_flag_value ((record CompositeRecord)) -> bool + (match (. record maybe_flag) + ((some payload) + payload) + ((none) + false))) + +(fn maybe_label_value ((record CompositeRecord)) -> string + (match (. record maybe_label) + ((some payload) + payload) + ((none) + "missing"))) + +(fn count_result_value ((record CompositeRecord)) -> i32 + (match (. record count_result) + ((ok payload) + payload) + ((err code) + code))) + +(fn wide_result_value ((record CompositeRecord)) -> i64 + (match (. record wide_result) + ((ok payload) + payload) + ((err code) + 0i64))) + +(fn ratio_result_value ((record CompositeRecord)) -> f64 + (match (. record ratio_result) + ((ok payload) + payload) + ((err code) + 0.0))) + +(fn flag_result_value ((record CompositeRecord)) -> bool + (match (. record flag_result) + ((ok payload) + payload) + ((err code) + false))) + +(fn label_result_value ((record CompositeRecord)) -> string + (match (. record label_result) + ((ok payload) + payload) + ((err code) + "missing"))) + +(fn pair_total ((record CompositeRecord)) -> i32 + (+ (. (. record pair) left) (. (. record pair) right))) + +(fn is_ready ((record CompositeRecord)) -> bool + (= (. record status) (Status.Ready))) + +(test "struct vec i32 field access" + (= (ints_second (make_record)) 11)) + +(test "struct vec i64 field access" + (= (wides_second (make_record)) 101i64)) + +(test "struct vec f64 field access" + (= (ratios_second (make_record)) 4.5)) + +(test "struct vec bool field access" + (flags_second (make_record))) + +(test "struct vec string field access" + (= (labels_second (make_record)) "elm")) + +(test "struct option i32 field access" + (= (maybe_count_value (make_record)) 7)) + +(test "struct option i64 field access" + (= (maybe_wide_value (make_record)) 7000000000i64)) + +(test "struct option f64 field access" + (= (maybe_ratio_value (make_record)) 7.5)) + +(test "struct option bool field access" + (maybe_flag_value (make_record))) + +(test "struct option string field access" + (= (maybe_label_value (make_record)) "alpha")) + +(test "struct result i32 field access" + (= (count_result_value (make_record)) 9)) + +(test "struct result i64 field access" + (= (wide_result_value (make_record)) 9000000000i64)) + +(test "struct result f64 field access" + (= (ratio_result_value (make_record)) 9.25)) + +(test "struct result bool field access" + (flag_result_value (make_record))) + +(test "struct result string field access" + (= (label_result_value (make_record)) "beta")) + +(test "nested struct field access" + (= (pair_total (make_record)) 42)) + +(test "nested struct local param return call flow" + (= (pair_total (local_record)) 42)) + +(test "direct enum struct field equality" + (is_ready (make_record))) + +(fn main () -> i32 + (pair_total (local_record))) diff --git a/examples/enum-basic.slo b/examples/enum-basic.slo new file mode 100644 index 0000000..b4e3799 --- /dev/null +++ b/examples/enum-basic.slo @@ -0,0 +1,53 @@ +(module main) + +(enum Signal Red Yellow Green) + +(fn red () -> Signal + (Signal.Red)) + +(fn yellow () -> Signal + (Signal.Yellow)) + +(fn echo ((signal Signal)) -> Signal + signal) + +(fn local_signal () -> Signal + (let signal Signal (Signal.Green)) + signal) + +(fn same_signal ((left Signal) (right Signal)) -> bool + (= left right)) + +(fn signal_code ((signal Signal)) -> i32 + (match signal + ((Signal.Red) + 1) + ((Signal.Yellow) + (let code i32 2) + code) + ((Signal.Green) + 3))) + +(fn call_flow () -> Signal + (echo (local_signal))) + +(test "enum constructor equality" + (= (Signal.Red) (red))) + +(test "enum local return call flow" + (= (call_flow) (Signal.Green))) + +(test "enum parameter equality" + (same_signal (yellow) (Signal.Yellow))) + +(test "enum match red" + (= (signal_code (Signal.Red)) 1)) + +(test "enum match multi expression arm" + (= (signal_code (Signal.Yellow)) 2)) + +(test "enum match green" + (= (signal_code (call_flow)) 3)) + +(fn main () -> i32 + (signal_code (call_flow))) diff --git a/examples/enum-payload-direct-scalars.slo b/examples/enum-payload-direct-scalars.slo new file mode 100644 index 0000000..eb69cc7 --- /dev/null +++ b/examples/enum-payload-direct-scalars.slo @@ -0,0 +1,202 @@ +(module main) + +(enum Reading + Missing + (Value i32) + (Offset i32)) + +(enum WideReading + Missing + (Value i64)) + +(enum RatioReading + Missing + (Value f64)) + +(enum FlagReading + Missing + (Value bool)) + +(enum LabelReading + Missing + (Value string)) + +(struct PayloadRecord + (reading Reading) + (wide WideReading) + (ratio RatioReading) + (flag FlagReading) + (label LabelReading)) + +(fn value ((payload i32)) -> Reading + (Reading.Value payload)) + +(fn echo_reading ((reading Reading)) -> Reading + reading) + +(fn local_reading ((payload i32)) -> Reading + (let reading Reading (Reading.Value payload)) + reading) + +(fn call_reading ((payload i32)) -> Reading + (echo_reading (local_reading payload))) + +(fn reading_code ((reading Reading)) -> i32 + (match reading + ((Reading.Missing) + 0) + ((Reading.Value payload) + payload) + ((Reading.Offset payload) + (+ payload 100)))) + +(fn wide_value ((payload i64)) -> WideReading + (WideReading.Value payload)) + +(fn echo_wide ((reading WideReading)) -> WideReading + reading) + +(fn local_wide ((payload i64)) -> WideReading + (let reading WideReading (WideReading.Value payload)) + reading) + +(fn call_wide ((payload i64)) -> WideReading + (echo_wide (local_wide payload))) + +(fn wide_code ((reading WideReading)) -> i64 + (match reading + ((WideReading.Missing) + 0i64) + ((WideReading.Value payload) + payload))) + +(fn ratio_value ((payload f64)) -> RatioReading + (RatioReading.Value payload)) + +(fn ratio_code ((reading RatioReading)) -> f64 + (match reading + ((RatioReading.Missing) + 0.0) + ((RatioReading.Value payload) + payload))) + +(fn flag_value ((payload bool)) -> FlagReading + (FlagReading.Value payload)) + +(fn flag_code ((reading FlagReading)) -> bool + (match reading + ((FlagReading.Missing) + false) + ((FlagReading.Value payload) + payload))) + +(fn label_value ((payload string)) -> LabelReading + (LabelReading.Value payload)) + +(fn echo_label ((reading LabelReading)) -> LabelReading + reading) + +(fn local_label ((payload string)) -> LabelReading + (let reading LabelReading (LabelReading.Value payload)) + reading) + +(fn call_label ((payload string)) -> LabelReading + (echo_label (local_label payload))) + +(fn label_text ((reading LabelReading)) -> string + (match reading + ((LabelReading.Missing) + "") + ((LabelReading.Value payload) + payload))) + +(fn pack_record ((reading Reading) (wide WideReading) (ratio RatioReading) (flag FlagReading) (label LabelReading)) -> PayloadRecord + (PayloadRecord (reading reading) (wide wide) (ratio ratio) (flag flag) (label label))) + +(fn make_record () -> PayloadRecord + (pack_record (Reading.Offset 5) (WideReading.Value 4294967296i64) (RatioReading.Value 3.5) (FlagReading.Value true) (LabelReading.Value "hello"))) + +(fn echo_record ((record PayloadRecord)) -> PayloadRecord + record) + +(fn local_record () -> PayloadRecord + (let record PayloadRecord (make_record)) + (echo_record record)) + +(fn record_reading_code ((record PayloadRecord)) -> i32 + (reading_code (. record reading))) + +(fn record_wide_code ((record PayloadRecord)) -> i64 + (wide_code (. record wide))) + +(fn record_ratio_code ((record PayloadRecord)) -> f64 + (ratio_code (. record ratio))) + +(fn record_flag_code ((record PayloadRecord)) -> bool + (flag_code (. record flag))) + +(fn record_label_text ((record PayloadRecord)) -> string + (label_text (. record label))) + +(test "i32 enum constructor equality" + (= (Reading.Value 7) (value 7))) + +(test "i32 enum equality compares payload" + (if (= (Reading.Value 7) (Reading.Value 8)) + false + true)) + +(test "i32 enum payloadless equality" + (= (Reading.Missing) (echo_reading (Reading.Missing)))) + +(test "i32 enum local return call flow" + (= (call_reading 9) (Reading.Value 9))) + +(test "i64 enum constructor equality" + (= (WideReading.Value 4294967296i64) (wide_value 4294967296i64))) + +(test "i64 enum local return call flow" + (= (call_wide 4294967297i64) (WideReading.Value 4294967297i64))) + +(test "f64 enum constructor equality" + (= (RatioReading.Value 3.5) (ratio_value 3.5))) + +(test "f64 enum equality compares payload" + (if (= (RatioReading.Value 3.5) (RatioReading.Value 4.5)) + false + true)) + +(test "bool enum constructor equality" + (= (FlagReading.Value true) (flag_value true))) + +(test "bool enum match value" + (flag_code (FlagReading.Value true))) + +(test "string enum constructor equality" + (= (LabelReading.Value "hello") (label_value "hello"))) + +(test "string enum equality compares payload" + (if (= (LabelReading.Value "left") (LabelReading.Value "right")) + false + true)) + +(test "string enum local return call flow" + (= (call_label "hello") (LabelReading.Value "hello"))) + +(test "struct field enum i32 flow" + (= (record_reading_code (local_record)) 105)) + +(test "struct field enum i64 flow" + (= (record_wide_code (local_record)) 4294967296i64)) + +(test "struct field enum f64 flow" + (= (record_ratio_code (local_record)) 3.5)) + +(test "struct field enum bool flow" + (record_flag_code (local_record))) + +(test "struct field enum string flow" + (= (record_label_text (local_record)) "hello")) + +(fn main () -> i32 + (record_reading_code (local_record))) diff --git a/examples/enum-payload-i32.slo b/examples/enum-payload-i32.slo new file mode 100644 index 0000000..39e9007 --- /dev/null +++ b/examples/enum-payload-i32.slo @@ -0,0 +1,63 @@ +(module main) + +(enum Reading + Missing + (Value i32) + (Offset i32)) + +(fn missing () -> Reading + (Reading.Missing)) + +(fn value ((payload i32)) -> Reading + (Reading.Value payload)) + +(fn echo ((reading Reading)) -> Reading + reading) + +(fn local_reading ((payload i32)) -> Reading + (let reading Reading (Reading.Value payload)) + reading) + +(fn same_reading ((left Reading) (right Reading)) -> bool + (= left right)) + +(fn reading_code ((reading Reading)) -> i32 + (match reading + ((Reading.Missing) + 0) + ((Reading.Value payload) + payload) + ((Reading.Offset payload) + (+ payload 100)))) + +(fn call_flow ((payload i32)) -> Reading + (echo (local_reading payload))) + +(test "enum payload constructor equality" + (= (Reading.Value 7) (value 7))) + +(test "enum payload equality compares payload" + (if (= (Reading.Value 7) (Reading.Value 8)) + false + true)) + +(test "enum payloadless equality" + (= (Reading.Missing) (missing))) + +(test "enum payload local return call flow" + (= (call_flow 9) (Reading.Value 9))) + +(test "enum payload parameter equality" + (same_reading (value 11) (Reading.Value 11))) + +(test "enum payload match missing" + (= (reading_code (Reading.Missing)) 0)) + +(test "enum payload match value" + (= (reading_code (Reading.Value 12)) 12)) + +(test "enum payload match offset" + (= (reading_code (Reading.Offset 5)) 105)) + +(fn main () -> i32 + (reading_code (call_flow 42))) diff --git a/examples/enum-payload-structs.slo b/examples/enum-payload-structs.slo new file mode 100644 index 0000000..3589746 --- /dev/null +++ b/examples/enum-payload-structs.slo @@ -0,0 +1,78 @@ +(module main) + +(struct Packet + (values (array i32 3)) + (labels (array string 2)) + (flags (array bool 2))) + +(enum PacketState + Missing + (Live Packet) + (Cached Packet)) + +(fn make_packet ((base i32) (head string)) -> Packet + (Packet (values (array i32 base (+ base 1) (+ base 2))) (labels (array string head "tail")) (flags (array bool false true)))) + +(fn live ((packet Packet)) -> PacketState + (PacketState.Live packet)) + +(fn cached ((packet Packet)) -> PacketState + (PacketState.Cached packet)) + +(fn echo_state ((state PacketState)) -> PacketState + state) + +(fn local_state ((base i32) (head string)) -> PacketState + (let state PacketState (PacketState.Live (make_packet base head))) + state) + +(fn call_state ((base i32) (head string)) -> PacketState + (echo_state (cached (make_packet base head)))) + +(fn state_value ((state PacketState) (i i32)) -> i32 + (match state + ((PacketState.Missing) + 0) + ((PacketState.Live payload) + (index (. payload values) i)) + ((PacketState.Cached payload) + (index (. payload values) i)))) + +(fn state_label ((state PacketState) (i i32)) -> string + (match state + ((PacketState.Missing) + "") + ((PacketState.Live payload) + (index (. payload labels) i)) + ((PacketState.Cached payload) + (index (. payload labels) i)))) + +(fn state_flag ((state PacketState) (i i32)) -> bool + (match state + ((PacketState.Missing) + false) + ((PacketState.Live payload) + (index (. payload flags) i)) + ((PacketState.Cached payload) + (index (. payload flags) i)))) + +(test "struct payload missing arm" + (= (state_value (PacketState.Missing) 0) 0)) + +(test "struct payload live constructor flow" + (= (state_value (live (make_packet 7 "sun")) 2) 9)) + +(test "struct payload cached param return call flow" + (= (state_value (call_state 7 "sun") 1) 8)) + +(test "struct payload string array field access" + (= (state_label (call_state 7 "sun") 0) "sun")) + +(test "struct payload bool array field access" + (state_flag (call_state 7 "sun") 1)) + +(test "struct payload local return flow" + (= (state_label (local_state 9 "orb") 1) "tail")) + +(fn main () -> i32 + (state_value (call_state 7 "sun") 2)) diff --git a/examples/enum-struct-fields.slo b/examples/enum-struct-fields.slo new file mode 100644 index 0000000..46ff92e --- /dev/null +++ b/examples/enum-struct-fields.slo @@ -0,0 +1,67 @@ +(module main) + +(enum Status Ready Blocked) + +(enum Reading + Missing + (Value i32)) + +(struct TaggedReading + (status Status) + (reading Reading)) + +(fn make_tagged ((status Status) (reading Reading)) -> TaggedReading + (TaggedReading (status status) (reading reading))) + +(fn ready_value ((payload i32)) -> TaggedReading + (make_tagged (Status.Ready) (Reading.Value payload))) + +(fn missing_blocked () -> TaggedReading + (make_tagged (Status.Blocked) (Reading.Missing))) + +(fn echo_tagged ((tagged TaggedReading)) -> TaggedReading + tagged) + +(fn local_tagged ((payload i32)) -> TaggedReading + (let tagged TaggedReading (ready_value payload)) + (echo_tagged tagged)) + +(fn status_of ((tagged TaggedReading)) -> Status + (. tagged status)) + +(fn reading_of ((tagged TaggedReading)) -> Reading + (. tagged reading)) + +(fn is_ready ((tagged TaggedReading)) -> bool + (= (. tagged status) (Status.Ready))) + +(fn reading_matches ((tagged TaggedReading) (reading Reading)) -> bool + (= (. tagged reading) reading)) + +(fn reading_code ((tagged TaggedReading)) -> i32 + (match (. tagged reading) + ((Reading.Missing) + 0) + ((Reading.Value payload) + payload))) + +(test "enum struct field payloadless equality" + (= (status_of (missing_blocked)) (Status.Blocked))) + +(test "enum struct field unary payload equality" + (reading_matches (ready_value 7) (Reading.Value 7))) + +(test "enum struct field access return" + (= (reading_of (missing_blocked)) (Reading.Missing))) + +(test "enum struct field local param return call flow" + (= (reading_code (local_tagged 9)) 9)) + +(test "enum struct field match missing" + (= (reading_code (missing_blocked)) 0)) + +(test "enum struct field status predicate" + (is_ready (ready_value 11))) + +(fn main () -> i32 + (reading_code (local_tagged 42))) diff --git a/examples/f64-numeric-primitive.slo b/examples/f64-numeric-primitive.slo new file mode 100644 index 0000000..edd20b5 --- /dev/null +++ b/examples/f64-numeric-primitive.slo @@ -0,0 +1,32 @@ +(module main) + +(fn half ((value f64)) -> f64 + (/ value 2.0)) + +(fn weighted ((base f64) (scale f64)) -> f64 + (+ base (* scale 2.5))) + +(fn local_total () -> f64 + (let subtotal f64 (weighted 4.0 3.0)) + (- subtotal 1.5)) + +(fn close_enough ((value f64)) -> bool + (if (> value 9.0) + (< value 11.0) + false)) + +(fn exact_literal () -> bool + (= (half 7.0) 3.5)) + +(fn main () -> i32 + (std.io.print_f64 (local_total)) + (if (close_enough (local_total)) 0 1)) + +(test "f64 arithmetic returns exact fixture value" + (= (local_total) 10.0)) + +(test "f64 comparison works in predicates" + (close_enough (local_total))) + +(test "f64 division and equality" + (exact_literal)) diff --git a/examples/f64-to-i32-result.slo b/examples/f64-to-i32-result.slo new file mode 100644 index 0000000..601c149 --- /dev/null +++ b/examples/f64-to-i32-result.slo @@ -0,0 +1,48 @@ +(module main) + +(fn narrow ((value f64)) -> (result i32 i32) + (std.num.f64_to_i32_result value)) + +(fn zero_ok () -> (result i32 i32) + (narrow (- 2.5 2.5))) + +(fn negative_ok () -> (result i32 i32) + (narrow (- 1.0 13.0))) + +(fn fractional_err () -> (result i32 i32) + (narrow (/ 7.0 2.0))) + +(fn above_high_err () -> (result i32 i32) + (narrow 2147483648.0)) + +(test "f64 zero narrows to i32" + (let value (result i32 i32) (zero_ok)) + (if (std.result.is_ok value) + (= (std.result.unwrap_ok value) 0) + false)) + +(test "negative f64 narrows to i32" + (let value (result i32 i32) (negative_ok)) + (if (std.result.is_ok value) + (= (std.result.unwrap_ok value) -12) + false)) + +(test "fractional f64 returns err" + (let value (result i32 i32) (fractional_err)) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "above i32 range f64 returns err" + (let value (result i32 i32) (above_high_err)) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(fn main () -> i32 + (let value (result i32 i32) (negative_ok)) + (if (std.result.is_ok value) + (if (= (std.result.unwrap_ok value) -12) + 0 + 1) + (std.result.unwrap_err value))) diff --git a/examples/f64-to-i64-result.slo b/examples/f64-to-i64-result.slo new file mode 100644 index 0000000..8aedccd --- /dev/null +++ b/examples/f64-to-i64-result.slo @@ -0,0 +1,48 @@ +(module main) + +(fn narrow ((value f64)) -> (result i64 i32) + (std.num.f64_to_i64_result value)) + +(fn zero_ok () -> (result i64 i32) + (narrow (- 2.5 2.5))) + +(fn negative_ok () -> (result i64 i32) + (narrow (- 1.0 13.0))) + +(fn fractional_err () -> (result i64 i32) + (narrow (/ 7.0 2.0))) + +(fn above_high_err () -> (result i64 i32) + (narrow 9223372036854776000.0)) + +(test "f64 zero narrows to i64" + (let value (result i64 i32) (zero_ok)) + (if (std.result.is_ok value) + (= (std.result.unwrap_ok value) 0i64) + false)) + +(test "negative f64 narrows to i64" + (let value (result i64 i32) (negative_ok)) + (if (std.result.is_ok value) + (= (std.result.unwrap_ok value) -12i64) + false)) + +(test "fractional f64 returns err for i64" + (let value (result i64 i32) (fractional_err)) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "above i64 range f64 returns err" + (let value (result i64 i32) (above_high_err)) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(fn main () -> i32 + (let value (result i64 i32) (negative_ok)) + (if (std.result.is_ok value) + (if (= (std.result.unwrap_ok value) -12i64) + 0 + 1) + (std.result.unwrap_err value))) diff --git a/examples/f64-to-string.slo b/examples/f64-to-string.slo new file mode 100644 index 0000000..f24c865 --- /dev/null +++ b/examples/f64-to-string.slo @@ -0,0 +1,40 @@ +(module main) + +(fn f64_zero_text () -> string + (std.num.f64_to_string (- 2.5 2.5))) + +(fn f64_fractional_text () -> string + (std.num.f64_to_string (/ 7.0 2.0))) + +(fn f64_negative_text () -> string + (std.num.f64_to_string (- 2.5 4.0))) + +(fn f64_whole_text () -> string + (std.num.f64_to_string (+ 7.0 3.0))) + +(test "f64 zero to string" + (= (f64_zero_text) "0.0")) + +(test "f64 fractional to string" + (= (f64_fractional_text) "3.5")) + +(test "f64 negative to string" + (= (f64_negative_text) "-1.5")) + +(test "f64 whole to string" + (= (f64_whole_text) "10.0")) + +(test "f64 negative string length" + (= (std.string.len (f64_negative_text)) 4)) + +(test "f64 whole string length" + (= (std.string.len (f64_whole_text)) 4)) + +(fn main () -> i32 + (std.io.print_string (f64_zero_text)) + (std.io.print_string (f64_fractional_text)) + (std.io.print_string (f64_negative_text)) + (std.io.print_string (f64_whole_text)) + (if (= (std.string.len (f64_fractional_text)) 3) + 0 + 1)) diff --git a/examples/ffi/exp-6-c-add/c_add.c b/examples/ffi/exp-6-c-add/c_add.c new file mode 100644 index 0000000..eb71321 --- /dev/null +++ b/examples/ffi/exp-6-c-add/c_add.c @@ -0,0 +1,5 @@ +#include + +int32_t c_add(int32_t lhs, int32_t rhs) { + return lhs + rhs; +} diff --git a/examples/ffi/exp-6-c-add/main.slo b/examples/ffi/exp-6-c-add/main.slo new file mode 100644 index 0000000..7a54fc2 --- /dev/null +++ b/examples/ffi/exp-6-c-add/main.slo @@ -0,0 +1,15 @@ +(module main) + +(import_c c_add ((lhs i32) (rhs i32)) -> i32) + +(fn add_42 () -> i32 + (unsafe + (c_add 40 2))) + +(fn main () -> i32 + (add_42)) + +(test "C import test runner diagnostic" + (= (unsafe + (c_add 1 2)) + 3)) diff --git a/examples/ffi/exp-6-c-add/slovo.toml b/examples/ffi/exp-6-c-add/slovo.toml new file mode 100644 index 0000000..b5e71fa --- /dev/null +++ b/examples/ffi/exp-6-c-add/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "exp-6-c-add" +source_root = "." +entry = "main" diff --git a/examples/host-io-result.slo b/examples/host-io-result.slo new file mode 100644 index 0000000..3ed43a6 --- /dev/null +++ b/examples/host-io-result.slo @@ -0,0 +1,77 @@ +(module main) + +(fn fixture_path () -> string + "slovo-exp10-host-io-result.txt") + +(fn ok_text ((value string)) -> (result string i32) + (ok string i32 value)) + +(fn err_text ((code i32)) -> (result string i32) + (err string i32 code)) + +(fn result_text_or_error ((value (result string i32))) -> string + (match value + ((ok payload) + payload) + ((err code) + (std.io.print_i32 code) + "error"))) + +(fn result_text_len_or_code ((value (result string i32))) -> i32 + (match value + ((ok payload) + (std.string.len payload)) + ((err code) + code))) + +(fn write_fixture_result () -> (result i32 i32) + (std.fs.write_text_result (fixture_path) "exp10 host io")) + +(fn read_fixture_result () -> (result string i32) + (std.fs.write_text_result (fixture_path) "exp10 host io") + (std.fs.read_text_result (fixture_path))) + +(fn missing_env_result () -> (result string i32) + (std.env.get_result "SLOVO_EXP10_MISSING_ENV")) + +(fn first_arg_ok_score () -> i32 + (if (< 0 (std.process.argc)) + (if (is_ok (std.process.arg_result 0)) + 1 + 0) + 0)) + +(fn read_unwrapped_text () -> string + (unwrap_ok (read_fixture_result))) + +(fn missing_env_code () -> i32 + (unwrap_err (missing_env_result))) + +(test "string result constructor ok" + (= (result_text_or_error (ok_text "constructed")) "constructed")) + +(test "string result constructor err" + (= (result_text_len_or_code (err_text 1)) 1)) + +(test "missing env returns err 1" + (= (missing_env_code) 1)) + +(test "arg result is ok when argc is positive" + (= (first_arg_ok_score) (if (< 0 (std.process.argc)) + 1 + 0))) + +(test "write text result returns ok zero" + (= (unwrap_ok (write_fixture_result)) 0)) + +(test "read text result returns written text" + (= (read_unwrapped_text) "exp10 host io")) + +(test "read text result match observes string payload" + (= (result_text_len_or_code (read_fixture_result)) 13)) + +(fn main () -> i32 + (std.io.eprint "exp-10 host io result") + (if (is_ok (write_fixture_result)) + (unwrap_ok (write_fixture_result)) + 1)) diff --git a/examples/host-io.slo b/examples/host-io.slo new file mode 100644 index 0000000..7d1eec5 --- /dev/null +++ b/examples/host-io.slo @@ -0,0 +1,34 @@ +(module main) + +(fn fixture_path () -> string + "slovo-exp3-host-io.txt") + +(fn write_fixture () -> i32 + (std.fs.write_text (fixture_path) "host io")) + +(fn read_fixture () -> string + (std.fs.read_text (fixture_path))) + +(fn missing_env () -> string + (std.env.get "SLOVO_EXP3_MISSING")) + +(fn arg_count_plus_one () -> i32 + (+ (std.process.argc) 1)) + +(test "missing env returns empty string" + (= (missing_env) "")) + +(test "argc is not negative" + (< 0 (arg_count_plus_one))) + +(test "write text returns success" + (= (write_fixture) 0)) + +(test "read text returns written text" + (= (read_fixture) "host io")) + +(fn main () -> i32 + (std.io.eprint "exp-3 host io") + (std.io.print_string (std.process.arg 0)) + (std.io.print_string (read_fixture)) + (std.fs.write_text (fixture_path) "host io")) diff --git a/examples/i64-numeric-primitive.slo b/examples/i64-numeric-primitive.slo new file mode 100644 index 0000000..f297f92 --- /dev/null +++ b/examples/i64-numeric-primitive.slo @@ -0,0 +1,37 @@ +(module main) + +(fn base () -> i64 + 2147483648i64) + +(fn adjust ((value i64) (delta i64)) -> i64 + (+ value delta)) + +(fn doubled ((value i64)) -> i64 + (* value 2i64)) + +(fn local_total () -> i64 + (let offset i64 -7i64) + (adjust (- (doubled (base)) 0i64) offset)) + +(fn high_enough ((value i64)) -> bool + (if (> value 4294967280i64) + (< value 4294967300i64) + false)) + +(fn exact_i64 () -> bool + (= (local_total) 4294967289i64)) + +(fn main () -> i32 + (std.io.print_i64 (local_total)) + (if (high_enough (local_total)) 0 1)) + +(test "i64 arithmetic returns exact fixture value" + (exact_i64)) + +(test "i64 comparison works in predicates" + (high_enough (local_total))) + +(test "i64 division and ordering" + (if (>= (/ (local_total) 3i64) 1431655763i64) + (<= (/ (local_total) 3i64) 1431655763i64) + false)) diff --git a/examples/if.slo b/examples/if.slo new file mode 100644 index 0000000..b94c047 --- /dev/null +++ b/examples/if.slo @@ -0,0 +1,20 @@ +(module main) + +(fn choose ((value i32)) -> i32 + (if (< value 3) + 10 + 20)) + +(test "if chooses then" + (= (choose 2) 10)) + +(test "if chooses else" + (= (choose 4) 20)) + +(test "if returns bool" + (if (< 1 2) + true + false)) + +(fn main () -> i32 + (choose 2)) diff --git a/examples/integer-bitwise.slo b/examples/integer-bitwise.slo new file mode 100644 index 0000000..30b8022 --- /dev/null +++ b/examples/integer-bitwise.slo @@ -0,0 +1,58 @@ +(module main) + +(fn i32_and () -> i32 + (bit_and 6 3)) + +(fn i32_or () -> i32 + (bit_or 4 2)) + +(fn i32_xor () -> i32 + (bit_xor 7 3)) + +(fn i64_and () -> i64 + (bit_and 6i64 3i64)) + +(fn i64_or () -> i64 + (bit_or 4i64 2i64)) + +(fn i64_xor () -> i64 + (bit_xor 7i64 3i64)) + +(fn bitwise_ok () -> bool + (if (= (i32_and) 2) + (if (= (i32_or) 6) + (if (= (i32_xor) 4) + (if (= (i64_and) 2i64) + (if (= (i64_or) 6i64) + (= (i64_xor) 4i64) + false) + false) + false) + false) + false)) + +(fn main () -> i32 + (if (bitwise_ok) + 42 + 1)) + +(test "i32 bit and" + (= (i32_and) 2)) + +(test "i32 bit or" + (= (i32_or) 6)) + +(test "i32 bit xor" + (= (i32_xor) 4)) + +(test "i64 bit and" + (= (i64_and) 2i64)) + +(test "i64 bit or" + (= (i64_or) 6i64)) + +(test "i64 bit xor" + (= (i64_xor) 4i64)) + +(test "integer bitwise summary" + (bitwise_ok)) diff --git a/examples/integer-remainder.slo b/examples/integer-remainder.slo new file mode 100644 index 0000000..94615f0 --- /dev/null +++ b/examples/integer-remainder.slo @@ -0,0 +1,42 @@ +(module main) + +(fn i32_remainder () -> i32 + (% 17 5)) + +(fn i32_signed_remainder () -> i32 + (% -17 5)) + +(fn i64_remainder () -> i64 + (% 42i64 5i64)) + +(fn i64_signed_remainder () -> i64 + (% -17i64 5i64)) + +(fn remainder_ok () -> bool + (if (= (i32_remainder) 2) + (if (= (i32_signed_remainder) -2) + (if (= (i64_remainder) 2i64) + (= (i64_signed_remainder) -2i64) + false) + false) + false)) + +(fn main () -> i32 + (if (remainder_ok) + 42 + 1)) + +(test "i32 remainder" + (= (i32_remainder) 2)) + +(test "i32 signed remainder" + (= (i32_signed_remainder) -2)) + +(test "i64 remainder" + (= (i64_remainder) 2i64)) + +(test "i64 signed remainder" + (= (i64_signed_remainder) -2i64)) + +(test "integer remainder summary" + (remainder_ok)) diff --git a/examples/integer-to-string.slo b/examples/integer-to-string.slo new file mode 100644 index 0000000..e73bd4d --- /dev/null +++ b/examples/integer-to-string.slo @@ -0,0 +1,54 @@ +(module main) + +(fn i32_zero_text () -> string + (std.num.i32_to_string 0)) + +(fn i32_negative_text () -> string + (std.num.i32_to_string -7)) + +(fn i32_high_text () -> string + (std.num.i32_to_string 2147483647)) + +(fn i64_low_text () -> string + (std.num.i64_to_string -9223372036854775808i64)) + +(fn i64_high_text () -> string + (std.num.i64_to_string 9223372036854775807i64)) + +(fn i64_beyond_i32_text () -> string + (std.num.i64_to_string 2147483648i64)) + +(test "i32 zero to string" + (= (i32_zero_text) "0")) + +(test "i32 negative to string" + (= (i32_negative_text) "-7")) + +(test "i32 high to string" + (= (i32_high_text) "2147483647")) + +(test "i32 negative string length" + (= (std.string.len (i32_negative_text)) 2)) + +(test "i64 low to string" + (= (i64_low_text) "-9223372036854775808")) + +(test "i64 high to string" + (= (i64_high_text) "9223372036854775807")) + +(test "i64 beyond i32 to string" + (= (i64_beyond_i32_text) "2147483648")) + +(test "i64 low string length" + (= (std.string.len (i64_low_text)) 20)) + +(fn main () -> i32 + (std.io.print_string (i32_zero_text)) + (std.io.print_string (i32_negative_text)) + (std.io.print_string (i32_high_text)) + (std.io.print_string (i64_low_text)) + (std.io.print_string (i64_high_text)) + (std.io.print_string (i64_beyond_i32_text)) + (if (= (std.string.len (i64_high_text)) 19) + 0 + 1)) diff --git a/examples/local-variables.slo b/examples/local-variables.slo new file mode 100644 index 0000000..441eab9 --- /dev/null +++ b/examples/local-variables.slo @@ -0,0 +1,65 @@ +(module main) + +(fn add_local ((a i32)) -> i32 + (let one i32 1) + (var total i32 (+ a one)) + (set total (+ total 1)) + total) + +(fn keep_flag ((flag bool)) -> bool + (let local_flag bool flag) + local_flag) + +(fn flip_flag ((flag bool)) -> bool + (var current bool flag) + (set current (if current + false + true)) + current) + +(fn add_wide_local ((base i64)) -> i64 + (var count i64 base) + (set count (+ count 2i64)) + count) + +(fn add_ratio_local ((base f64)) -> f64 + (var amount f64 base) + (set amount (+ amount 0.5)) + amount) + +(test "locals work" + (let base i32 2) + (var value i32 (add_local base)) + (set value (+ value 1)) + (= value 5)) + +(test "bool let locals work" + (let expected bool true) + (let actual bool (keep_flag expected)) + actual) + +(test "bool let locals preserve false" + (if (keep_flag false) + false + true)) + +(test "bool var set flips true" + (if (flip_flag true) + false + true)) + +(test "bool var set flips false" + (flip_flag false)) + +(test "i64 var set works" + (= (add_wide_local 40i64) 42i64)) + +(test "f64 var set works" + (= (add_ratio_local 41.5) 42.0)) + +(fn main () -> i32 + (var flag bool false) + (set flag (flip_flag flag)) + (if flag + (add_local 2) + 1)) diff --git a/examples/numeric-struct-fields.slo b/examples/numeric-struct-fields.slo new file mode 100644 index 0000000..6034d90 --- /dev/null +++ b/examples/numeric-struct-fields.slo @@ -0,0 +1,82 @@ +(module main) + +(struct NumericRecord + (wide i64) + (ratio f64)) + +(fn make_record ((wide i64) (ratio f64)) -> NumericRecord + (NumericRecord (wide wide) (ratio ratio))) + +(fn literal_record () -> NumericRecord + (NumericRecord (wide 2147483648i64) (ratio 3.5))) + +(fn echo_record ((record NumericRecord)) -> NumericRecord + record) + +(fn local_record ((wide i64) (ratio f64)) -> NumericRecord + (let record NumericRecord (make_record wide ratio)) + (echo_record record)) + +(fn record_wide ((record NumericRecord)) -> i64 + (. record wide)) + +(fn record_ratio ((record NumericRecord)) -> f64 + (. record ratio)) + +(fn wide_total ((record NumericRecord)) -> i64 + (+ (. record wide) 10i64)) + +(fn ratio_total ((record NumericRecord)) -> f64 + (+ (. record ratio) 1.5)) + +(fn wide_in_range ((record NumericRecord)) -> bool + (if (> (. record wide) 2147483640i64) + (< (. record wide) 2147483660i64) + false)) + +(fn ratio_in_range ((record NumericRecord)) -> bool + (if (> (. record ratio) 3.0) + (< (. record ratio) 4.0) + false)) + +(fn wide_text ((record NumericRecord)) -> string + (std.num.i64_to_string (. record wide))) + +(fn ratio_text ((record NumericRecord)) -> string + (std.num.f64_to_string (. record ratio))) + +(test "numeric struct i64 field access" + (= (record_wide (literal_record)) 2147483648i64)) + +(test "numeric struct f64 field access" + (= (record_ratio (literal_record)) 3.5)) + +(test "numeric struct i64 arithmetic after access" + (= (wide_total (literal_record)) 2147483658i64)) + +(test "numeric struct f64 arithmetic after access" + (= (ratio_total (literal_record)) 5.0)) + +(test "numeric struct i64 comparison after access" + (wide_in_range (literal_record))) + +(test "numeric struct f64 comparison after access" + (ratio_in_range (literal_record))) + +(test "numeric struct local param return call flow" + (= (wide_total (local_record 2147483648i64 3.5)) 2147483658i64)) + +(test "numeric struct i64 format after access" + (= (wide_text (literal_record)) "2147483648")) + +(test "numeric struct f64 format after access" + (= (ratio_text (literal_record)) "3.5")) + +(fn main () -> i32 + (std.io.print_i64 (record_wide (literal_record))) + (std.io.print_f64 (record_ratio (literal_record))) + (if (wide_in_range (local_record 2147483648i64 3.5)) + (if (ratio_in_range (local_record 2147483648i64 3.5)) + 0 + 1) + 1)) diff --git a/examples/numeric-widening-conversions.slo b/examples/numeric-widening-conversions.slo new file mode 100644 index 0000000..8efc10c --- /dev/null +++ b/examples/numeric-widening-conversions.slo @@ -0,0 +1,32 @@ +(module main) + +(fn base_i64 () -> i64 + (std.num.i32_to_i64 2147483647)) + +(fn widened_i64 () -> i64 + (+ (base_i64) 1i64)) + +(fn half_step () -> f64 + (+ (std.num.i32_to_f64 5) 0.5)) + +(fn wide_as_f64 () -> f64 + (std.num.i64_to_f64 (widened_i64))) + +(fn main () -> i32 + (std.io.print_i64 (widened_i64)) + (std.io.print_f64 (wide_as_f64)) + (if (= (widened_i64) 2147483648i64) + 0 + 1)) + +(test "i32 widens to i64" + (= (std.num.i32_to_i64 42) 42i64)) + +(test "i32 to i64 feeds i64 arithmetic" + (= (widened_i64) 2147483648i64)) + +(test "i32 widens to f64" + (= (std.num.i32_to_f64 42) 42.0)) + +(test "i64 widens to f64" + (= (wide_as_f64) 2147483648.0)) diff --git a/examples/option-result-flow.slo b/examples/option-result-flow.slo new file mode 100644 index 0000000..14b9ba0 --- /dev/null +++ b/examples/option-result-flow.slo @@ -0,0 +1,160 @@ +(module main) + +(fn maybe_value ((value i32)) -> (option i32) + (some i32 value)) + +(fn maybe_empty () -> (option i32) + (none i32)) + +(fn maybe_wide_value ((value i64)) -> (option i64) + (some i64 value)) + +(fn maybe_wide_empty () -> (option i64) + (none i64)) + +(fn maybe_float_value ((value f64)) -> (option f64) + (some f64 value)) + +(fn maybe_float_empty () -> (option f64) + (none f64)) + +(fn maybe_flag_value ((value bool)) -> (option bool) + (some bool value)) + +(fn maybe_flag_empty () -> (option bool) + (none bool)) + +(fn maybe_string_value ((value string)) -> (option string) + (some string value)) + +(fn maybe_string_empty () -> (option string) + (none string)) + +(fn option_score ((value (option i32))) -> i32 + (if (is_some value) + 1 + 0)) + +(fn option_empty_score ((value (option i32))) -> i32 + (if (is_none value) + 1 + 0)) + +(fn option_wide_score ((value (option i64))) -> i32 + (if (is_some value) + 1 + 0)) + +(fn option_wide_empty_score ((value (option i64))) -> i32 + (if (is_none value) + 1 + 0)) + +(fn option_float_score ((value (option f64))) -> i32 + (if (is_some value) + 1 + 0)) + +(fn option_float_empty_score ((value (option f64))) -> i32 + (if (is_none value) + 1 + 0)) + +(fn option_flag_score ((value (option bool))) -> i32 + (if (is_some value) + 1 + 0)) + +(fn option_flag_empty_score ((value (option bool))) -> i32 + (if (is_none value) + 1 + 0)) + +(fn option_string_score ((value (option string))) -> i32 + (if (is_some value) + 1 + 0)) + +(fn option_string_empty_score ((value (option string))) -> i32 + (if (is_none value) + 1 + 0)) + +(fn result_ok_value ((value i32)) -> (result i32 i32) + (ok i32 i32 value)) + +(fn result_err_value ((code i32)) -> (result i32 i32) + (err i32 i32 code)) + +(fn result_success_score ((value (result i32 i32))) -> i32 + (if (is_ok value) + 1 + 0)) + +(fn result_failure_score ((value (result i32 i32))) -> i32 + (if (is_err value) + 1 + 0)) + +(fn option_local_flow () -> i32 + (let value (option i32) (maybe_value 42)) + (option_score value)) + +(fn option_wide_local_flow () -> i32 + (let value (option i64) (maybe_wide_value 2147483648i64)) + (option_wide_score value)) + +(fn option_float_local_flow () -> i32 + (let value (option f64) (maybe_float_value 42.5)) + (option_float_score value)) + +(fn option_flag_local_flow () -> i32 + (let value (option bool) (maybe_flag_value true)) + (option_flag_score value)) + +(fn option_string_local_flow () -> i32 + (let value (option string) (maybe_string_value "slovo")) + (option_string_score value)) + +(fn result_local_flow () -> i32 + (let value (result i32 i32) (result_err_value 7)) + (result_failure_score value)) + +(test "option local value flow" + (= (option_local_flow) 1)) + +(test "option call observation" + (= (option_empty_score (maybe_empty)) 1)) + +(test "option i64 local value flow" + (= (option_wide_local_flow) 1)) + +(test "option i64 call observation" + (= (option_wide_empty_score (maybe_wide_empty)) 1)) + +(test "option f64 local value flow" + (= (option_float_local_flow) 1)) + +(test "option f64 call observation" + (= (option_float_empty_score (maybe_float_empty)) 1)) + +(test "option bool local value flow" + (= (option_flag_local_flow) 1)) + +(test "option bool call observation" + (= (option_flag_empty_score (maybe_flag_empty)) 1)) + +(test "option string local value flow" + (= (option_string_local_flow) 1)) + +(test "option string call observation" + (= (option_string_empty_score (maybe_string_empty)) 1)) + +(test "result call observation" + (= (result_success_score (result_ok_value 42)) 1)) + +(test "result local value flow" + (= (result_local_flow) 1)) + +(fn main () -> i32 + 0) diff --git a/examples/option-result-match.slo b/examples/option-result-match.slo new file mode 100644 index 0000000..827d6a3 --- /dev/null +++ b/examples/option-result-match.slo @@ -0,0 +1,185 @@ +(module main) + +(fn maybe_value ((value i32)) -> (option i32) + (some i32 value)) + +(fn maybe_empty () -> (option i32) + (none i32)) + +(fn maybe_wide_value ((value i64)) -> (option i64) + (some i64 value)) + +(fn maybe_wide_empty () -> (option i64) + (none i64)) + +(fn maybe_float_value ((value f64)) -> (option f64) + (some f64 value)) + +(fn maybe_float_empty () -> (option f64) + (none f64)) + +(fn maybe_flag_value ((value bool)) -> (option bool) + (some bool value)) + +(fn maybe_flag_empty () -> (option bool) + (none bool)) + +(fn maybe_string_value ((value string)) -> (option string) + (some string value)) + +(fn maybe_string_empty () -> (option string) + (none string)) + +(fn result_ok_value ((value i32)) -> (result i32 i32) + (ok i32 i32 value)) + +(fn result_err_value ((code i32)) -> (result i32 i32) + (err i32 i32 code)) + +(fn option_value_or ((value (option i32)) (fallback i32)) -> i32 + (match value + ((some payload) + payload) + ((none) + fallback))) + +(fn option_bump_or_zero ((value (option i32))) -> i32 + (match value + ((some payload) + (let bumped i32 (+ payload 1)) + bumped) + ((none) + 0))) + +(fn option_wide_value_or ((value (option i64)) (fallback i64)) -> i64 + (match value + ((some payload) + payload) + ((none) + fallback))) + +(fn option_wide_bump_or_zero ((value (option i64))) -> i64 + (match value + ((some payload) + (let bumped i64 (+ payload 1i64)) + bumped) + ((none) + 0i64))) + +(fn option_float_value_or ((value (option f64)) (fallback f64)) -> f64 + (match value + ((some payload) + payload) + ((none) + fallback))) + +(fn option_float_bump_or_zero ((value (option f64))) -> f64 + (match value + ((some payload) + (let bumped f64 (+ payload 1.0)) + bumped) + ((none) + 0.0))) + +(fn option_flag_value_or ((value (option bool)) (fallback bool)) -> bool + (match value + ((some payload) + payload) + ((none) + fallback))) + +(fn option_flag_selected_or ((value (option bool)) (fallback bool)) -> bool + (match value + ((some payload) + (if payload + true + false) + payload) + ((none) + fallback))) + +(fn option_string_value_or ((value (option string)) (fallback string)) -> string + (match value + ((some payload) + payload) + ((none) + fallback))) + +(fn option_string_selected_or ((value (option string)) (fallback string)) -> string + (match value + ((some payload) + (let chosen string payload) + chosen) + ((none) + fallback))) + +(fn result_value_or_code ((value (result i32 i32))) -> i32 + (match value + ((ok payload) + payload) + ((err code) + code))) + +(fn result_score ((value (result i32 i32))) -> i32 + (match value + ((ok payload) + (+ payload 10)) + ((err code) + code))) + +(test "option match some payload" + (= (option_value_or (maybe_value 42) 0) 42)) + +(test "option match none fallback" + (= (option_value_or (maybe_empty) 7) 7)) + +(test "option match multi expression arm" + (= (option_bump_or_zero (maybe_value 8)) 9)) + +(test "option i64 match some payload" + (= (option_wide_value_or (maybe_wide_value 2147483648i64) 0i64) 2147483648i64)) + +(test "option i64 match none fallback" + (= (option_wide_value_or (maybe_wide_empty) 7i64) 7i64)) + +(test "option i64 match multi expression arm" + (= (option_wide_bump_or_zero (maybe_wide_value 8i64)) 9i64)) + +(test "option f64 match some payload" + (= (option_float_value_or (maybe_float_value 42.5) 0.0) 42.5)) + +(test "option f64 match none fallback" + (= (option_float_value_or (maybe_float_empty) 7.0) 7.0)) + +(test "option f64 match multi expression arm" + (= (option_float_bump_or_zero (maybe_float_value 8.5)) 9.5)) + +(test "option bool match some payload" + (option_flag_value_or (maybe_flag_value true) false)) + +(test "option bool match none fallback" + (option_flag_value_or (maybe_flag_empty) true)) + +(test "option bool match multi expression arm" + (option_flag_selected_or (maybe_flag_value true) false)) + +(test "option string match some payload" + (= (option_string_value_or (maybe_string_value "slovo") "fallback") "slovo")) + +(test "option string match none fallback" + (= (option_string_value_or (maybe_string_empty) "fallback") "fallback")) + +(test "option string match multi expression arm" + (= (option_string_selected_or (maybe_string_value "oak") "fallback") "oak")) + +(test "result match ok payload" + (= (result_value_or_code (result_ok_value 30)) 30)) + +(test "result match err payload" + (= (result_value_or_code (result_err_value 5)) 5)) + +(test "result match computed arm" + (= (result_score (result_ok_value 2)) 12)) + +(fn main () -> i32 + 0) diff --git a/examples/option-result-payload.slo b/examples/option-result-payload.slo new file mode 100644 index 0000000..80af456 --- /dev/null +++ b/examples/option-result-payload.slo @@ -0,0 +1,259 @@ +(module main) + +(fn maybe_value ((value i32)) -> (option i32) + (some i32 value)) + +(fn maybe_empty () -> (option i32) + (none i32)) + +(fn maybe_wide_value ((value i64)) -> (option i64) + (some i64 value)) + +(fn maybe_wide_empty () -> (option i64) + (none i64)) + +(fn maybe_float_value ((value f64)) -> (option f64) + (some f64 value)) + +(fn maybe_float_empty () -> (option f64) + (none f64)) + +(fn maybe_flag_value ((value bool)) -> (option bool) + (some bool value)) + +(fn maybe_flag_empty () -> (option bool) + (none bool)) + +(fn maybe_string_value ((value string)) -> (option string) + (some string value)) + +(fn maybe_string_empty () -> (option string) + (none string)) + +(fn result_ok_value ((value i32)) -> (result i32 i32) + (ok i32 i32 value)) + +(fn result_err_value ((code i32)) -> (result i32 i32) + (err i32 i32 code)) + +(fn option_direct_payload () -> i32 + (unwrap_some (some i32 42))) + +(fn option_param_payload ((value (option i32))) -> i32 + (unwrap_some value)) + +(fn option_local_payload () -> i32 + (let value (option i32) (maybe_value 21)) + (unwrap_some value)) + +(fn option_call_payload () -> i32 + (unwrap_some (maybe_value 22))) + +(fn option_guarded_payload ((value (option i32))) -> i32 + (if (is_some value) + (unwrap_some value) + 0)) + +(fn option_wide_direct_payload () -> i64 + (unwrap_some (some i64 2147483648i64))) + +(fn option_wide_param_payload ((value (option i64))) -> i64 + (unwrap_some value)) + +(fn option_wide_local_payload () -> i64 + (let value (option i64) (maybe_wide_value 21i64)) + (unwrap_some value)) + +(fn option_wide_call_payload () -> i64 + (unwrap_some (maybe_wide_value 22i64))) + +(fn option_wide_guarded_payload ((value (option i64))) -> i64 + (if (is_some value) + (unwrap_some value) + 0i64)) + +(fn option_float_direct_payload () -> f64 + (unwrap_some (some f64 42.5))) + +(fn option_float_param_payload ((value (option f64))) -> f64 + (unwrap_some value)) + +(fn option_float_local_payload () -> f64 + (let value (option f64) (maybe_float_value 21.5)) + (unwrap_some value)) + +(fn option_float_call_payload () -> f64 + (unwrap_some (maybe_float_value 22.5))) + +(fn option_float_guarded_payload ((value (option f64))) -> f64 + (if (is_some value) + (unwrap_some value) + 0.0)) + +(fn option_flag_direct_payload () -> bool + (unwrap_some (some bool true))) + +(fn option_flag_param_payload ((value (option bool))) -> bool + (unwrap_some value)) + +(fn option_flag_local_payload () -> bool + (let value (option bool) (maybe_flag_value true)) + (unwrap_some value)) + +(fn option_flag_call_payload () -> bool + (unwrap_some (maybe_flag_value true))) + +(fn option_flag_guarded_payload ((value (option bool))) -> bool + (if (is_some value) + (unwrap_some value) + false)) + +(fn option_string_direct_payload () -> string + (unwrap_some (some string "slovo"))) + +(fn option_string_param_payload ((value (option string))) -> string + (unwrap_some value)) + +(fn option_string_local_payload () -> string + (let value (option string) (maybe_string_value "oak")) + (unwrap_some value)) + +(fn option_string_call_payload () -> string + (unwrap_some (maybe_string_value "pine"))) + +(fn option_string_guarded_payload ((value (option string))) -> string + (if (is_some value) + (unwrap_some value) + "fallback")) + +(fn result_ok_direct_payload () -> i32 + (unwrap_ok (ok i32 i32 30))) + +(fn result_err_direct_payload () -> i32 + (unwrap_err (err i32 i32 7))) + +(fn result_ok_param_payload ((value (result i32 i32))) -> i32 + (unwrap_ok value)) + +(fn result_err_param_payload ((value (result i32 i32))) -> i32 + (unwrap_err value)) + +(fn result_ok_local_payload () -> i32 + (let value (result i32 i32) (result_ok_value 31)) + (unwrap_ok value)) + +(fn result_err_call_payload () -> i32 + (unwrap_err (result_err_value 9))) + +(test "unwrap some direct constructor" + (= (option_direct_payload) 42)) + +(test "unwrap some local" + (= (option_local_payload) 21)) + +(test "unwrap some call" + (= (option_call_payload) 22)) + +(test "unwrap some parameter" + (= (option_param_payload (maybe_value 23)) 23)) + +(test "guarded unwrap some present" + (= (option_guarded_payload (maybe_value 24)) 24)) + +(test "guarded unwrap some absent" + (= (option_guarded_payload (maybe_empty)) 0)) + +(test "unwrap some i64 direct constructor" + (= (option_wide_direct_payload) 2147483648i64)) + +(test "unwrap some i64 local" + (= (option_wide_local_payload) 21i64)) + +(test "unwrap some i64 call" + (= (option_wide_call_payload) 22i64)) + +(test "unwrap some i64 parameter" + (= (option_wide_param_payload (maybe_wide_value 23i64)) 23i64)) + +(test "guarded unwrap some i64 present" + (= (option_wide_guarded_payload (maybe_wide_value 24i64)) 24i64)) + +(test "guarded unwrap some i64 absent" + (= (option_wide_guarded_payload (maybe_wide_empty)) 0i64)) + +(test "unwrap some f64 direct constructor" + (= (option_float_direct_payload) 42.5)) + +(test "unwrap some f64 local" + (= (option_float_local_payload) 21.5)) + +(test "unwrap some f64 call" + (= (option_float_call_payload) 22.5)) + +(test "unwrap some f64 parameter" + (= (option_float_param_payload (maybe_float_value 23.5)) 23.5)) + +(test "guarded unwrap some f64 present" + (= (option_float_guarded_payload (maybe_float_value 24.5)) 24.5)) + +(test "guarded unwrap some f64 absent" + (= (option_float_guarded_payload (maybe_float_empty)) 0.0)) + +(test "unwrap some bool direct constructor" + (option_flag_direct_payload)) + +(test "unwrap some bool local" + (option_flag_local_payload)) + +(test "unwrap some bool call" + (option_flag_call_payload)) + +(test "unwrap some bool parameter" + (option_flag_param_payload (maybe_flag_value true))) + +(test "guarded unwrap some bool present" + (option_flag_guarded_payload (maybe_flag_value true))) + +(test "guarded unwrap some bool absent" + (if (option_flag_guarded_payload (maybe_flag_empty)) + false + true)) + +(test "unwrap some string direct constructor" + (= (option_string_direct_payload) "slovo")) + +(test "unwrap some string local" + (= (option_string_local_payload) "oak")) + +(test "unwrap some string call" + (= (option_string_call_payload) "pine")) + +(test "unwrap some string parameter" + (= (option_string_param_payload (maybe_string_value "birch")) "birch")) + +(test "guarded unwrap some string present" + (= (option_string_guarded_payload (maybe_string_value "cedar")) "cedar")) + +(test "guarded unwrap some string absent" + (= (option_string_guarded_payload (maybe_string_empty)) "fallback")) + +(test "unwrap ok direct constructor" + (= (result_ok_direct_payload) 30)) + +(test "unwrap err direct constructor" + (= (result_err_direct_payload) 7)) + +(test "unwrap ok parameter" + (= (result_ok_param_payload (result_ok_value 32)) 32)) + +(test "unwrap err parameter" + (= (result_err_param_payload (result_err_value 8)) 8)) + +(test "unwrap ok local" + (= (result_ok_local_payload) 31)) + +(test "unwrap err call" + (= (result_err_call_payload) 9)) + +(fn main () -> i32 + 0) diff --git a/examples/option-result.slo b/examples/option-result.slo new file mode 100644 index 0000000..d54a5fe --- /dev/null +++ b/examples/option-result.slo @@ -0,0 +1,40 @@ +(module main) + +(fn maybe_value () -> (option i32) + (some i32 42)) + +(fn maybe_empty () -> (option i32) + (none i32)) + +(fn maybe_wide_value () -> (option i64) + (some i64 2147483648i64)) + +(fn maybe_wide_empty () -> (option i64) + (none i64)) + +(fn maybe_float_value () -> (option f64) + (some f64 42.5)) + +(fn maybe_float_empty () -> (option f64) + (none f64)) + +(fn maybe_flag_value () -> (option bool) + (some bool true)) + +(fn maybe_flag_empty () -> (option bool) + (none bool)) + +(fn maybe_string_value () -> (option string) + (some string "slovo")) + +(fn maybe_string_empty () -> (option string) + (none string)) + +(fn result_ok () -> (result i32 i32) + (ok i32 i32 42)) + +(fn result_err () -> (result i32 i32) + (err i32 i32 7)) + +(fn main () -> i32 + 0) diff --git a/examples/owned-string-concat.slo b/examples/owned-string-concat.slo new file mode 100644 index 0000000..808c345 --- /dev/null +++ b/examples/owned-string-concat.slo @@ -0,0 +1,21 @@ +(module main) + +(fn join ((left string) (right string)) -> string + (std.string.concat left right)) + +(fn greeting () -> string + (std.string.concat "hello, " "slovo")) + +(fn greeting_len () -> i32 + (std.string.len (greeting))) + +(test "owned string concat equality" + (= (greeting) "hello, slovo")) + +(test "owned string concat length" + (= (greeting_len) 12)) + +(fn main () -> i32 + (std.io.print_string (join "hello, " "slovo")) + (std.io.print_i32 (std.string.len (join "ab" "cd"))) + 0) diff --git a/examples/primitive-struct-fields.slo b/examples/primitive-struct-fields.slo new file mode 100644 index 0000000..5083c6b --- /dev/null +++ b/examples/primitive-struct-fields.slo @@ -0,0 +1,66 @@ +(module main) + +(struct PrimitiveRecord + (id i32) + (active bool) + (label string)) + +(fn make_record ((id i32) (label string)) -> PrimitiveRecord + (PrimitiveRecord (id id) (active (= id 7)) (label label))) + +(fn expected_record () -> PrimitiveRecord + (make_record 7 "alpha")) + +(fn inactive_record () -> PrimitiveRecord + (PrimitiveRecord (id 3) (active false) (label "beta"))) + +(fn echo_record ((record PrimitiveRecord)) -> PrimitiveRecord + record) + +(fn local_record ((label string)) -> PrimitiveRecord + (let record PrimitiveRecord (make_record 7 label)) + (echo_record record)) + +(fn record_id ((record PrimitiveRecord)) -> i32 + (. record id)) + +(fn record_active ((record PrimitiveRecord)) -> bool + (. record active)) + +(fn record_label ((record PrimitiveRecord)) -> string + (. record label)) + +(fn label_matches ((record PrimitiveRecord) (label string)) -> bool + (= (. record label) label)) + +(fn active_score ((record PrimitiveRecord)) -> i32 + (if (. record active) + (. record id) + 0)) + +(fn label_len ((record PrimitiveRecord)) -> i32 + (std.string.len (. record label))) + +(test "primitive struct i32 field access" + (= (record_id (expected_record)) 7)) + +(test "primitive struct bool field predicate" + (record_active (expected_record))) + +(test "primitive struct bool field false branch" + (= (active_score (inactive_record)) 0)) + +(test "primitive struct string field equality" + (label_matches (expected_record) "alpha")) + +(test "primitive struct string field length" + (= (label_len (expected_record)) 5)) + +(test "primitive struct local param return call flow" + (= (active_score (local_record "alpha")) 7)) + +(test "primitive struct string field access return" + (= (record_label (local_record "alpha")) "alpha")) + +(fn main () -> i32 + (active_score (local_record "alpha"))) diff --git a/examples/print-bool.slo b/examples/print-bool.slo new file mode 100644 index 0000000..04d8439 --- /dev/null +++ b/examples/print-bool.slo @@ -0,0 +1,7 @@ +(module main) + +(fn main () -> i32 + (print_bool true) + (print_bool false) + (print_bool (= "slovo" "slovo")) + 0) diff --git a/examples/projects/basic/slovo.toml b/examples/projects/basic/slovo.toml new file mode 100644 index 0000000..9b5fe3c --- /dev/null +++ b/examples/projects/basic/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "basic" +source_root = "src" +entry = "main" diff --git a/examples/projects/basic/src/main.slo b/examples/projects/basic/src/main.slo new file mode 100644 index 0000000..836d927 --- /dev/null +++ b/examples/projects/basic/src/main.slo @@ -0,0 +1,10 @@ +(module main) + +(import math (add_one)) + +(fn main () -> i32 + (print_i32 (add_one 41)) + 0) + +(test "imported add one" + (= (add_one 41) 42)) diff --git a/examples/projects/basic/src/math.slo b/examples/projects/basic/src/math.slo new file mode 100644 index 0000000..1693dd7 --- /dev/null +++ b/examples/projects/basic/src/math.slo @@ -0,0 +1,7 @@ +(module math (export add_one)) + +(fn add_one ((value i32)) -> i32 + (+ value 1)) + +(test "add one" + (= (add_one 41) 42)) diff --git a/examples/projects/enum-imports/slovo.toml b/examples/projects/enum-imports/slovo.toml new file mode 100644 index 0000000..38ceca9 --- /dev/null +++ b/examples/projects/enum-imports/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "enum-imports" +source_root = "src" +entry = "main" diff --git a/examples/projects/enum-imports/src/main.slo b/examples/projects/enum-imports/src/main.slo new file mode 100644 index 0000000..24ff863 --- /dev/null +++ b/examples/projects/enum-imports/src/main.slo @@ -0,0 +1,60 @@ +(module main) + +(import readings (Reading)) + +(fn missing () -> Reading + (Reading.Missing)) + +(fn value ((payload i32)) -> Reading + (Reading.Value payload)) + +(fn echo ((reading Reading)) -> Reading + reading) + +(fn local_reading ((payload i32)) -> Reading + (let reading Reading (Reading.Value payload)) + reading) + +(fn same_reading ((left Reading) (right Reading)) -> bool + (= left right)) + +(fn reading_code ((reading Reading)) -> i32 + (match reading + ((Reading.Missing) + 0) + ((Reading.Value payload) + payload) + ((Reading.Offset payload) + (+ payload 100)))) + +(fn call_flow ((payload i32)) -> Reading + (echo (local_reading payload))) + +(test "imported enum payload constructor equality" + (= (Reading.Value 7) (value 7))) + +(test "imported enum payload equality compares payload" + (if (= (Reading.Value 7) (Reading.Value 8)) + false + true)) + +(test "imported enum payloadless equality" + (= (Reading.Missing) (missing))) + +(test "imported enum local return call flow" + (= (call_flow 9) (Reading.Value 9))) + +(test "imported enum parameter equality" + (same_reading (value 11) (Reading.Value 11))) + +(test "imported enum match missing" + (= (reading_code (Reading.Missing)) 0)) + +(test "imported enum match value" + (= (reading_code (Reading.Value 12)) 12)) + +(test "imported enum match offset" + (= (reading_code (Reading.Offset 5)) 105)) + +(fn main () -> i32 + (reading_code (call_flow 42))) diff --git a/examples/projects/enum-imports/src/readings.slo b/examples/projects/enum-imports/src/readings.slo new file mode 100644 index 0000000..50b9ae9 --- /dev/null +++ b/examples/projects/enum-imports/src/readings.slo @@ -0,0 +1,9 @@ +(module readings (export Reading)) + +(enum Reading + Missing + (Value i32) + (Offset i32)) + +(test "local exported enum constructor" + (= (Reading.Missing) (Reading.Missing))) diff --git a/examples/projects/std-import-cli/slovo.toml b/examples/projects/std-import-cli/slovo.toml new file mode 100644 index 0000000..30f83aa --- /dev/null +++ b/examples/projects/std-import-cli/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-import-cli" +source_root = "src" +entry = "main" diff --git a/examples/projects/std-import-cli/src/main.slo b/examples/projects/std-import-cli/src/main.slo new file mode 100644 index 0000000..db2bb31 --- /dev/null +++ b/examples/projects/std-import-cli/src/main.slo @@ -0,0 +1,278 @@ +(module main) + +(import std.cli (arg_text_result arg_text_option arg_i32_result arg_i32_option arg_i32_or_zero arg_i32_or arg_u32_result arg_u32_option arg_u32_or_zero arg_u32_or arg_i64_result arg_i64_option arg_i64_or_zero arg_i64_or arg_u64_result arg_u64_option arg_u64_or_zero arg_u64_or arg_f64_result arg_f64_option arg_f64_or_zero arg_f64_or arg_bool_result arg_bool_option arg_bool_or_false arg_bool_or)) + +(fn impossible_index () -> i32 + 99999) + +(fn first_index () -> i32 + 0) + +(fn imported_cli_arg_text_missing () -> bool + (match (arg_text_result (impossible_index)) + ((ok text) + false) + ((err code) + (= code 1)))) + +(fn imported_cli_arg_text_option_ok () -> bool + (if (match (arg_text_option (first_index)) + ((some text) + true) + ((none) + false)) + (match (arg_text_option (impossible_index)) + ((some text) + false) + ((none) + true)) + false)) + +(fn imported_cli_arg_i32_missing () -> bool + (match (arg_i32_result (impossible_index)) + ((ok value) + false) + ((err code) + (= code 1)))) + +(fn imported_cli_arg_i32_or_zero_missing () -> bool + (= (arg_i32_or_zero (impossible_index)) 0)) + +(fn imported_cli_arg_u32_missing () -> bool + (match (arg_u32_result (impossible_index)) + ((ok value) + false) + ((err code) + (= code 1)))) + +(fn imported_cli_arg_u32_or_zero_missing () -> bool + (= (arg_u32_or_zero (impossible_index)) 0u32)) + +(fn imported_cli_arg_i64_missing () -> bool + (match (arg_i64_result (impossible_index)) + ((ok value) + false) + ((err code) + (= code 1)))) + +(fn imported_cli_arg_i64_or_zero_missing () -> bool + (= (arg_i64_or_zero (impossible_index)) 0i64)) + +(fn imported_cli_arg_u64_missing () -> bool + (match (arg_u64_result (impossible_index)) + ((ok value) + false) + ((err code) + (= code 1)))) + +(fn imported_cli_arg_u64_or_zero_missing () -> bool + (= (arg_u64_or_zero (impossible_index)) 0u64)) + +(fn imported_cli_arg_f64_missing () -> bool + (match (arg_f64_result (impossible_index)) + ((ok value) + false) + ((err code) + (= code 1)))) + +(fn imported_cli_arg_f64_or_zero_missing () -> bool + (= (arg_f64_or_zero (impossible_index)) 0.0)) + +(fn imported_cli_arg_bool_missing () -> bool + (match (arg_bool_result (impossible_index)) + ((ok value) + false) + ((err code) + (= code 1)))) + +(fn imported_cli_arg_bool_or_false_missing () -> bool + (if (arg_bool_or_false (impossible_index)) + false + true)) + +(fn imported_cli_typed_options_ok () -> bool + (if (match (arg_i32_option (impossible_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_i32_option (first_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_u32_option (impossible_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_u32_option (first_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_i64_option (impossible_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_i64_option (first_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_u64_option (impossible_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_u64_option (first_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_f64_option (impossible_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_f64_option (first_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_bool_option (impossible_index)) + ((some value) + false) + ((none) + true)) + (match (arg_bool_option (first_index)) + ((some value) + false) + ((none) + true)) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_cli_typed_custom_fallbacks_ok () -> bool + (if (= (arg_i32_or (impossible_index) 7) 7) + (if (= (arg_i32_or (first_index) 7) 7) + (if (= (arg_u32_or (impossible_index) 7u32) 7u32) + (if (= (arg_u32_or (first_index) 7u32) 7u32) + (if (= (arg_i64_or (impossible_index) 9i64) 9i64) + (if (= (arg_i64_or (first_index) 9i64) 9i64) + (if (= (arg_u64_or (impossible_index) 9u64) 9u64) + (if (= (arg_u64_or (first_index) 9u64) 9u64) + (if (= (arg_f64_or (impossible_index) 1.5) 1.5) + (if (= (arg_f64_or (first_index) 1.5) 1.5) + (if (arg_bool_or (impossible_index) true) + (arg_bool_or (first_index) true) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_cli_facade_ok () -> bool + (if (imported_cli_arg_text_missing) + (if (imported_cli_arg_text_option_ok) + (if (imported_cli_arg_i32_missing) + (if (imported_cli_arg_i32_or_zero_missing) + (if (imported_cli_arg_u32_missing) + (if (imported_cli_arg_u32_or_zero_missing) + (if (imported_cli_arg_i64_missing) + (if (imported_cli_arg_i64_or_zero_missing) + (if (imported_cli_arg_u64_missing) + (if (imported_cli_arg_u64_or_zero_missing) + (if (imported_cli_arg_f64_missing) + (if (imported_cli_arg_f64_or_zero_missing) + (if (imported_cli_arg_bool_missing) + (if (imported_cli_arg_bool_or_false_missing) + (if (imported_cli_typed_options_ok) + (imported_cli_typed_custom_fallbacks_ok) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn main () -> i32 + (if (imported_cli_facade_ok) + 42 + 1)) + +(test "explicit std cli arg text missing" + (imported_cli_arg_text_missing)) + +(test "explicit std cli arg text option" + (imported_cli_arg_text_option_ok)) + +(test "explicit std cli arg i32 missing" + (imported_cli_arg_i32_missing)) + +(test "explicit std cli arg i32 fallback missing" + (imported_cli_arg_i32_or_zero_missing)) + +(test "explicit std cli arg u32 missing" + (imported_cli_arg_u32_missing)) + +(test "explicit std cli arg u32 fallback missing" + (imported_cli_arg_u32_or_zero_missing)) + +(test "explicit std cli arg i64 missing" + (imported_cli_arg_i64_missing)) + +(test "explicit std cli arg i64 fallback missing" + (imported_cli_arg_i64_or_zero_missing)) + +(test "explicit std cli arg u64 missing" + (imported_cli_arg_u64_missing)) + +(test "explicit std cli arg u64 fallback missing" + (imported_cli_arg_u64_or_zero_missing)) + +(test "explicit std cli arg f64 missing" + (imported_cli_arg_f64_missing)) + +(test "explicit std cli arg f64 fallback missing" + (imported_cli_arg_f64_or_zero_missing)) + +(test "explicit std cli arg bool missing" + (imported_cli_arg_bool_missing)) + +(test "explicit std cli arg bool fallback missing" + (imported_cli_arg_bool_or_false_missing)) + +(test "explicit std cli typed option" + (imported_cli_typed_options_ok)) + +(test "explicit std cli typed custom fallback" + (imported_cli_typed_custom_fallbacks_ok)) + +(test "explicit std cli facade all" + (= (main) 42)) diff --git a/examples/projects/std-import-env/slovo.toml b/examples/projects/std-import-env/slovo.toml new file mode 100644 index 0000000..8072f45 --- /dev/null +++ b/examples/projects/std-import-env/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-import-env" +source_root = "src" +entry = "main" diff --git a/examples/projects/std-import-env/src/main.slo b/examples/projects/std-import-env/src/main.slo new file mode 100644 index 0000000..1da6918 --- /dev/null +++ b/examples/projects/std-import-env/src/main.slo @@ -0,0 +1,364 @@ +(module main) + +(import std.env (get get_result get_option has get_or get_i32_result get_i32_option get_i32_or_zero get_i32_or get_u32_result get_u32_option get_u32_or_zero get_u32_or get_i64_result get_i64_option get_i64_or_zero get_i64_or get_u64_result get_u64_option get_u64_or_zero get_u64_or get_f64_result get_f64_option get_f64_or_zero get_f64_or get_bool_result get_bool_option get_bool_or_false get_bool_or)) + +(fn missing_env_name () -> string + "GLAGOL_STD_IMPORT_ENV_ALPHA_UNLIKELY_MISSING") + +(fn present_env_name () -> string + "GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT") + +(fn present_env_value () -> string + "glagol-std-import-env-alpha-value") + +(fn present_i32_env_name () -> string + "GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT_I32") + +(fn present_i64_env_name () -> string + "GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT_I64") + +(fn present_u32_env_name () -> string + "GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT_U32") + +(fn present_u64_env_name () -> string + "GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT_U64") + +(fn present_f64_env_name () -> string + "GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT_F64") + +(fn present_bool_env_name () -> string + "GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT_BOOL") + +(fn invalid_env_name () -> string + "GLAGOL_STD_IMPORT_ENV_ALPHA_INVALID") + +(fn imported_env_missing_get_empty () -> bool + (= (get (missing_env_name)) "")) + +(fn imported_env_missing_result_err_one () -> bool + (match (get_result (missing_env_name)) + ((ok payload) + false) + ((err code) + (= code 1)))) + +(fn imported_env_has_missing_false () -> bool + (if (has (missing_env_name)) + false + true)) + +(fn imported_env_get_or_missing_fallback () -> bool + (= (get_or (missing_env_name) "fallback") "fallback")) + +(fn imported_env_get_option_missing_none () -> bool + (match (get_option (missing_env_name)) + ((some payload) + false) + ((none) + true))) + +(fn imported_env_has_present_true () -> bool + (has (present_env_name))) + +(fn imported_env_get_or_present_value () -> bool + (if (= (get_or (present_env_name) "fallback") (present_env_value)) + (= (get (present_env_name)) (present_env_value)) + false)) + +(fn imported_env_get_option_present_some () -> bool + (match (get_option (present_env_name)) + ((some payload) + (= payload (present_env_value))) + ((none) + false))) + +(fn imported_env_get_i32_result_present () -> bool + (match (get_i32_result (present_i32_env_name)) + ((ok value) + (= value 42)) + ((err code) + false))) + +(fn imported_env_get_i32_or_zero_invalid () -> bool + (= (get_i32_or_zero (invalid_env_name)) 0)) + +(fn imported_env_get_u32_result_present () -> bool + (match (get_u32_result (present_u32_env_name)) + ((ok value) + (= value 42u32)) + ((err code) + false))) + +(fn imported_env_get_u32_or_zero_invalid () -> bool + (= (get_u32_or_zero (invalid_env_name)) 0u32)) + +(fn imported_env_get_i64_result_present () -> bool + (match (get_i64_result (present_i64_env_name)) + ((ok value) + (= value 42000000000i64)) + ((err code) + false))) + +(fn imported_env_get_i64_or_zero_missing () -> bool + (= (get_i64_or_zero (missing_env_name)) 0i64)) + +(fn imported_env_get_u64_result_present () -> bool + (match (get_u64_result (present_u64_env_name)) + ((ok value) + (= value 4294967296u64)) + ((err code) + false))) + +(fn imported_env_get_u64_or_zero_missing () -> bool + (= (get_u64_or_zero (missing_env_name)) 0u64)) + +(fn imported_env_get_f64_result_present () -> bool + (match (get_f64_result (present_f64_env_name)) + ((ok value) + (= value 42.5)) + ((err code) + false))) + +(fn imported_env_get_f64_or_zero_invalid () -> bool + (= (get_f64_or_zero (invalid_env_name)) 0.0)) + +(fn imported_env_get_bool_result_present () -> bool + (match (get_bool_result (present_bool_env_name)) + ((ok value) + value) + ((err code) + false))) + +(fn imported_env_get_bool_or_false_invalid () -> bool + (if (get_bool_or_false (invalid_env_name)) + false + true)) + +(fn imported_env_typed_options_ok () -> bool + (let i32_present bool (match (get_i32_option (present_i32_env_name)) + ((some payload) + (= payload 42)) + ((none) + false))) + (let i32_invalid bool (match (get_i32_option (invalid_env_name)) + ((some payload) + false) + ((none) + true))) + (let u32_present bool (match (get_u32_option (present_u32_env_name)) + ((some payload) + (= payload 42u32)) + ((none) + false))) + (let u32_invalid bool (match (get_u32_option (invalid_env_name)) + ((some payload) + false) + ((none) + true))) + (let i64_present bool (match (get_i64_option (present_i64_env_name)) + ((some payload) + (= payload 42000000000i64)) + ((none) + false))) + (let i64_missing bool (match (get_i64_option (missing_env_name)) + ((some payload) + false) + ((none) + true))) + (let u64_present bool (match (get_u64_option (present_u64_env_name)) + ((some payload) + (= payload 4294967296u64)) + ((none) + false))) + (let u64_missing bool (match (get_u64_option (missing_env_name)) + ((some payload) + false) + ((none) + true))) + (let f64_present bool (match (get_f64_option (present_f64_env_name)) + ((some payload) + (= payload 42.5)) + ((none) + false))) + (let f64_invalid bool (match (get_f64_option (invalid_env_name)) + ((some payload) + false) + ((none) + true))) + (let bool_present bool (match (get_bool_option (present_bool_env_name)) + ((some payload) + payload) + ((none) + false))) + (let bool_invalid bool (match (get_bool_option (invalid_env_name)) + ((some payload) + false) + ((none) + true))) + (if i32_present + (if i32_invalid + (if u32_present + (if u32_invalid + (if i64_present + (if i64_missing + (if u64_present + (if u64_missing + (if f64_present + (if f64_invalid + (if bool_present + bool_invalid + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_env_typed_custom_fallbacks_ok () -> bool + (if (= (get_i32_or (present_i32_env_name) 7) 42) + (if (= (get_i32_or (invalid_env_name) 7) 7) + (if (= (get_u32_or (present_u32_env_name) 7u32) 42u32) + (if (= (get_u32_or (invalid_env_name) 7u32) 7u32) + (if (= (get_i64_or (present_i64_env_name) 9i64) 42000000000i64) + (if (= (get_i64_or (invalid_env_name) 9i64) 9i64) + (if (= (get_u64_or (present_u64_env_name) 9u64) 4294967296u64) + (if (= (get_u64_or (invalid_env_name) 9u64) 9u64) + (if (= (get_f64_or (present_f64_env_name) 1.5) 42.5) + (if (= (get_f64_or (invalid_env_name) 1.5) 1.5) + (if (get_bool_or (present_bool_env_name) false) + (get_bool_or (invalid_env_name) true) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_env_facade_ok () -> bool + (if (imported_env_missing_get_empty) + (if (imported_env_missing_result_err_one) + (if (imported_env_has_missing_false) + (if (imported_env_get_or_missing_fallback) + (if (imported_env_get_option_missing_none) + (if (imported_env_has_present_true) + (if (imported_env_get_or_present_value) + (if (imported_env_get_option_present_some) + (if (imported_env_get_i32_result_present) + (if (imported_env_get_i32_or_zero_invalid) + (if (imported_env_get_u32_result_present) + (if (imported_env_get_u32_or_zero_invalid) + (if (imported_env_get_i64_result_present) + (if (imported_env_get_i64_or_zero_missing) + (if (imported_env_get_u64_result_present) + (if (imported_env_get_u64_or_zero_missing) + (if (imported_env_get_f64_result_present) + (if (imported_env_get_f64_or_zero_invalid) + (if (imported_env_get_bool_result_present) + (if (imported_env_get_bool_or_false_invalid) + (if (imported_env_typed_options_ok) + (imported_env_typed_custom_fallbacks_ok) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn main () -> i32 + (if (imported_env_facade_ok) + 42 + 1)) + +(test "explicit std env get missing facade" + (imported_env_missing_get_empty)) + +(test "explicit std env get result missing facade" + (imported_env_missing_result_err_one)) + +(test "explicit std env has missing facade" + (imported_env_has_missing_false)) + +(test "explicit std env get or missing facade" + (imported_env_get_or_missing_fallback)) + +(test "explicit std env get option missing facade" + (imported_env_get_option_missing_none)) + +(test "explicit std env has present facade" + (imported_env_has_present_true)) + +(test "explicit std env get or present facade" + (imported_env_get_or_present_value)) + +(test "explicit std env get option present facade" + (imported_env_get_option_present_some)) + +(test "explicit std env get i32 result present facade" + (imported_env_get_i32_result_present)) + +(test "explicit std env get i32 or zero invalid facade" + (imported_env_get_i32_or_zero_invalid)) + +(test "explicit std env get u32 result present facade" + (imported_env_get_u32_result_present)) + +(test "explicit std env get u32 or zero invalid facade" + (imported_env_get_u32_or_zero_invalid)) + +(test "explicit std env get i64 result present facade" + (imported_env_get_i64_result_present)) + +(test "explicit std env get i64 or zero missing facade" + (imported_env_get_i64_or_zero_missing)) + +(test "explicit std env get u64 result present facade" + (imported_env_get_u64_result_present)) + +(test "explicit std env get u64 or zero missing facade" + (imported_env_get_u64_or_zero_missing)) + +(test "explicit std env get f64 result present facade" + (imported_env_get_f64_result_present)) + +(test "explicit std env get f64 or zero invalid facade" + (imported_env_get_f64_or_zero_invalid)) + +(test "explicit std env get bool result present facade" + (imported_env_get_bool_result_present)) + +(test "explicit std env get bool or false invalid facade" + (imported_env_get_bool_or_false_invalid)) + +(test "explicit std env typed option facade" + (imported_env_typed_options_ok)) + +(test "explicit std env typed custom fallback facade" + (imported_env_typed_custom_fallbacks_ok)) + +(test "explicit std env facade all" + (= (main) 42)) diff --git a/examples/projects/std-import-fs/slovo.toml b/examples/projects/std-import-fs/slovo.toml new file mode 100644 index 0000000..feab5dd --- /dev/null +++ b/examples/projects/std-import-fs/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-import-fs" +source_root = "src" +entry = "main" diff --git a/examples/projects/std-import-fs/src/main.slo b/examples/projects/std-import-fs/src/main.slo new file mode 100644 index 0000000..1928b8a --- /dev/null +++ b/examples/projects/std-import-fs/src/main.slo @@ -0,0 +1,387 @@ +(module main) + +(import std.fs (read_text read_text_result read_text_option write_text_status write_text_result read_text_or write_text_ok read_i32_result read_i32_option read_i32_or_zero read_i32_or read_u32_result read_u32_option read_u32_or_zero read_u32_or read_i64_result read_i64_option read_i64_or_zero read_i64_or read_u64_result read_u64_option read_u64_or_zero read_u64_or read_f64_result read_f64_option read_f64_or_zero read_f64_or read_bool_result read_bool_option read_bool_or_false read_bool_or)) + +(fn fixture_path () -> string + "glagol-std-import-fs-alpha.txt") + +(fn fixture_text () -> string + "std fs source search alpha") + +(fn missing_fixture_path () -> string + "glagol-std-import-fs-alpha-missing.txt") + +(fn fallback_text () -> string + "std fs source fallback alpha") + +(fn i32_fixture_path () -> string + "glagol-std-import-fs-alpha-i32.txt") + +(fn i64_fixture_path () -> string + "glagol-std-import-fs-alpha-i64.txt") + +(fn u32_fixture_path () -> string + "glagol-std-import-fs-alpha-u32.txt") + +(fn u64_fixture_path () -> string + "glagol-std-import-fs-alpha-u64.txt") + +(fn f64_fixture_path () -> string + "glagol-std-import-fs-alpha-f64.txt") + +(fn bool_fixture_path () -> string + "glagol-std-import-fs-alpha-bool.txt") + +(fn invalid_fixture_path () -> string + "glagol-std-import-fs-alpha-invalid.txt") + +(fn imported_write_text_status () -> i32 + (write_text_status (fixture_path) (fixture_text))) + +(fn imported_read_text () -> string + (write_text_status (fixture_path) (fixture_text)) + (read_text (fixture_path))) + +(fn imported_write_text_result () -> (result i32 i32) + (write_text_result (fixture_path) (fixture_text))) + +(fn imported_read_text_result () -> (result string i32) + (write_text_status (fixture_path) (fixture_text)) + (read_text_result (fixture_path))) + +(fn imported_read_text_option_missing_none () -> bool + (match (read_text_option (missing_fixture_path)) + ((some text) + false) + ((none) + true))) + +(fn imported_read_text_option_present_some () -> bool + (write_text_status (fixture_path) (fixture_text)) + (match (read_text_option (fixture_path)) + ((some text) + (= text (fixture_text))) + ((none) + false))) + +(fn imported_read_text_or_missing_fallback () -> bool + (= (read_text_or (missing_fixture_path) (fallback_text)) (fallback_text))) + +(fn imported_read_text_or_present_value () -> bool + (write_text_status (fixture_path) (fixture_text)) + (= (read_text_or (fixture_path) (fallback_text)) (fixture_text))) + +(fn imported_write_text_ok () -> bool + (write_text_ok (fixture_path) (fixture_text))) + +(fn imported_read_i32_result_present () -> bool + (write_text_status (i32_fixture_path) "42") + (match (read_i32_result (i32_fixture_path)) + ((ok value) + (= value 42)) + ((err code) + false))) + +(fn imported_read_i32_or_zero_invalid () -> bool + (write_text_status (invalid_fixture_path) "bad") + (= (read_i32_or_zero (invalid_fixture_path)) 0)) + +(fn imported_read_u32_result_present () -> bool + (write_text_status (u32_fixture_path) "42") + (match (read_u32_result (u32_fixture_path)) + ((ok value) + (= value 42u32)) + ((err code) + false))) + +(fn imported_read_u32_or_zero_invalid () -> bool + (write_text_status (invalid_fixture_path) "bad") + (= (read_u32_or_zero (invalid_fixture_path)) 0u32)) + +(fn imported_read_i64_result_present () -> bool + (write_text_status (i64_fixture_path) "42000000000") + (match (read_i64_result (i64_fixture_path)) + ((ok value) + (= value 42000000000i64)) + ((err code) + false))) + +(fn imported_read_i64_or_zero_missing () -> bool + (= (read_i64_or_zero (missing_fixture_path)) 0i64)) + +(fn imported_read_u64_result_present () -> bool + (write_text_status (u64_fixture_path) "4294967296") + (match (read_u64_result (u64_fixture_path)) + ((ok value) + (= value 4294967296u64)) + ((err code) + false))) + +(fn imported_read_u64_or_zero_missing () -> bool + (= (read_u64_or_zero (missing_fixture_path)) 0u64)) + +(fn imported_read_f64_result_present () -> bool + (write_text_status (f64_fixture_path) "42.5") + (match (read_f64_result (f64_fixture_path)) + ((ok value) + (= value 42.5)) + ((err code) + false))) + +(fn imported_read_f64_or_zero_invalid () -> bool + (write_text_status (invalid_fixture_path) "bad") + (= (read_f64_or_zero (invalid_fixture_path)) 0.0)) + +(fn imported_read_bool_result_present () -> bool + (write_text_status (bool_fixture_path) "true") + (match (read_bool_result (bool_fixture_path)) + ((ok value) + value) + ((err code) + false))) + +(fn imported_read_bool_or_false_invalid () -> bool + (write_text_status (invalid_fixture_path) "bad") + (if (read_bool_or_false (invalid_fixture_path)) + false + true)) + +(fn imported_typed_options_ok () -> bool + (write_text_status (i32_fixture_path) "42") + (write_text_status (u32_fixture_path) "42") + (write_text_status (i64_fixture_path) "42000000000") + (write_text_status (u64_fixture_path) "4294967296") + (write_text_status (f64_fixture_path) "42.5") + (write_text_status (bool_fixture_path) "true") + (write_text_status (invalid_fixture_path) "bad") + (if (match (read_i32_option (i32_fixture_path)) + ((some value) + (= value 42)) + ((none) + false)) + (if (match (read_i32_option (invalid_fixture_path)) + ((some value) + false) + ((none) + true)) + (if (match (read_u32_option (u32_fixture_path)) + ((some value) + (= value 42u32)) + ((none) + false)) + (if (match (read_u32_option (invalid_fixture_path)) + ((some value) + false) + ((none) + true)) + (if (match (read_i64_option (i64_fixture_path)) + ((some value) + (= value 42000000000i64)) + ((none) + false)) + (if (match (read_i64_option (missing_fixture_path)) + ((some value) + false) + ((none) + true)) + (if (match (read_u64_option (u64_fixture_path)) + ((some value) + (= value 4294967296u64)) + ((none) + false)) + (if (match (read_u64_option (missing_fixture_path)) + ((some value) + false) + ((none) + true)) + (if (match (read_f64_option (f64_fixture_path)) + ((some value) + (= value 42.5)) + ((none) + false)) + (if (match (read_f64_option (invalid_fixture_path)) + ((some value) + false) + ((none) + true)) + (if (match (read_bool_option (bool_fixture_path)) + ((some value) + value) + ((none) + false)) + (match (read_bool_option (invalid_fixture_path)) + ((some value) + false) + ((none) + true)) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_typed_custom_fallbacks_ok () -> bool + (write_text_status (i32_fixture_path) "42") + (write_text_status (u32_fixture_path) "42") + (write_text_status (i64_fixture_path) "42000000000") + (write_text_status (u64_fixture_path) "4294967296") + (write_text_status (f64_fixture_path) "42.5") + (write_text_status (bool_fixture_path) "true") + (write_text_status (invalid_fixture_path) "bad") + (if (= (read_i32_or (i32_fixture_path) 7) 42) + (if (= (read_i32_or (invalid_fixture_path) 7) 7) + (if (= (read_u32_or (u32_fixture_path) 7u32) 42u32) + (if (= (read_u32_or (invalid_fixture_path) 7u32) 7u32) + (if (= (read_i64_or (i64_fixture_path) 9i64) 42000000000i64) + (if (= (read_i64_or (invalid_fixture_path) 9i64) 9i64) + (if (= (read_u64_or (u64_fixture_path) 9u64) 4294967296u64) + (if (= (read_u64_or (invalid_fixture_path) 9u64) 9u64) + (if (= (read_f64_or (f64_fixture_path) 1.5) 42.5) + (if (= (read_f64_or (invalid_fixture_path) 1.5) 1.5) + (if (read_bool_or (bool_fixture_path) false) + (read_bool_or (invalid_fixture_path) true) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_status_roundtrip_ok () -> bool + (write_text_status (fixture_path) (fixture_text)) + (= (read_text (fixture_path)) (fixture_text))) + +(fn imported_result_roundtrip_ok () -> bool + (write_text_result (fixture_path) (fixture_text)) + (= (unwrap_ok (read_text_result (fixture_path))) (fixture_text))) + +(fn imported_fs_facade_ok () -> bool + (if (imported_status_roundtrip_ok) + (if (imported_result_roundtrip_ok) + (if (imported_read_text_option_missing_none) + (if (imported_read_text_option_present_some) + (if (imported_read_text_or_missing_fallback) + (if (imported_read_text_or_present_value) + (if (imported_write_text_ok) + (if (imported_read_i32_result_present) + (if (imported_read_i32_or_zero_invalid) + (if (imported_read_u32_result_present) + (if (imported_read_u32_or_zero_invalid) + (if (imported_read_i64_result_present) + (if (imported_read_i64_or_zero_missing) + (if (imported_read_u64_result_present) + (if (imported_read_u64_or_zero_missing) + (if (imported_read_f64_result_present) + (if (imported_read_f64_or_zero_invalid) + (if (imported_read_bool_result_present) + (if (imported_read_bool_or_false_invalid) + (if (imported_typed_options_ok) + (imported_typed_custom_fallbacks_ok) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn main () -> i32 + (if (imported_fs_facade_ok) + 42 + 1)) + +(test "explicit std fs write text status facade" + (= (imported_write_text_status) 0)) + +(test "explicit std fs read text facade" + (= (imported_read_text) (fixture_text))) + +(test "explicit std fs write text result facade" + (= (unwrap_ok (imported_write_text_result)) 0)) + +(test "explicit std fs read text result facade" + (= (unwrap_ok (imported_read_text_result)) (fixture_text))) + +(test "explicit std fs read text option missing facade" + (imported_read_text_option_missing_none)) + +(test "explicit std fs read text option present facade" + (imported_read_text_option_present_some)) + +(test "explicit std fs read text or missing facade" + (imported_read_text_or_missing_fallback)) + +(test "explicit std fs read text or present facade" + (imported_read_text_or_present_value)) + +(test "explicit std fs write text ok facade" + (imported_write_text_ok)) + +(test "explicit std fs read i32 result present facade" + (imported_read_i32_result_present)) + +(test "explicit std fs read i32 or zero invalid facade" + (imported_read_i32_or_zero_invalid)) + +(test "explicit std fs read u32 result present facade" + (imported_read_u32_result_present)) + +(test "explicit std fs read u32 or zero invalid facade" + (imported_read_u32_or_zero_invalid)) + +(test "explicit std fs read i64 result present facade" + (imported_read_i64_result_present)) + +(test "explicit std fs read i64 or zero missing facade" + (imported_read_i64_or_zero_missing)) + +(test "explicit std fs read u64 result present facade" + (imported_read_u64_result_present)) + +(test "explicit std fs read u64 or zero missing facade" + (imported_read_u64_or_zero_missing)) + +(test "explicit std fs read f64 result present facade" + (imported_read_f64_result_present)) + +(test "explicit std fs read f64 or zero invalid facade" + (imported_read_f64_or_zero_invalid)) + +(test "explicit std fs read bool result present facade" + (imported_read_bool_result_present)) + +(test "explicit std fs read bool or false invalid facade" + (imported_read_bool_or_false_invalid)) + +(test "explicit std fs typed option facade" + (imported_typed_options_ok)) + +(test "explicit std fs typed custom fallback facade" + (imported_typed_custom_fallbacks_ok)) + +(test "explicit std fs facade all" + (= (main) 42)) diff --git a/examples/projects/std-import-io/slovo.toml b/examples/projects/std-import-io/slovo.toml new file mode 100644 index 0000000..3b24318 --- /dev/null +++ b/examples/projects/std-import-io/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-import-io" +source_root = "src" +entry = "main" diff --git a/examples/projects/std-import-io/src/main.slo b/examples/projects/std-import-io/src/main.slo new file mode 100644 index 0000000..4279801 --- /dev/null +++ b/examples/projects/std-import-io/src/main.slo @@ -0,0 +1,221 @@ +(module main) + +(import std.io (print_i32_zero print_u32_zero print_i64_zero print_u64_zero print_f64_zero print_string_zero print_bool_zero print_i32_value print_u32_value print_i64_value print_u64_value print_f64_value print_string_value print_bool_value read_stdin_result read_stdin_option read_stdin_or read_stdin_i32_result read_stdin_i32_option read_stdin_i32_or_zero read_stdin_i32_or read_stdin_u32_result read_stdin_u32_option read_stdin_u32_or_zero read_stdin_u32_or read_stdin_i64_result read_stdin_i64_option read_stdin_i64_or_zero read_stdin_i64_or read_stdin_u64_result read_stdin_u64_option read_stdin_u64_or_zero read_stdin_u64_or read_stdin_f64_result read_stdin_f64_option read_stdin_f64_or_zero read_stdin_f64_or read_stdin_bool_result read_stdin_bool_option read_stdin_bool_or_false read_stdin_bool_or)) + +(fn imported_print_i32_status () -> i32 + (print_i32_zero 42)) + +(fn imported_print_i64_status () -> i32 + (print_i64_zero 42i64)) + +(fn imported_print_u32_status () -> i32 + (print_u32_zero 42u32)) + +(fn imported_print_u64_status () -> i32 + (print_u64_zero 42u64)) + +(fn imported_print_f64_status () -> i32 + (print_f64_zero 42.5)) + +(fn imported_print_string_status () -> i32 + (print_string_zero "slovo")) + +(fn imported_print_bool_status () -> i32 + (print_bool_zero true)) + +(fn imported_print_value_helpers_ok () -> bool + (if (= (print_u32_value 42u32) 42u32) + (if (= (print_i64_value 42i64) 42i64) + (if (= (print_u64_value 42u64) 42u64) + (if (= (print_f64_value 42.5) 42.5) + true + false) + false) + false) + false)) + +(fn imported_stdin_result_ok () -> bool + (match (read_stdin_result) + ((ok payload) + (= payload "")) + ((err code) + false))) + +(fn imported_stdin_option_ok () -> bool + (match (read_stdin_option) + ((some payload) + (= payload "")) + ((none) + false))) + +(fn imported_stdin_text_fallback_ok () -> bool + (= (read_stdin_or "fallback") "")) + +(fn imported_stdin_typed_results_ok () -> bool + (if (match (read_stdin_i32_result) + ((ok payload) + false) + ((err code) + true)) + (if (match (read_stdin_u32_result) + ((ok payload) + false) + ((err code) + true)) + (if (match (read_stdin_i64_result) + ((ok payload) + false) + ((err code) + true)) + (if (match (read_stdin_u64_result) + ((ok payload) + false) + ((err code) + true)) + (if (match (read_stdin_f64_result) + ((ok payload) + false) + ((err code) + true)) + (match (read_stdin_bool_result) + ((ok payload) + false) + ((err code) + true)) + false) + false) + false) + false) + false)) + +(fn imported_stdin_typed_options_ok () -> bool + (if (match (read_stdin_i32_option) + ((some payload) + false) + ((none) + true)) + (if (match (read_stdin_u32_option) + ((some payload) + false) + ((none) + true)) + (if (match (read_stdin_i64_option) + ((some payload) + false) + ((none) + true)) + (if (match (read_stdin_u64_option) + ((some payload) + false) + ((none) + true)) + (if (match (read_stdin_f64_option) + ((some payload) + false) + ((none) + true)) + (match (read_stdin_bool_option) + ((some payload) + false) + ((none) + true)) + false) + false) + false) + false) + false)) + +(fn imported_stdin_bool_fallbacks_ok () -> bool + (let empty_bool bool (read_stdin_bool_or_false)) + (let fallback_bool bool (read_stdin_bool_or true)) + (if empty_bool + false + fallback_bool)) + +(fn imported_stdin_typed_fallbacks_ok () -> bool + (if (= (read_stdin_i32_or_zero) 0) + (if (= (read_stdin_i32_or 7) 7) + (if (= (read_stdin_u32_or_zero) 0u32) + (if (= (read_stdin_u32_or 7u32) 7u32) + (if (= (read_stdin_i64_or_zero) 0i64) + (if (= (read_stdin_i64_or 9i64) 9i64) + (if (= (read_stdin_u64_or_zero) 0u64) + (if (= (read_stdin_u64_or 9u64) 9u64) + (if (= (read_stdin_f64_or_zero) 0.0) + (if (= (read_stdin_f64_or 1.5) 1.5) + (imported_stdin_bool_fallbacks_ok) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_io_status_all () -> i32 + (imported_print_u32_status) + (imported_print_i64_status) + (imported_print_u64_status) + (imported_print_f64_status) + 42) + +(fn imported_io_helpers_ok () -> bool + (if (= (imported_io_status_all) 42) + (if (imported_print_value_helpers_ok) + (if (imported_stdin_result_ok) + (if (imported_stdin_option_ok) + (if (imported_stdin_text_fallback_ok) + (if (imported_stdin_typed_results_ok) + (if (imported_stdin_typed_options_ok) + (imported_stdin_typed_fallbacks_ok) + false) + false) + false) + false) + false) + false) + false)) + +(fn main () -> i32 + (if (imported_io_helpers_ok) + 42 + 1)) + +(test "explicit std io i64 zero facade" + (= (imported_print_i64_status) 0)) + +(test "explicit std io u32 zero facade" + (= (imported_print_u32_status) 0)) + +(test "explicit std io u64 zero facade" + (= (imported_print_u64_status) 0)) + +(test "explicit std io f64 zero facade" + (= (imported_print_f64_status) 0)) + +(test "explicit std io value facade" + (imported_print_value_helpers_ok)) + +(test "explicit std io stdin result facade" + (imported_stdin_result_ok)) + +(test "explicit std io stdin option facade" + (imported_stdin_option_ok)) + +(test "explicit std io stdin text fallback facade" + (imported_stdin_text_fallback_ok)) + +(test "explicit std io stdin typed result facade" + (imported_stdin_typed_results_ok)) + +(test "explicit std io stdin typed option facade" + (imported_stdin_typed_options_ok)) + +(test "explicit std io stdin typed fallback facade" + (imported_stdin_typed_fallbacks_ok)) + +(test "explicit std io helpers all" + (= (main) 42)) diff --git a/examples/projects/std-import-math/slovo.toml b/examples/projects/std-import-math/slovo.toml new file mode 100644 index 0000000..e0e742c --- /dev/null +++ b/examples/projects/std-import-math/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-import-math" +source_root = "src" +entry = "main" diff --git a/examples/projects/std-import-math/src/main.slo b/examples/projects/std-import-math/src/main.slo new file mode 100644 index 0000000..cf2beb4 --- /dev/null +++ b/examples/projects/std-import-math/src/main.slo @@ -0,0 +1,102 @@ +(module main) + +(import std.math (abs_i32 neg_i32 rem_i32 bit_and_i32 bit_or_i32 bit_xor_i32 is_even_i32 is_odd_i32 min_i32 max_i32 clamp_i32 square_i32 cube_i32 is_zero_i32 is_positive_i32 is_negative_i32 in_range_i32 abs_i64 neg_i64 rem_i64 bit_and_i64 bit_or_i64 bit_xor_i64 is_even_i64 is_odd_i64 min_i64 max_i64 clamp_i64 square_i64 cube_i64 is_zero_i64 is_positive_i64 is_negative_i64 in_range_i64 abs_f64 neg_f64 min_f64 max_f64 clamp_f64 square_f64 cube_f64 is_zero_f64 is_positive_f64 is_negative_f64 in_range_f64)) + +(fn imported_i32_score () -> i32 + (clamp_i32 (+ (square_i32 (abs_i32 (neg_i32 6))) (max_i32 (min_i32 (cube_i32 1) 7) 6)) 0 42)) + +(fn imported_i64_score () -> i64 + (clamp_i64 (+ (square_i64 (abs_i64 (neg_i64 6i64))) (max_i64 (min_i64 (cube_i64 1i64) 7i64) 6i64)) 0i64 42i64)) + +(fn imported_f64_score () -> f64 + (clamp_f64 (+ (square_f64 (abs_f64 (neg_f64 6.0))) (max_f64 (min_f64 (cube_f64 1.0) 7.0) 6.0)) 0.0 42.0)) + +(fn imported_i32_predicates_ok () -> bool + (if (is_zero_i32 (neg_i32 0)) + (if (is_positive_i32 (abs_i32 -5)) + (if (is_negative_i32 (neg_i32 5)) + (if (= (rem_i32 17 5) 2) + (if (is_even_i32 42) + (if (is_odd_i32 -41) + (if (= (bit_and_i32 6 3) 2) + (if (= (bit_or_i32 4 2) 6) + (if (= (bit_xor_i32 7 3) 4) + (in_range_i32 (imported_i32_score) 0 42) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_i64_predicates_ok () -> bool + (if (is_zero_i64 (neg_i64 0i64)) + (if (is_positive_i64 (abs_i64 -5i64)) + (if (is_negative_i64 (neg_i64 5i64)) + (if (= (rem_i64 17i64 5i64) 2i64) + (if (is_even_i64 42i64) + (if (is_odd_i64 -41i64) + (if (= (bit_and_i64 6i64 3i64) 2i64) + (if (= (bit_or_i64 4i64 2i64) 6i64) + (if (= (bit_xor_i64 7i64 3i64) 4i64) + (in_range_i64 (imported_i64_score) 0i64 42i64) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_f64_predicates_ok () -> bool + (if (is_zero_f64 (neg_f64 0.0)) + (if (is_positive_f64 (abs_f64 -5.0)) + (if (is_negative_f64 (neg_f64 5.0)) + (in_range_f64 (imported_f64_score) 0.0 42.0) + false) + false) + false)) + +(fn imported_i32_helpers_ok () -> bool + (if (= (imported_i32_score) 42) + (imported_i32_predicates_ok) + false)) + +(fn imported_i64_helpers_ok () -> bool + (if (= (imported_i64_score) 42i64) + (imported_i64_predicates_ok) + false)) + +(fn imported_f64_helpers_ok () -> bool + (if (= (imported_f64_score) 42.0) + (imported_f64_predicates_ok) + false)) + +(fn imported_math_helpers_ok () -> bool + (if (imported_i32_helpers_ok) + (if (imported_i64_helpers_ok) + (imported_f64_helpers_ok) + false) + false)) + +(fn main () -> i32 + (if (imported_math_helpers_ok) + 42 + 1)) + +(test "explicit std math import i32 helpers" + (imported_i32_helpers_ok)) + +(test "explicit std math import i64 helpers" + (imported_i64_helpers_ok)) + +(test "explicit std math import f64 helpers" + (imported_f64_helpers_ok)) + +(test "explicit std math import all helpers" + (= (main) 42)) diff --git a/examples/projects/std-import-num/slovo.toml b/examples/projects/std-import-num/slovo.toml new file mode 100644 index 0000000..440bbd0 --- /dev/null +++ b/examples/projects/std-import-num/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-import-num" +source_root = "src" +entry = "main" diff --git a/examples/projects/std-import-num/src/main.slo b/examples/projects/std-import-num/src/main.slo new file mode 100644 index 0000000..989a86d --- /dev/null +++ b/examples/projects/std-import-num/src/main.slo @@ -0,0 +1,70 @@ +(module main) + +(import std.num (i32_to_i64 i32_to_f64 i64_to_f64 i64_to_i32_result f64_to_i32_result f64_to_i64_result i32_to_string u32_to_string i64_to_string u64_to_string f64_to_string i64_to_i32_or f64_to_i32_or f64_to_i64_or)) + +(fn imported_num_widening_ok () -> bool + (if (= (i32_to_i64 42) 42i64) + (if (= (i32_to_f64 42) 42.0) + (= (i64_to_f64 42i64) 42.0) + false) + false)) + +(fn imported_num_checked_ok () -> bool + (if (= (unwrap_ok (i64_to_i32_result 42i64)) 42) + (if (= (unwrap_ok (f64_to_i32_result 42.0)) 42) + (= (unwrap_ok (f64_to_i64_result 42.0)) 42i64) + false) + false)) + +(fn imported_num_strings_ok () -> bool + (if (= (i32_to_string 42) "42") + (if (= (u32_to_string 42u32) "42") + (if (= (i64_to_string 42i64) "42") + (if (= (u64_to_string 42u64) "42") + (= (f64_to_string 42.0) "42.0") + false) + false) + false) + false)) + +(fn imported_num_fallbacks_ok () -> bool + (if (= (i64_to_i32_or 42i64 7) 42) + (if (= (i64_to_i32_or 2147483648i64 7) 7) + (if (= (f64_to_i32_or 42.0 7) 42) + (if (= (f64_to_i32_or 2147483648.0 7) 7) + (if (= (f64_to_i64_or 42.0 7i64) 42i64) + (= (f64_to_i64_or 100000000000000000000.0 7i64) 7i64) + false) + false) + false) + false) + false)) + +(fn imported_num_helpers_ok () -> bool + (if (imported_num_widening_ok) + (if (imported_num_checked_ok) + (if (imported_num_strings_ok) + (imported_num_fallbacks_ok) + false) + false) + false)) + +(fn main () -> i32 + (if (imported_num_helpers_ok) + 42 + 1)) + +(test "explicit std num widening" + (imported_num_widening_ok)) + +(test "explicit std num checked conversions" + (imported_num_checked_ok)) + +(test "explicit std num to string" + (imported_num_strings_ok)) + +(test "explicit std num checked fallbacks" + (imported_num_fallbacks_ok)) + +(test "explicit std num helpers all" + (= (main) 42)) diff --git a/examples/projects/std-import-option/slovo.toml b/examples/projects/std-import-option/slovo.toml new file mode 100644 index 0000000..03f924c --- /dev/null +++ b/examples/projects/std-import-option/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-import-option" +source_root = "src" +entry = "main" diff --git a/examples/projects/std-import-option/src/main.slo b/examples/projects/std-import-option/src/main.slo new file mode 100644 index 0000000..f571792 --- /dev/null +++ b/examples/projects/std-import-option/src/main.slo @@ -0,0 +1,408 @@ +(module main) + +(import std.option (some_i32 none_i32 is_some_i32 is_none_i32 unwrap_some_i32 unwrap_or_i32 some_or_err_i32 some_u32 none_u32 is_some_u32 is_none_u32 unwrap_some_u32 unwrap_or_u32 some_or_err_u32 some_i64 none_i64 is_some_i64 is_none_i64 unwrap_some_i64 unwrap_or_i64 some_or_err_i64 some_u64 none_u64 is_some_u64 is_none_u64 unwrap_some_u64 unwrap_or_u64 some_or_err_u64 some_f64 none_f64 is_some_f64 is_none_f64 unwrap_some_f64 unwrap_or_f64 some_or_err_f64 some_bool none_bool is_some_bool is_none_bool unwrap_some_bool unwrap_or_bool some_or_err_bool some_string none_string is_some_string is_none_string unwrap_some_string unwrap_or_string some_or_err_string)) + +(fn some_value_i32 ((value i32)) -> (option i32) + (some_i32 value)) + +(fn none_value_i32 () -> (option i32) + (none_i32)) + +(fn some_value_i64 ((value i64)) -> (option i64) + (some_i64 value)) + +(fn none_value_i64 () -> (option i64) + (none_i64)) + +(fn some_value_u32 ((value u32)) -> (option u32) + (some_u32 value)) + +(fn none_value_u32 () -> (option u32) + (none_u32)) + +(fn some_value_u64 ((value u64)) -> (option u64) + (some_u64 value)) + +(fn none_value_u64 () -> (option u64) + (none_u64)) + +(fn some_value_f64 ((value f64)) -> (option f64) + (some_f64 value)) + +(fn none_value_f64 () -> (option f64) + (none_f64)) + +(fn some_value_bool ((value bool)) -> (option bool) + (some_bool value)) + +(fn none_value_bool () -> (option bool) + (none_bool)) + +(fn some_value_string ((value string)) -> (option string) + (some_string value)) + +(fn none_value_string () -> (option string) + (none_string)) + +(fn imported_option_i32_observation_ok () -> bool + (if (is_some_i32 (some_value_i32 42)) + (is_none_i32 (none_value_i32)) + false)) + +(fn imported_option_i32_unwrap_some_score () -> i32 + (+ (unwrap_some_i32 (some_value_i32 20)) (unwrap_some_i32 (some_value_i32 22)))) + +(fn imported_option_i32_unwrap_or_score () -> i32 + (+ (unwrap_or_i32 (some_value_i32 40) 0) (unwrap_or_i32 (none_value_i32) 2))) + +(fn imported_option_i32_some_or_err_ok () -> bool + (match (some_or_err_i32 (some_value_i32 42) 5) + ((ok payload) + (= payload 42)) + ((err code) + false))) + +(fn imported_option_i32_some_or_err_err () -> bool + (match (some_or_err_i32 (none_value_i32) 5) + ((ok payload) + false) + ((err code) + (= code 5)))) + +(fn imported_option_i64_observation_ok () -> bool + (if (is_some_i64 (some_value_i64 2147483648i64)) + (is_none_i64 (none_value_i64)) + false)) + +(fn imported_option_i64_unwrap_some_score () -> i64 + (+ (unwrap_some_i64 (some_value_i64 2147483648i64)) (unwrap_some_i64 (some_value_i64 12i64)))) + +(fn imported_option_i64_unwrap_or_score () -> i64 + (+ (unwrap_or_i64 (some_value_i64 40i64) 0i64) (unwrap_or_i64 (none_value_i64) 2i64))) + +(fn imported_option_i64_some_or_err_ok () -> bool + (match (some_or_err_i64 (some_value_i64 2147483648i64) 6) + ((ok payload) + (= payload 2147483648i64)) + ((err code) + false))) + +(fn imported_option_i64_some_or_err_err () -> bool + (match (some_or_err_i64 (none_value_i64) 6) + ((ok payload) + false) + ((err code) + (= code 6)))) + +(fn imported_option_u32_observation_ok () -> bool + (if (is_some_u32 (some_value_u32 42u32)) + (is_none_u32 (none_value_u32)) + false)) + +(fn imported_option_u32_unwrap_some_score () -> u32 + (+ (unwrap_some_u32 (some_value_u32 20u32)) (unwrap_some_u32 (some_value_u32 22u32)))) + +(fn imported_option_u32_unwrap_or_score () -> u32 + (+ (unwrap_or_u32 (some_value_u32 40u32) 0u32) (unwrap_or_u32 (none_value_u32) 2u32))) + +(fn imported_option_u32_some_or_err_ok () -> bool + (match (some_or_err_u32 (some_value_u32 42u32) 10) + ((ok payload) + (= payload 42u32)) + ((err code) + false))) + +(fn imported_option_u32_some_or_err_err () -> bool + (match (some_or_err_u32 (none_value_u32) 10) + ((ok payload) + false) + ((err code) + (= code 10)))) + +(fn imported_option_u64_observation_ok () -> bool + (if (is_some_u64 (some_value_u64 4294967296u64)) + (is_none_u64 (none_value_u64)) + false)) + +(fn imported_option_u64_unwrap_some_score () -> u64 + (+ (unwrap_some_u64 (some_value_u64 4294967296u64)) (unwrap_some_u64 (some_value_u64 12u64)))) + +(fn imported_option_u64_unwrap_or_score () -> u64 + (+ (unwrap_or_u64 (some_value_u64 40u64) 0u64) (unwrap_or_u64 (none_value_u64) 2u64))) + +(fn imported_option_u64_some_or_err_ok () -> bool + (match (some_or_err_u64 (some_value_u64 4294967296u64) 11) + ((ok payload) + (= payload 4294967296u64)) + ((err code) + false))) + +(fn imported_option_u64_some_or_err_err () -> bool + (match (some_or_err_u64 (none_value_u64) 11) + ((ok payload) + false) + ((err code) + (= code 11)))) + +(fn imported_option_f64_observation_ok () -> bool + (if (is_some_f64 (some_value_f64 42.5)) + (is_none_f64 (none_value_f64)) + false)) + +(fn imported_option_f64_unwrap_some_score () -> f64 + (+ (unwrap_some_f64 (some_value_f64 20.5)) (unwrap_some_f64 (some_value_f64 21.5)))) + +(fn imported_option_f64_unwrap_or_score () -> f64 + (+ (unwrap_or_f64 (some_value_f64 40.0) 0.0) (unwrap_or_f64 (none_value_f64) 2.0))) + +(fn imported_option_f64_some_or_err_ok () -> bool + (match (some_or_err_f64 (some_value_f64 42.5) 7) + ((ok payload) + (= payload 42.5)) + ((err code) + false))) + +(fn imported_option_f64_some_or_err_err () -> bool + (match (some_or_err_f64 (none_value_f64) 7) + ((ok payload) + false) + ((err code) + (= code 7)))) + +(fn imported_option_bool_observation_ok () -> bool + (if (is_some_bool (some_value_bool true)) + (is_none_bool (none_value_bool)) + false)) + +(fn imported_option_bool_unwrap_some_ok () -> bool + (unwrap_some_bool (some_value_bool true))) + +(fn imported_option_bool_unwrap_or_ok () -> bool + (if (unwrap_or_bool (some_value_bool true) false) + (unwrap_or_bool (none_value_bool) true) + false)) + +(fn imported_option_bool_some_or_err_ok () -> bool + (match (some_or_err_bool (some_value_bool true) 8) + ((ok payload) + payload) + ((err code) + false))) + +(fn imported_option_bool_some_or_err_err () -> bool + (match (some_or_err_bool (none_value_bool) 8) + ((ok payload) + false) + ((err code) + (= code 8)))) + +(fn imported_option_string_observation_ok () -> bool + (if (is_some_string (some_value_string "slovo")) + (is_none_string (none_value_string)) + false)) + +(fn imported_option_string_unwrap_some_ok () -> bool + (= (unwrap_some_string (some_value_string "slovo")) "slovo")) + +(fn imported_option_string_unwrap_or_ok () -> bool + (if (= (unwrap_or_string (some_value_string "oak") "") "oak") + (= (unwrap_or_string (none_value_string) "fallback") "fallback") + false)) + +(fn imported_option_string_some_or_err_ok () -> bool + (match (some_or_err_string (some_value_string "slovo") 9) + ((ok payload) + (= payload "slovo")) + ((err code) + false))) + +(fn imported_option_string_some_or_err_err () -> bool + (match (some_or_err_string (none_value_string) 9) + ((ok payload) + false) + ((err code) + (= code 9)))) + +(fn imported_option_helpers_ok () -> bool + (if (imported_option_i32_observation_ok) + (if (= (imported_option_i32_unwrap_some_score) 42) + (if (= (imported_option_i32_unwrap_or_score) 42) + (if (imported_option_i32_some_or_err_ok) + (if (imported_option_i32_some_or_err_err) + (if (imported_option_u32_observation_ok) + (if (= (imported_option_u32_unwrap_some_score) 42u32) + (if (= (imported_option_u32_unwrap_or_score) 42u32) + (if (imported_option_u32_some_or_err_ok) + (if (imported_option_u32_some_or_err_err) + (if (imported_option_i64_observation_ok) + (if (= (imported_option_i64_unwrap_some_score) 2147483660i64) + (if (= (imported_option_i64_unwrap_or_score) 42i64) + (if (imported_option_i64_some_or_err_ok) + (if (imported_option_i64_some_or_err_err) + (if (imported_option_u64_observation_ok) + (if (= (imported_option_u64_unwrap_some_score) 4294967308u64) + (if (= (imported_option_u64_unwrap_or_score) 42u64) + (if (imported_option_u64_some_or_err_ok) + (if (imported_option_u64_some_or_err_err) + (if (imported_option_f64_observation_ok) + (if (= (imported_option_f64_unwrap_some_score) 42.0) + (if (= (imported_option_f64_unwrap_or_score) 42.0) + (if (imported_option_f64_some_or_err_ok) + (if (imported_option_f64_some_or_err_err) + (if (imported_option_bool_observation_ok) + (if (imported_option_bool_unwrap_some_ok) + (if (imported_option_bool_unwrap_or_ok) + (if (imported_option_bool_some_or_err_ok) + (if (imported_option_bool_some_or_err_err) + (if (imported_option_string_observation_ok) + (if (imported_option_string_unwrap_some_ok) + (if (imported_option_string_unwrap_or_ok) + (if (imported_option_string_some_or_err_ok) + (imported_option_string_some_or_err_err) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn main () -> i32 + (if (imported_option_helpers_ok) + 42 + 1)) + +(test "explicit std option i32 observation" + (imported_option_i32_observation_ok)) + +(test "explicit std option i32 unwrap_some" + (= (imported_option_i32_unwrap_some_score) 42)) + +(test "explicit std option i32 unwrap_or" + (= (imported_option_i32_unwrap_or_score) 42)) + +(test "explicit std option i32 some_or_err ok" + (imported_option_i32_some_or_err_ok)) + +(test "explicit std option i32 some_or_err err" + (imported_option_i32_some_or_err_err)) + +(test "explicit std option u32 observation" + (imported_option_u32_observation_ok)) + +(test "explicit std option u32 unwrap_some" + (= (imported_option_u32_unwrap_some_score) 42u32)) + +(test "explicit std option u32 unwrap_or" + (= (imported_option_u32_unwrap_or_score) 42u32)) + +(test "explicit std option u32 some_or_err ok" + (imported_option_u32_some_or_err_ok)) + +(test "explicit std option u32 some_or_err err" + (imported_option_u32_some_or_err_err)) + +(test "explicit std option i64 observation" + (imported_option_i64_observation_ok)) + +(test "explicit std option i64 unwrap_some" + (= (imported_option_i64_unwrap_some_score) 2147483660i64)) + +(test "explicit std option i64 unwrap_or" + (= (imported_option_i64_unwrap_or_score) 42i64)) + +(test "explicit std option i64 some_or_err ok" + (imported_option_i64_some_or_err_ok)) + +(test "explicit std option i64 some_or_err err" + (imported_option_i64_some_or_err_err)) + +(test "explicit std option u64 observation" + (imported_option_u64_observation_ok)) + +(test "explicit std option u64 unwrap_some" + (= (imported_option_u64_unwrap_some_score) 4294967308u64)) + +(test "explicit std option u64 unwrap_or" + (= (imported_option_u64_unwrap_or_score) 42u64)) + +(test "explicit std option u64 some_or_err ok" + (imported_option_u64_some_or_err_ok)) + +(test "explicit std option u64 some_or_err err" + (imported_option_u64_some_or_err_err)) + +(test "explicit std option f64 observation" + (imported_option_f64_observation_ok)) + +(test "explicit std option f64 unwrap_some" + (= (imported_option_f64_unwrap_some_score) 42.0)) + +(test "explicit std option f64 unwrap_or" + (= (imported_option_f64_unwrap_or_score) 42.0)) + +(test "explicit std option f64 some_or_err ok" + (imported_option_f64_some_or_err_ok)) + +(test "explicit std option f64 some_or_err err" + (imported_option_f64_some_or_err_err)) + +(test "explicit std option bool observation" + (imported_option_bool_observation_ok)) + +(test "explicit std option bool unwrap_some" + (imported_option_bool_unwrap_some_ok)) + +(test "explicit std option bool unwrap_or" + (imported_option_bool_unwrap_or_ok)) + +(test "explicit std option bool some_or_err ok" + (imported_option_bool_some_or_err_ok)) + +(test "explicit std option bool some_or_err err" + (imported_option_bool_some_or_err_err)) + +(test "explicit std option string observation" + (imported_option_string_observation_ok)) + +(test "explicit std option string unwrap_some" + (imported_option_string_unwrap_some_ok)) + +(test "explicit std option string unwrap_or" + (imported_option_string_unwrap_or_ok)) + +(test "explicit std option string some_or_err ok" + (imported_option_string_some_or_err_ok)) + +(test "explicit std option string some_or_err err" + (imported_option_string_some_or_err_err)) + +(test "explicit std option helpers all" + (= (main) 42)) diff --git a/examples/projects/std-import-process/slovo.toml b/examples/projects/std-import-process/slovo.toml new file mode 100644 index 0000000..5d444fe --- /dev/null +++ b/examples/projects/std-import-process/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-import-process" +source_root = "src" +entry = "main" diff --git a/examples/projects/std-import-process/src/main.slo b/examples/projects/std-import-process/src/main.slo new file mode 100644 index 0000000..75290c5 --- /dev/null +++ b/examples/projects/std-import-process/src/main.slo @@ -0,0 +1,319 @@ +(module main) + +(import std.process (argc arg arg_result arg_option has_arg arg_or arg_or_empty arg_i32_result arg_i32_option arg_i32_or_zero arg_i32_or arg_u32_result arg_u32_option arg_u32_or_zero arg_u32_or arg_i64_result arg_i64_option arg_i64_or_zero arg_i64_or arg_u64_result arg_u64_option arg_u64_or_zero arg_u64_or arg_f64_result arg_f64_option arg_f64_or_zero arg_f64_or arg_bool_result arg_bool_option arg_bool_or_false arg_bool_or)) + +(fn impossible_index () -> i32 + 99999) + +(fn first_arg () -> string + (arg 0)) + +(fn first_index () -> i32 + 0) + +(fn imported_arg_count_positive () -> bool + (< 0 (argc))) + +(fn imported_arg_result_oob_err_one () -> bool + (match (arg_result (impossible_index)) + ((ok payload) + false) + ((err code) + (= code 1)))) + +(fn imported_arg_option_flows_ok () -> bool + (if (match (arg_option (first_index)) + ((some payload) + (= payload (arg (first_index)))) + ((none) + false)) + (match (arg_option (impossible_index)) + ((some payload) + false) + ((none) + true)) + false)) + +(fn imported_has_arg_zero () -> bool + (has_arg 0)) + +(fn imported_arg_fallback_missing_ok () -> bool + (if (= (arg_or (impossible_index) "fallback") "fallback") + (= (arg_or_empty (impossible_index)) "") + false)) + +(fn imported_arg_fallback_present_ok () -> bool + (if (has_arg 0) + (if (= (arg_or 0 "fallback") (arg 0)) + (= (arg_or_empty 0) (arg 0)) + false) + false)) + +(fn imported_arg_i32_missing_err_one () -> bool + (match (arg_i32_result (impossible_index)) + ((ok value) + false) + ((err code) + (= code 1)))) + +(fn imported_arg_i32_present_invalid_zero () -> bool + (= (arg_i32_or_zero (first_index)) 0)) + +(fn imported_arg_u32_missing_err_one () -> bool + (match (arg_u32_result (impossible_index)) + ((ok value) + false) + ((err code) + (= code 1)))) + +(fn imported_arg_u32_present_invalid_zero () -> bool + (= (arg_u32_or_zero (first_index)) 0u32)) + +(fn imported_arg_i64_missing_err_one () -> bool + (match (arg_i64_result (impossible_index)) + ((ok value) + false) + ((err code) + (= code 1)))) + +(fn imported_arg_i64_present_invalid_zero () -> bool + (= (arg_i64_or_zero (first_index)) 0i64)) + +(fn imported_arg_u64_missing_err_one () -> bool + (match (arg_u64_result (impossible_index)) + ((ok value) + false) + ((err code) + (= code 1)))) + +(fn imported_arg_u64_present_invalid_zero () -> bool + (= (arg_u64_or_zero (first_index)) 0u64)) + +(fn imported_arg_f64_missing_err_one () -> bool + (match (arg_f64_result (impossible_index)) + ((ok value) + false) + ((err code) + (= code 1)))) + +(fn imported_arg_f64_present_invalid_zero () -> bool + (= (arg_f64_or_zero (first_index)) 0.0)) + +(fn imported_arg_bool_missing_err_one () -> bool + (match (arg_bool_result (impossible_index)) + ((ok value) + false) + ((err code) + (= code 1)))) + +(fn imported_arg_bool_present_invalid_false () -> bool + (if (arg_bool_or_false (first_index)) + false + true)) + +(fn imported_arg_typed_options_ok () -> bool + (if (match (arg_i32_option (impossible_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_i32_option (first_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_u32_option (impossible_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_u32_option (first_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_i64_option (impossible_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_i64_option (first_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_u64_option (impossible_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_u64_option (first_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_f64_option (impossible_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_f64_option (first_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_bool_option (impossible_index)) + ((some value) + false) + ((none) + true)) + (match (arg_bool_option (first_index)) + ((some value) + false) + ((none) + true)) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_arg_typed_custom_fallbacks_ok () -> bool + (if (= (arg_i32_or (impossible_index) 7) 7) + (if (= (arg_i32_or (first_index) 7) 7) + (if (= (arg_u32_or (impossible_index) 7u32) 7u32) + (if (= (arg_u32_or (first_index) 7u32) 7u32) + (if (= (arg_i64_or (impossible_index) 9i64) 9i64) + (if (= (arg_i64_or (first_index) 9i64) 9i64) + (if (= (arg_u64_or (impossible_index) 9u64) 9u64) + (if (= (arg_u64_or (first_index) 9u64) 9u64) + (if (= (arg_f64_or (impossible_index) 1.5) 1.5) + (if (= (arg_f64_or (first_index) 1.5) 1.5) + (if (arg_bool_or (impossible_index) true) + (arg_bool_or (first_index) true) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_process_facade_ok () -> bool + (if (imported_arg_count_positive) + (if (imported_arg_result_oob_err_one) + (if (imported_arg_option_flows_ok) + (if (imported_has_arg_zero) + (if (imported_arg_fallback_missing_ok) + (if (imported_arg_fallback_present_ok) + (if (imported_arg_i32_missing_err_one) + (if (imported_arg_i32_present_invalid_zero) + (if (imported_arg_u32_missing_err_one) + (if (imported_arg_u32_present_invalid_zero) + (if (imported_arg_i64_missing_err_one) + (if (imported_arg_i64_present_invalid_zero) + (if (imported_arg_u64_missing_err_one) + (if (imported_arg_u64_present_invalid_zero) + (if (imported_arg_f64_missing_err_one) + (if (imported_arg_f64_present_invalid_zero) + (if (imported_arg_bool_missing_err_one) + (if (imported_arg_bool_present_invalid_false) + (if (imported_arg_typed_options_ok) + (imported_arg_typed_custom_fallbacks_ok) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn main () -> i32 + (if (imported_process_facade_ok) + 42 + 1)) + +(test "explicit std process argc facade" + (imported_arg_count_positive)) + +(test "explicit std process arg result oob facade" + (imported_arg_result_oob_err_one)) + +(test "explicit std process arg option facade" + (imported_arg_option_flows_ok)) + +(test "explicit std process has arg facade" + (imported_has_arg_zero)) + +(test "explicit std process arg fallback missing facade" + (imported_arg_fallback_missing_ok)) + +(test "explicit std process arg fallback present facade" + (imported_arg_fallback_present_ok)) + +(test "explicit std process arg i32 missing facade" + (imported_arg_i32_missing_err_one)) + +(test "explicit std process arg i32 invalid fallback facade" + (imported_arg_i32_present_invalid_zero)) + +(test "explicit std process arg u32 missing facade" + (imported_arg_u32_missing_err_one)) + +(test "explicit std process arg u32 invalid fallback facade" + (imported_arg_u32_present_invalid_zero)) + +(test "explicit std process arg i64 missing facade" + (imported_arg_i64_missing_err_one)) + +(test "explicit std process arg i64 invalid fallback facade" + (imported_arg_i64_present_invalid_zero)) + +(test "explicit std process arg u64 missing facade" + (imported_arg_u64_missing_err_one)) + +(test "explicit std process arg u64 invalid fallback facade" + (imported_arg_u64_present_invalid_zero)) + +(test "explicit std process arg f64 missing facade" + (imported_arg_f64_missing_err_one)) + +(test "explicit std process arg f64 invalid fallback facade" + (imported_arg_f64_present_invalid_zero)) + +(test "explicit std process arg bool missing facade" + (imported_arg_bool_missing_err_one)) + +(test "explicit std process arg bool invalid fallback facade" + (imported_arg_bool_present_invalid_false)) + +(test "explicit std process typed option facade" + (imported_arg_typed_options_ok)) + +(test "explicit std process typed custom fallback facade" + (imported_arg_typed_custom_fallbacks_ok)) + +(test "explicit std process facade all" + (= (main) 42)) diff --git a/examples/projects/std-import-random/slovo.toml b/examples/projects/std-import-random/slovo.toml new file mode 100644 index 0000000..27260be --- /dev/null +++ b/examples/projects/std-import-random/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-import-random" +source_root = "src" +entry = "main" diff --git a/examples/projects/std-import-random/src/main.slo b/examples/projects/std-import-random/src/main.slo new file mode 100644 index 0000000..78d98ca --- /dev/null +++ b/examples/projects/std-import-random/src/main.slo @@ -0,0 +1,22 @@ +(module main) + +(import std.random (random_i32 random_i32_non_negative)) + +(fn imported_random_i32_non_negative () -> bool + (>= (random_i32) 0)) + +(fn imported_random_facade_ok () -> bool + (if (imported_random_i32_non_negative) + (random_i32_non_negative) + false)) + +(fn main () -> i32 + (if (imported_random_facade_ok) + 42 + 1)) + +(test "explicit std random i32 non-negative facade" + (imported_random_i32_non_negative)) + +(test "explicit std random facade all" + (= (main) 42)) diff --git a/examples/projects/std-import-result/slovo.toml b/examples/projects/std-import-result/slovo.toml new file mode 100644 index 0000000..3edb270 --- /dev/null +++ b/examples/projects/std-import-result/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-import-result" +source_root = "src" +entry = "main" diff --git a/examples/projects/std-import-result/src/main.slo b/examples/projects/std-import-result/src/main.slo new file mode 100644 index 0000000..48070ae --- /dev/null +++ b/examples/projects/std-import-result/src/main.slo @@ -0,0 +1,353 @@ +(module main) + +(import std.result (ok_i32 err_i32 is_ok_i32 is_err_i32 unwrap_ok_i32 unwrap_err_i32 unwrap_or_i32 ok_or_none_i32 ok_u32 err_u32 is_ok_u32 is_err_u32 unwrap_ok_u32 unwrap_err_u32 unwrap_or_u32 ok_or_none_u32 ok_i64 err_i64 is_err_i64 unwrap_err_i64 unwrap_or_i64 ok_or_none_i64 ok_u64 err_u64 is_ok_u64 is_err_u64 unwrap_ok_u64 unwrap_err_u64 unwrap_or_u64 ok_or_none_u64 ok_string err_string is_err_string unwrap_err_string unwrap_or_string ok_or_none_string ok_f64 err_f64 is_err_f64 unwrap_err_f64 unwrap_or_f64 ok_or_none_f64 ok_bool err_bool is_ok_bool is_err_bool unwrap_ok_bool unwrap_err_bool unwrap_or_bool ok_or_none_bool)) + +(fn i32_ok () -> (result i32 i32) + (ok_i32 42)) + +(fn i32_err () -> (result i32 i32) + (err_i32 5)) + +(fn i64_ok () -> (result i64 i32) + (ok_i64 60i64)) + +(fn i64_err () -> (result i64 i32) + (err_i64 6)) + +(fn u32_ok () -> (result u32 i32) + (ok_u32 42u32)) + +(fn u32_err () -> (result u32 i32) + (err_u32 8)) + +(fn u64_ok () -> (result u64 i32) + (ok_u64 4294967296u64)) + +(fn u64_err () -> (result u64 i32) + (err_u64 9)) + +(fn string_ok () -> (result string i32) + (ok_string "value")) + +(fn string_err () -> (result string i32) + (err_string 7)) + +(fn f64_ok () -> (result f64 i32) + (ok_f64 12.5)) + +(fn f64_err () -> (result f64 i32) + (err_f64 1)) + +(fn bool_ok () -> (result bool i32) + (ok_bool true)) + +(fn bool_false () -> (result bool i32) + (ok_bool false)) + +(fn bool_err () -> (result bool i32) + (err_bool 1)) + +(fn imported_i32_result_wrappers_ok () -> bool + (if (is_ok_i32 (i32_ok)) + (if (= (unwrap_ok_i32 (i32_ok)) 42) + (if (is_err_i32 (i32_err)) + (= (unwrap_err_i32 (i32_err)) 5) + false) + false) + false)) + +(fn imported_result_error_wrappers_ok () -> bool + (if (is_err_i64 (i64_err)) + (if (= (unwrap_err_i64 (i64_err)) 6) + (if (is_err_string (string_err)) + (if (= (unwrap_err_string (string_err)) 7) + (if (is_err_f64 (f64_err)) + (= (unwrap_err_f64 (f64_err)) 1) + false) + false) + false) + false) + false)) + +(fn imported_unwrap_or_i32_score () -> i32 + (+ (unwrap_or_i32 (i32_ok) 0) (unwrap_or_i32 (i32_err) 5))) + +(fn imported_ok_or_none_i32_ok () -> bool + (match (ok_or_none_i32 (i32_ok)) + ((some payload) + (= payload 42)) + ((none) + false))) + +(fn imported_ok_or_none_i32_none () -> bool + (match (ok_or_none_i32 (i32_err)) + ((some payload) + false) + ((none) + true))) + +(fn imported_unwrap_or_i64_score () -> i64 + (+ (unwrap_or_i64 (i64_ok) 0i64) (unwrap_or_i64 (i64_err) 5i64))) + +(fn imported_u32_result_wrappers_ok () -> bool + (if (is_ok_u32 (u32_ok)) + (if (= (unwrap_ok_u32 (u32_ok)) 42u32) + (if (is_err_u32 (u32_err)) + (= (unwrap_err_u32 (u32_err)) 8) + false) + false) + false)) + +(fn imported_unwrap_or_u32_score () -> u32 + (+ (unwrap_or_u32 (u32_ok) 0u32) (unwrap_or_u32 (u32_err) 5u32))) + +(fn imported_ok_or_none_u32_ok () -> bool + (match (ok_or_none_u32 (u32_ok)) + ((some payload) + (= payload 42u32)) + ((none) + false))) + +(fn imported_ok_or_none_u32_none () -> bool + (match (ok_or_none_u32 (u32_err)) + ((some payload) + false) + ((none) + true))) + +(fn imported_ok_or_none_i64_ok () -> bool + (match (ok_or_none_i64 (i64_ok)) + ((some payload) + (= payload 60i64)) + ((none) + false))) + +(fn imported_ok_or_none_i64_none () -> bool + (match (ok_or_none_i64 (i64_err)) + ((some payload) + false) + ((none) + true))) + +(fn imported_u64_result_wrappers_ok () -> bool + (if (is_ok_u64 (u64_ok)) + (if (= (unwrap_ok_u64 (u64_ok)) 4294967296u64) + (if (is_err_u64 (u64_err)) + (= (unwrap_err_u64 (u64_err)) 9) + false) + false) + false)) + +(fn imported_unwrap_or_u64_score () -> u64 + (+ (unwrap_or_u64 (u64_ok) 0u64) (unwrap_or_u64 (u64_err) 5u64))) + +(fn imported_ok_or_none_u64_ok () -> bool + (match (ok_or_none_u64 (u64_ok)) + ((some payload) + (= payload 4294967296u64)) + ((none) + false))) + +(fn imported_ok_or_none_u64_none () -> bool + (match (ok_or_none_u64 (u64_err)) + ((some payload) + false) + ((none) + true))) + +(fn imported_unwrap_or_string_ok () -> bool + (if (= (unwrap_or_string (string_ok) "fallback") "value") + (= (unwrap_or_string (string_err) "fallback") "fallback") + false)) + +(fn imported_ok_or_none_string_ok () -> bool + (match (ok_or_none_string (string_ok)) + ((some payload) + (= payload "value")) + ((none) + false))) + +(fn imported_ok_or_none_string_none () -> bool + (match (ok_or_none_string (string_err)) + ((some payload) + false) + ((none) + true))) + +(fn imported_unwrap_or_f64_score () -> f64 + (+ (unwrap_or_f64 (f64_ok) 0.0) (unwrap_or_f64 (f64_err) 2.5))) + +(fn imported_ok_or_none_f64_ok () -> bool + (match (ok_or_none_f64 (f64_ok)) + ((some payload) + (= payload 12.5)) + ((none) + false))) + +(fn imported_ok_or_none_f64_none () -> bool + (match (ok_or_none_f64 (f64_err)) + ((some payload) + false) + ((none) + true))) + +(fn imported_bool_result_helpers_ok () -> bool + (if (is_ok_bool (bool_ok)) + (if (unwrap_ok_bool (bool_ok)) + (if (is_err_bool (bool_err)) + (if (= (unwrap_err_bool (bool_err)) 1) + (if (unwrap_or_bool (bool_false) true) + false + (unwrap_or_bool (bool_err) true)) + false) + false) + false) + false)) + +(fn imported_ok_or_none_bool_ok () -> bool + (match (ok_or_none_bool (bool_ok)) + ((some payload) + payload) + ((none) + false))) + +(fn imported_ok_or_none_bool_none () -> bool + (match (ok_or_none_bool (bool_err)) + ((some payload) + false) + ((none) + true))) + +(fn imported_result_helpers_ok () -> bool + (if (imported_i32_result_wrappers_ok) + (if (imported_u32_result_wrappers_ok) + (if (imported_result_error_wrappers_ok) + (if (= (imported_unwrap_or_i32_score) 47) + (if (imported_ok_or_none_i32_ok) + (if (imported_ok_or_none_i32_none) + (if (= (imported_unwrap_or_u32_score) 47u32) + (if (imported_ok_or_none_u32_ok) + (if (imported_ok_or_none_u32_none) + (if (= (imported_unwrap_or_i64_score) 65i64) + (if (imported_ok_or_none_i64_ok) + (if (imported_ok_or_none_i64_none) + (if (imported_u64_result_wrappers_ok) + (if (= (imported_unwrap_or_u64_score) 4294967301u64) + (if (imported_ok_or_none_u64_ok) + (if (imported_ok_or_none_u64_none) + (if (imported_unwrap_or_string_ok) + (if (imported_ok_or_none_string_ok) + (if (imported_ok_or_none_string_none) + (if (= (imported_unwrap_or_f64_score) 15.0) + (if (imported_ok_or_none_f64_ok) + (if (imported_ok_or_none_f64_none) + (if (imported_bool_result_helpers_ok) + (if (imported_ok_or_none_bool_ok) + (imported_ok_or_none_bool_none) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn main () -> i32 + (if (imported_result_helpers_ok) + 42 + 1)) + +(test "explicit std result i32 wrappers" + (imported_i32_result_wrappers_ok)) + +(test "explicit std result err wrappers" + (imported_result_error_wrappers_ok)) + +(test "explicit std result unwrap_or i32" + (= (imported_unwrap_or_i32_score) 47)) + +(test "explicit std result ok_or_none i32 ok" + (imported_ok_or_none_i32_ok)) + +(test "explicit std result ok_or_none i32 none" + (imported_ok_or_none_i32_none)) + +(test "explicit std result u32 wrappers" + (imported_u32_result_wrappers_ok)) + +(test "explicit std result unwrap_or u32" + (= (imported_unwrap_or_u32_score) 47u32)) + +(test "explicit std result ok_or_none u32 ok" + (imported_ok_or_none_u32_ok)) + +(test "explicit std result ok_or_none u32 none" + (imported_ok_or_none_u32_none)) + +(test "explicit std result unwrap_or i64" + (= (imported_unwrap_or_i64_score) 65i64)) + +(test "explicit std result ok_or_none i64 ok" + (imported_ok_or_none_i64_ok)) + +(test "explicit std result ok_or_none i64 none" + (imported_ok_or_none_i64_none)) + +(test "explicit std result u64 wrappers" + (imported_u64_result_wrappers_ok)) + +(test "explicit std result unwrap_or u64" + (= (imported_unwrap_or_u64_score) 4294967301u64)) + +(test "explicit std result ok_or_none u64 ok" + (imported_ok_or_none_u64_ok)) + +(test "explicit std result ok_or_none u64 none" + (imported_ok_or_none_u64_none)) + +(test "explicit std result unwrap_or string" + (imported_unwrap_or_string_ok)) + +(test "explicit std result ok_or_none string ok" + (imported_ok_or_none_string_ok)) + +(test "explicit std result ok_or_none string none" + (imported_ok_or_none_string_none)) + +(test "explicit std result unwrap_or f64" + (= (imported_unwrap_or_f64_score) 15.0)) + +(test "explicit std result ok_or_none f64 ok" + (imported_ok_or_none_f64_ok)) + +(test "explicit std result ok_or_none f64 none" + (imported_ok_or_none_f64_none)) + +(test "explicit std result bool helpers" + (imported_bool_result_helpers_ok)) + +(test "explicit std result ok_or_none bool ok" + (imported_ok_or_none_bool_ok)) + +(test "explicit std result ok_or_none bool none" + (imported_ok_or_none_bool_none)) + +(test "explicit std result helpers all" + (= (main) 42)) diff --git a/examples/projects/std-import-string/slovo.toml b/examples/projects/std-import-string/slovo.toml new file mode 100644 index 0000000..af442f5 --- /dev/null +++ b/examples/projects/std-import-string/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-import-string" +source_root = "src" +entry = "main" diff --git a/examples/projects/std-import-string/src/main.slo b/examples/projects/std-import-string/src/main.slo new file mode 100644 index 0000000..5ae4c9f --- /dev/null +++ b/examples/projects/std-import-string/src/main.slo @@ -0,0 +1,196 @@ +(module main) + +(import std.string (len concat parse_i32_result parse_i32_option parse_u32_result parse_u32_option parse_i64_result parse_i64_option parse_u64_result parse_u64_option parse_f64_result parse_f64_option parse_bool_result parse_bool_option parse_i32_or_zero parse_u32_or_zero parse_i64_or_zero parse_u64_or_zero parse_f64_or_zero parse_bool_or_false parse_i32_or parse_u32_or parse_i64_or parse_u64_or parse_f64_or parse_bool_or)) + +(fn imported_string_concat () -> string + (concat "slo" "vo")) + +(fn imported_string_len_concat_score () -> i32 + (+ (len (imported_string_concat)) 37)) + +(fn imported_string_parse_result_ok () -> bool + (if (= (unwrap_ok (parse_i32_result "40")) 40) + (if (= (unwrap_ok (parse_u32_result "40")) 40u32) + (if (= (unwrap_ok (parse_i64_result "40")) 40i64) + (if (= (unwrap_ok (parse_u64_result "40")) 40u64) + (if (= (unwrap_ok (parse_f64_result "40.0")) 40.0) + (imported_string_parse_bool_ok) + false) + false) + false) + false) + false)) + +(fn imported_string_parse_bool_ok () -> bool + (if (unwrap_ok (parse_bool_result "true")) + (= (unwrap_err (parse_bool_result "TRUE")) 1) + false)) + +(fn imported_string_parse_options_ok () -> bool + (if (match (parse_i32_option "40") + ((some payload) + (= payload 40)) + ((none) + false)) + (if (match (parse_i32_option "bad") + ((some payload) + false) + ((none) + true)) + (if (match (parse_u32_option "40") + ((some payload) + (= payload 40u32)) + ((none) + false)) + (if (match (parse_u32_option "bad") + ((some payload) + false) + ((none) + true)) + (if (match (parse_i64_option "40") + ((some payload) + (= payload 40i64)) + ((none) + false)) + (if (match (parse_i64_option "bad") + ((some payload) + false) + ((none) + true)) + (if (match (parse_u64_option "40") + ((some payload) + (= payload 40u64)) + ((none) + false)) + (if (match (parse_u64_option "bad") + ((some payload) + false) + ((none) + true)) + (if (match (parse_f64_option "40.0") + ((some payload) + (= payload 40.0)) + ((none) + false)) + (if (match (parse_f64_option "bad") + ((some payload) + false) + ((none) + true)) + (if (match (parse_bool_option "true") + ((some payload) + payload) + ((none) + false)) + (match (parse_bool_option "bad") + ((some payload) + false) + ((none) + true)) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_string_parse_integer_fallbacks_ok () -> bool + (if (= (parse_i32_or_zero "40") 40) + (if (= (parse_i32_or_zero "bad") 0) + (if (= (parse_u32_or_zero "40") 40u32) + (if (= (parse_u32_or_zero "bad") 0u32) + (if (= (parse_i64_or_zero "40") 40i64) + (if (= (parse_i64_or_zero "bad") 0i64) + (if (= (parse_u64_or_zero "40") 40u64) + (= (parse_u64_or_zero "bad") 0u64) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_string_parse_float_bool_fallbacks_ok () -> bool + (if (= (parse_f64_or_zero "40.0") 40.0) + (if (= (parse_f64_or_zero "bad") 0.0) + (if (parse_bool_or_false "true") + (if (parse_bool_or_false "bad") + false + (if (parse_bool_or_false "false") + false + true)) + false) + false) + false)) + +(fn imported_string_parse_custom_fallbacks_ok () -> bool + (if (= (parse_i32_or "40" 7) 40) + (if (= (parse_i32_or "bad" 7) 7) + (if (= (parse_u32_or "40" 9u32) 40u32) + (if (= (parse_u32_or "bad" 9u32) 9u32) + (if (= (parse_i64_or "40" 9i64) 40i64) + (if (= (parse_i64_or "bad" 9i64) 9i64) + (if (= (parse_u64_or "40" 11u64) 40u64) + (if (= (parse_u64_or "bad" 11u64) 11u64) + (if (= (parse_f64_or "40.0" 1.5) 40.0) + (if (= (parse_f64_or "bad" 1.5) 1.5) + (if (parse_bool_or "true" false) + (if (parse_bool_or "bad" true) + true + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_string_helpers_ok () -> bool + (if (= (imported_string_len_concat_score) 42) + (if (imported_string_parse_result_ok) + (if (imported_string_parse_options_ok) + (if (imported_string_parse_integer_fallbacks_ok) + (if (imported_string_parse_float_bool_fallbacks_ok) + (imported_string_parse_custom_fallbacks_ok) + false) + false) + false) + false) + false)) + +(fn main () -> i32 + (if (imported_string_helpers_ok) + 42 + 1)) + +(test "explicit std string len concat" + (= (imported_string_len_concat_score) 42)) + +(test "explicit std string parse result wrappers" + (imported_string_parse_result_ok)) + +(test "explicit std string parse option wrappers" + (imported_string_parse_options_ok)) + +(test "explicit std string parse integer fallbacks" + (imported_string_parse_integer_fallbacks_ok)) + +(test "explicit std string parse float bool fallbacks" + (imported_string_parse_float_bool_fallbacks_ok)) + +(test "explicit std string parse custom fallbacks" + (imported_string_parse_custom_fallbacks_ok)) + +(test "explicit std string helpers all" + (= (main) 42)) diff --git a/examples/projects/std-import-time/slovo.toml b/examples/projects/std-import-time/slovo.toml new file mode 100644 index 0000000..ef9e4ca --- /dev/null +++ b/examples/projects/std-import-time/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-import-time" +source_root = "src" +entry = "main" diff --git a/examples/projects/std-import-time/src/main.slo b/examples/projects/std-import-time/src/main.slo new file mode 100644 index 0000000..596149c --- /dev/null +++ b/examples/projects/std-import-time/src/main.slo @@ -0,0 +1,28 @@ +(module main) + +(import std.time (monotonic_ms sleep_ms_zero)) + +(fn imported_time_monotonic_non_negative () -> bool + (>= (monotonic_ms) 0)) + +(fn imported_time_sleep_zero_score () -> i32 + (+ (sleep_ms_zero) 42)) + +(fn imported_time_facade_ok () -> bool + (if (imported_time_monotonic_non_negative) + (= (imported_time_sleep_zero_score) 42) + false)) + +(fn main () -> i32 + (if (imported_time_facade_ok) + 42 + 1)) + +(test "explicit std time monotonic facade" + (imported_time_monotonic_non_negative)) + +(test "explicit std time sleep zero facade" + (= (imported_time_sleep_zero_score) 42)) + +(test "explicit std time facade all" + (= (main) 42)) diff --git a/examples/projects/std-import-vec_bool/slovo.toml b/examples/projects/std-import-vec_bool/slovo.toml new file mode 100644 index 0000000..d071d06 --- /dev/null +++ b/examples/projects/std-import-vec_bool/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-import-vec-bool" +source_root = "src" +entry = "main" diff --git a/examples/projects/std-import-vec_bool/src/main.slo b/examples/projects/std-import-vec_bool/src/main.slo new file mode 100644 index 0000000..b7af2d1 --- /dev/null +++ b/examples/projects/std-import-vec_bool/src/main.slo @@ -0,0 +1,382 @@ +(module main) + +(import std.vec_bool (empty append len at singleton append2 append3 pair triple is_empty index_or first_or last_or index_option first_option last_option index_of_option last_index_of_option contains count_of concat take starts_with without_prefix ends_with without_suffix drop reverse subvec insert_at insert_range replace_at replace_range remove_at remove_range)) + +(fn imported_empty_length () -> i32 + (len (empty))) + +(fn imported_pair_index () -> bool + (at (pair true false) 1)) + +(fn original_len_after_append () -> i32 + (let values (vec bool) (empty)) + (let more (vec bool) (append values true)) + (len values)) + +(fn option_bool_is_some_value ((value (option bool)) (expected bool)) -> bool + (if (is_some value) + (= (unwrap_some value) expected) + false)) + +(fn option_i32_is_some_value ((value (option i32)) (expected i32)) -> bool + (if (is_some value) + (= (unwrap_some value) expected) + false)) + +(fn imported_builder_helpers_ok () -> bool + (if (= (len (singleton true)) 1) + (if (= (len (append2 (empty) true false)) 2) + (if (= (len (append3 (singleton true) false true false)) 4) + (if (= (pair true false) (append2 (empty) true false)) + (= (triple true false true) (append3 (empty) true false true)) + false) + false) + false) + false)) + +(fn imported_query_helpers_ok () -> bool + (if (is_empty (empty)) + (if (= (first_or (empty) true) true) + (if (= (first_or (pair false true) true) false) + (if (= (last_or (triple true false false) true) false) + (if (= (index_or (pair true false) -1 true) true) + (if (= (index_or (pair true false) 9 true) true) + (= (index_or (pair true false) 1 true) false) + false) + false) + false) + false) + false) + false)) + +(fn imported_option_query_helpers_ok () -> bool + (let repeated (vec bool) (append3 (pair true false) true false true)) + (if (is_none (first_option (empty))) + (if (option_bool_is_some_value (first_option (pair true false)) true) + (if (is_none (last_option (empty))) + (if (option_bool_is_some_value (last_option (triple true false false)) false) + (if (is_none (index_option (pair true false) -1)) + (if (is_none (index_option (pair true false) 9)) + (if (option_bool_is_some_value (index_option (pair true false) 1) false) + (if (is_none (index_of_option (empty) true)) + (if (option_i32_is_some_value (index_of_option repeated false) 1) + (if (is_none (last_index_of_option (empty) false)) + (option_i32_is_some_value (last_index_of_option repeated false) 3) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_starts_with_helper_ok () -> bool + (let values (vec bool) (append2 (triple true false true) false true)) + (let prefix (vec bool) (pair true false)) + (let longer_prefix (vec bool) (append2 values true false)) + (let mismatched_prefix (vec bool) (pair false true)) + (if (starts_with values (empty)) + (if (starts_with values prefix) + (if (starts_with values values) + (if (starts_with values longer_prefix) + false + (if (starts_with values mismatched_prefix) + false + (if (= values (append2 (triple true false true) false true)) + (= prefix (pair true false)) + false))) + false) + false) + false)) + +(fn imported_ends_with_helper_ok () -> bool + (let values (vec bool) (append2 (triple true false true) false true)) + (let suffix (vec bool) (pair false true)) + (let longer_suffix (vec bool) (append2 values true false)) + (let mismatched_suffix (vec bool) (pair false false)) + (if (ends_with values (empty)) + (if (ends_with values suffix) + (if (ends_with values values) + (if (ends_with values longer_suffix) + false + (if (ends_with values mismatched_suffix) + false + (if (= values (append2 (triple true false true) false true)) + (= suffix (pair false true)) + false))) + false) + false) + false)) + +(fn imported_without_suffix_helper_ok () -> bool + (let values (vec bool) (append2 (triple true false true) false true)) + (let suffix (vec bool) (pair false true)) + (let longer_suffix (vec bool) (append2 values true false)) + (let mismatched_suffix (vec bool) (pair false false)) + (if (= (without_suffix values (empty)) values) + (if (= (without_suffix values values) (empty)) + (if (= (without_suffix values suffix) (triple true false true)) + (if (= (without_suffix values longer_suffix) values) + (if (= (without_suffix values mismatched_suffix) values) + (if (= values (append2 (triple true false true) false true)) + (= suffix (pair false true)) + false) + false) + false) + false) + false) + false)) + +(fn imported_without_prefix_helper_ok () -> bool + (let values (vec bool) (append2 (triple true false true) false true)) + (let prefix (vec bool) (pair true false)) + (let longer_prefix (vec bool) (append2 values true false)) + (let mismatched_prefix (vec bool) (pair true true)) + (if (= (without_prefix values (empty)) values) + (if (= (without_prefix values values) (empty)) + (if (= (without_prefix values prefix) (triple true false true)) + (if (= (without_prefix values longer_prefix) values) + (if (= (without_prefix values mismatched_prefix) values) + (if (= values (append2 (triple true false true) false true)) + (= prefix (pair true false)) + false) + false) + false) + false) + false) + false)) + +(fn imported_transform_helpers_ok () -> bool + (if (= (concat (empty) (pair true false)) (pair true false)) + (if (= (concat (pair true false) (triple false true false)) (append3 (pair true false) false true false)) + (if (= (take (triple true false true) -4) (empty)) + (if (= (take (triple true false true) 2) (pair true false)) + (if (= (take (pair true false) 9) (pair true false)) + (if (= (drop (triple true false true) -4) (triple true false true)) + (if (= (drop (triple true false true) 2) (singleton true)) + (if (= (drop (pair true false) 9) (empty)) + (if (= (reverse (empty)) (empty)) + (= (reverse (triple true false false)) (triple false false true)) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_subvec_helper_ok () -> bool + (let original (vec bool) (append2 (triple true false true) false true)) + (if (= (subvec original -1 2) (empty)) + (if (= (subvec original 3 3) (empty)) + (if (= (subvec original 9 10) (empty)) + (if (= (subvec original 2 99) (triple true false true)) + (if (= (subvec original 1 4) (triple false true false)) + (= original (append2 (triple true false true) false true)) + false) + false) + false) + false) + false)) + +(fn imported_insert_helper_ok () -> bool + (let values (vec bool) (triple true false true)) + (if (= (insert_at values 1 false) (append2 (pair true false) false true)) + (if (= (insert_at values 3 false) (append values false)) + (if (= values (triple true false true)) + (if (= (insert_at values -1 false) values) + (= (insert_at values 4 false) values) + false) + false) + false) + false)) + +(fn imported_insert_range_helper_ok () -> bool + (let values (vec bool) (append2 (triple true false true) false true)) + (let inserted (vec bool) (pair false false)) + (if (= (insert_range values -1 inserted) values) + (if (= (insert_range values 2 inserted) (append3 (append2 (pair true false) false false) true false true)) + (if (= (insert_range values 5 inserted) (append2 (append2 (triple true false true) false true) false false)) + (if (= values (append2 (triple true false true) false true)) + (if (= inserted (pair false false)) + (= (insert_range values 6 inserted) values) + false) + false) + false) + false) + false)) + +(fn imported_replace_helper_ok () -> bool + (if (= (replace_at (triple true false true) 1 true) (triple true true true)) + (if (= (replace_at (triple true false true) -1 true) (triple true false true)) + (= (replace_at (pair true false) 2 true) (pair true false)) + false) + false)) + +(fn imported_replace_range_helper_ok () -> bool + (let original (vec bool) (append2 (triple true false true) false true)) + (let replacement (vec bool) (pair false false)) + (if (= (replace_range original -1 2 replacement) original) + (if (= (replace_range original 3 3 replacement) original) + (if (= (replace_range original 4 2 replacement) original) + (if (= (replace_range original 5 9 replacement) original) + (if (= (replace_range original 2 99 replacement) (append2 (pair true false) false false)) + (if (= (replace_range original 1 4 replacement) (append2 (pair true false) false true)) + (if (= original (append2 (triple true false true) false true)) + (= replacement (pair false false)) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_remove_helper_ok () -> bool + (let original (vec bool) (triple true false true)) + (let removed_middle (vec bool) (remove_at original 1)) + (if (= removed_middle (pair true true)) + (if (= (len removed_middle) 2) + (if (= (remove_at original 0) (pair false true)) + (if (= (remove_at original 2) (pair true false)) + (if (= original (triple true false true)) + (if (= (remove_at original -1) original) + (= (remove_at original 3) original) + false) + false) + false) + false) + false) + false)) + +(fn imported_remove_range_helper_ok () -> bool + (let original (vec bool) (append2 (triple true false true) false true)) + (if (= (remove_range original -1 2) original) + (if (= (remove_range original 3 3) original) + (if (= (remove_range original 4 2) original) + (if (= (remove_range original 5 9) original) + (if (= (remove_range original 2 99) (pair true false)) + (if (= (remove_range original 1 4) (pair true true)) + (= original (append2 (triple true false true) false true)) + false) + false) + false) + false) + false) + false)) + +(fn imported_real_program_helpers_ok () -> bool + (let values (vec bool) (append2 (triple true false true) true true)) + (if (= (original_len_after_append) 0) + (if (contains values true) + (if (contains values false) + (if (= (count_of values true) 4) + (= (count_of values false) 1) + false) + false) + false) + false)) + +(fn helpers_all_ok () -> bool + (if (= (imported_empty_length) 0) + (if (= (imported_pair_index) false) + (if (imported_builder_helpers_ok) + (if (imported_query_helpers_ok) + (if (imported_option_query_helpers_ok) + (if (imported_starts_with_helper_ok) + (if (imported_ends_with_helper_ok) + (if (imported_without_suffix_helper_ok) + (if (imported_without_prefix_helper_ok) + (if (imported_transform_helpers_ok) + (if (imported_subvec_helper_ok) + (if (imported_insert_helper_ok) + (if (imported_insert_range_helper_ok) + (if (imported_replace_helper_ok) + (if (imported_replace_range_helper_ok) + (if (imported_remove_helper_ok) + (if (imported_remove_range_helper_ok) + (imported_real_program_helpers_ok) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(test "explicit std vec_bool empty len facade" + (= (imported_empty_length) 0)) + +(test "explicit std vec_bool direct at facade" + (= (imported_pair_index) false)) + +(test "explicit std vec_bool builder helpers" + (imported_builder_helpers_ok)) + +(test "explicit std vec_bool query helpers" + (imported_query_helpers_ok)) + +(test "explicit std vec_bool option query helpers" + (imported_option_query_helpers_ok)) + +(test "explicit std vec_bool starts_with helper" + (imported_starts_with_helper_ok)) + +(test "explicit std vec_bool ends_with helper" + (imported_ends_with_helper_ok)) + +(test "explicit std vec_bool without_suffix helper" + (imported_without_suffix_helper_ok)) + +(test "explicit std vec_bool without_prefix helper" + (imported_without_prefix_helper_ok)) + +(test "explicit std vec_bool transform helpers" + (imported_transform_helpers_ok)) + +(test "explicit std vec_bool subvec helper" + (imported_subvec_helper_ok)) + +(test "explicit std vec_bool insert helper" + (imported_insert_helper_ok)) + +(test "explicit std vec_bool insert range helper" + (imported_insert_range_helper_ok)) + +(test "explicit std vec_bool replace helper" + (imported_replace_helper_ok)) + +(test "explicit std vec_bool replace range helper" + (imported_replace_range_helper_ok)) + +(test "explicit std vec_bool remove helper" + (imported_remove_helper_ok)) + +(test "explicit std vec_bool remove range helper" + (imported_remove_range_helper_ok)) + +(test "explicit std vec_bool real program helpers" + (imported_real_program_helpers_ok)) + +(test "explicit std vec_bool helpers all" + (helpers_all_ok)) + +(fn main () -> i32 + (if (helpers_all_ok) + 42 + 1)) diff --git a/examples/projects/std-import-vec_f64/slovo.toml b/examples/projects/std-import-vec_f64/slovo.toml new file mode 100644 index 0000000..6f6e906 --- /dev/null +++ b/examples/projects/std-import-vec_f64/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-import-vec-f64" +source_root = "src" +entry = "main" diff --git a/examples/projects/std-import-vec_f64/src/main.slo b/examples/projects/std-import-vec_f64/src/main.slo new file mode 100644 index 0000000..e4a49dd --- /dev/null +++ b/examples/projects/std-import-vec_f64/src/main.slo @@ -0,0 +1,379 @@ +(module main) + +(import std.vec_f64 (empty append len at singleton append2 append3 pair triple is_empty index_or first_or last_or index_option first_option last_option index_of_option last_index_of_option contains sum concat take starts_with without_prefix ends_with without_suffix drop reverse subvec insert_at insert_range replace_at replace_range remove_at remove_range)) + +(fn imported_empty_length () -> i32 + (len (empty))) + +(fn imported_pair_index () -> f64 + (at (pair 40.0 41.0) 1)) + +(fn original_len_after_append () -> i32 + (let values (vec f64) (empty)) + (let more (vec f64) (append values 9.0)) + (len values)) + +(fn option_f64_is_some_value ((value (option f64)) (expected f64)) -> bool + (if (is_some value) + (= (unwrap_some value) expected) + false)) + +(fn option_i32_is_some_value ((value (option i32)) (expected i32)) -> bool + (if (is_some value) + (= (unwrap_some value) expected) + false)) + +(fn imported_builder_helpers_ok () -> bool + (if (= (len (singleton 42.0)) 1) + (if (= (len (append2 (empty) 10.0 20.0)) 2) + (if (= (len (append3 (singleton 10.0) 20.0 30.0 40.0)) 4) + (if (= (pair 5.0 6.0) (append2 (empty) 5.0 6.0)) + (= (triple 7.0 8.0 9.0) (append3 (empty) 7.0 8.0 9.0)) + false) + false) + false) + false)) + +(fn imported_query_helpers_ok () -> bool + (if (is_empty (empty)) + (if (= (first_or (empty) 9.0) 9.0) + (if (= (first_or (pair 40.0 41.0) 9.0) 40.0) + (if (= (last_or (triple 10.0 20.0 30.0) 9.0) 30.0) + (if (= (index_or (pair 40.0 41.0) -1 7.0) 7.0) + (if (= (index_or (pair 40.0 41.0) 9 7.0) 7.0) + (= (index_or (pair 40.0 41.0) 1 7.0) 41.0) + false) + false) + false) + false) + false) + false)) + +(fn imported_option_query_helpers_ok () -> bool + (if (is_none (first_option (empty))) + (if (option_f64_is_some_value (first_option (pair 40.0 41.0)) 40.0) + (if (is_none (last_option (empty))) + (if (option_f64_is_some_value (last_option (triple 10.0 20.0 30.0)) 30.0) + (if (is_none (index_option (pair 40.0 41.0) -1)) + (if (is_none (index_option (pair 40.0 41.0) 9)) + (if (option_f64_is_some_value (index_option (pair 40.0 41.0) 1) 41.0) + (if (is_none (index_of_option (append3 (pair 10.0 20.0) 30.0 20.0 10.0) 99.0)) + (if (option_i32_is_some_value (index_of_option (append3 (pair 10.0 20.0) 30.0 20.0 10.0) 20.0) 1) + (if (is_none (last_index_of_option (append3 (pair 10.0 20.0) 30.0 20.0 10.0) 99.0)) + (option_i32_is_some_value (last_index_of_option (append3 (pair 10.0 20.0) 30.0 20.0 10.0) 20.0) 3) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_starts_with_helper_ok () -> bool + (let values (vec f64) (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + (let prefix (vec f64) (pair 10.0 20.0)) + (let longer_prefix (vec f64) (append2 values 60.0 70.0)) + (let mismatched_prefix (vec f64) (pair 20.0 30.0)) + (if (starts_with values (empty)) + (if (starts_with values prefix) + (if (starts_with values values) + (if (starts_with values longer_prefix) + false + (if (starts_with values mismatched_prefix) + false + (if (= values (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + (= prefix (pair 10.0 20.0)) + false))) + false) + false) + false)) + +(fn imported_ends_with_helper_ok () -> bool + (let values (vec f64) (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + (let suffix (vec f64) (pair 40.0 50.0)) + (let longer_suffix (vec f64) (append2 values 60.0 70.0)) + (let mismatched_suffix (vec f64) (pair 40.0 51.0)) + (if (ends_with values (empty)) + (if (ends_with values suffix) + (if (ends_with values values) + (if (ends_with values longer_suffix) + false + (if (ends_with values mismatched_suffix) + false + (if (= values (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + (= suffix (pair 40.0 50.0)) + false))) + false) + false) + false)) + +(fn imported_without_suffix_helper_ok () -> bool + (let values (vec f64) (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + (let suffix (vec f64) (pair 40.0 50.0)) + (let longer_suffix (vec f64) (append2 values 60.0 70.0)) + (let mismatched_suffix (vec f64) (pair 40.0 51.0)) + (if (= (without_suffix values (empty)) values) + (if (= (without_suffix values values) (empty)) + (if (= (without_suffix values suffix) (triple 10.0 20.0 30.0)) + (if (= (without_suffix values longer_suffix) values) + (if (= (without_suffix values mismatched_suffix) values) + (if (= values (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + (= suffix (pair 40.0 50.0)) + false) + false) + false) + false) + false) + false)) + +(fn imported_without_prefix_helper_ok () -> bool + (let values (vec f64) (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + (let prefix (vec f64) (pair 10.0 20.0)) + (let longer_prefix (vec f64) (append2 values 60.0 70.0)) + (let mismatched_prefix (vec f64) (pair 10.0 21.0)) + (if (= (without_prefix values (empty)) values) + (if (= (without_prefix values values) (empty)) + (if (= (without_prefix values prefix) (triple 30.0 40.0 50.0)) + (if (= (without_prefix values longer_prefix) values) + (if (= (without_prefix values mismatched_prefix) values) + (if (= values (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + (= prefix (pair 10.0 20.0)) + false) + false) + false) + false) + false) + false)) + +(fn imported_transform_helpers_ok () -> bool + (if (= (concat (empty) (pair 7.0 8.0)) (pair 7.0 8.0)) + (if (= (concat (pair 1.0 2.0) (triple 3.0 4.0 5.0)) (append3 (pair 1.0 2.0) 3.0 4.0 5.0)) + (if (= (take (triple 10.0 20.0 30.0) -4) (empty)) + (if (= (take (triple 10.0 20.0 30.0) 2) (pair 10.0 20.0)) + (if (= (take (pair 10.0 20.0) 9) (pair 10.0 20.0)) + (if (= (drop (triple 10.0 20.0 30.0) -4) (triple 10.0 20.0 30.0)) + (if (= (drop (triple 10.0 20.0 30.0) 2) (singleton 30.0)) + (if (= (drop (pair 10.0 20.0) 9) (empty)) + (if (= (reverse (empty)) (empty)) + (= (reverse (append3 (pair 10.0 20.0) 30.0 40.0 50.0)) (append3 (pair 50.0 40.0) 30.0 20.0 10.0)) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_subvec_helper_ok () -> bool + (let original (vec f64) (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + (if (= (subvec original -1 2) (empty)) + (if (= (subvec original 3 3) (empty)) + (if (= (subvec original 9 10) (empty)) + (if (= (subvec original 2 99) (triple 30.0 40.0 50.0)) + (if (= (subvec original 1 4) (triple 20.0 30.0 40.0)) + (= original (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + false) + false) + false) + false) + false)) + +(fn imported_insert_helper_ok () -> bool + (let values (vec f64) (triple 10.0 20.0 30.0)) + (if (= (insert_at values 1 99.0) (append2 (pair 10.0 99.0) 20.0 30.0)) + (if (= (insert_at values 3 99.0) (append values 99.0)) + (if (= values (triple 10.0 20.0 30.0)) + (if (= (insert_at values -1 99.0) values) + (= (insert_at values 4 99.0) values) + false) + false) + false) + false)) + +(fn imported_insert_range_helper_ok () -> bool + (let values (vec f64) (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + (let inserted (vec f64) (pair 77.0 88.0)) + (if (= (insert_range values -1 inserted) values) + (if (= (insert_range values 2 inserted) (append3 (append2 (pair 10.0 20.0) 77.0 88.0) 30.0 40.0 50.0)) + (if (= (insert_range values 5 inserted) (append2 (append2 (triple 10.0 20.0 30.0) 40.0 50.0) 77.0 88.0)) + (if (= values (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + (if (= inserted (pair 77.0 88.0)) + (= (insert_range values 6 inserted) values) + false) + false) + false) + false) + false)) + +(fn imported_replace_helper_ok () -> bool + (if (= (replace_at (triple 10.0 20.0 30.0) 1 99.0) (triple 10.0 99.0 30.0)) + (if (= (replace_at (triple 10.0 20.0 30.0) -1 99.0) (triple 10.0 20.0 30.0)) + (= (replace_at (pair 10.0 20.0) 2 99.0) (pair 10.0 20.0)) + false) + false)) + +(fn imported_replace_range_helper_ok () -> bool + (let original (vec f64) (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + (let replacement (vec f64) (pair 77.0 88.0)) + (if (= (replace_range original -1 2 replacement) original) + (if (= (replace_range original 3 3 replacement) original) + (if (= (replace_range original 4 2 replacement) original) + (if (= (replace_range original 5 9 replacement) original) + (if (= (replace_range original 2 99 replacement) (append2 (pair 10.0 20.0) 77.0 88.0)) + (if (= (replace_range original 1 4 replacement) (append2 (pair 10.0 77.0) 88.0 50.0)) + (if (= original (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + (= replacement (pair 77.0 88.0)) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_remove_helper_ok () -> bool + (let original (vec f64) (triple 10.0 20.0 30.0)) + (let removed_middle (vec f64) (remove_at original 1)) + (if (= removed_middle (pair 10.0 30.0)) + (if (= (len removed_middle) 2) + (if (= (remove_at original 0) (pair 20.0 30.0)) + (if (= (remove_at original 2) (pair 10.0 20.0)) + (if (= original (triple 10.0 20.0 30.0)) + (if (= (remove_at original -1) original) + (= (remove_at original 3) original) + false) + false) + false) + false) + false) + false)) + +(fn imported_remove_range_helper_ok () -> bool + (let original (vec f64) (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + (if (= (remove_range original -1 2) original) + (if (= (remove_range original 3 3) original) + (if (= (remove_range original 4 2) original) + (if (= (remove_range original 5 9) original) + (if (= (remove_range original 2 99) (pair 10.0 20.0)) + (if (= (remove_range original 1 4) (pair 10.0 50.0)) + (= original (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + false) + false) + false) + false) + false) + false)) + +(fn imported_real_program_helpers_ok () -> bool + (let values (vec f64) (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + (if (= (original_len_after_append) 0) + (if (contains values 20.0) + (if (contains values 99.0) + false + (= (sum values) 150.0)) + false) + false)) + +(fn helpers_all_ok () -> bool + (if (= (imported_empty_length) 0) + (if (= (imported_pair_index) 41.0) + (if (imported_builder_helpers_ok) + (if (imported_query_helpers_ok) + (if (imported_option_query_helpers_ok) + (if (imported_starts_with_helper_ok) + (if (imported_ends_with_helper_ok) + (if (imported_without_suffix_helper_ok) + (if (imported_without_prefix_helper_ok) + (if (imported_transform_helpers_ok) + (if (imported_subvec_helper_ok) + (if (imported_insert_helper_ok) + (if (imported_insert_range_helper_ok) + (if (imported_replace_helper_ok) + (if (imported_replace_range_helper_ok) + (if (imported_remove_helper_ok) + (if (imported_remove_range_helper_ok) + (imported_real_program_helpers_ok) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(test "explicit std vec_f64 empty len facade" + (= (imported_empty_length) 0)) + +(test "explicit std vec_f64 direct at facade" + (= (imported_pair_index) 41.0)) + +(test "explicit std vec_f64 builder helpers" + (imported_builder_helpers_ok)) + +(test "explicit std vec_f64 query helpers" + (imported_query_helpers_ok)) + +(test "explicit std vec_f64 option query helpers" + (imported_option_query_helpers_ok)) + +(test "explicit std vec_f64 starts_with helper" + (imported_starts_with_helper_ok)) + +(test "explicit std vec_f64 ends_with helper" + (imported_ends_with_helper_ok)) + +(test "explicit std vec_f64 without_suffix helper" + (imported_without_suffix_helper_ok)) + +(test "explicit std vec_f64 without_prefix helper" + (imported_without_prefix_helper_ok)) + +(test "explicit std vec_f64 transform helpers" + (imported_transform_helpers_ok)) + +(test "explicit std vec_f64 subvec helper" + (imported_subvec_helper_ok)) + +(test "explicit std vec_f64 insert helper" + (imported_insert_helper_ok)) + +(test "explicit std vec_f64 insert range helper" + (imported_insert_range_helper_ok)) + +(test "explicit std vec_f64 replace helper" + (imported_replace_helper_ok)) + +(test "explicit std vec_f64 replace range helper" + (imported_replace_range_helper_ok)) + +(test "explicit std vec_f64 remove helper" + (imported_remove_helper_ok)) + +(test "explicit std vec_f64 remove range helper" + (imported_remove_range_helper_ok)) + +(test "explicit std vec_f64 real program helpers" + (imported_real_program_helpers_ok)) + +(test "explicit std vec_f64 helpers all" + (helpers_all_ok)) + +(fn main () -> i32 + (if (helpers_all_ok) + 42 + 1)) diff --git a/examples/projects/std-import-vec_i32/slovo.toml b/examples/projects/std-import-vec_i32/slovo.toml new file mode 100644 index 0000000..679ebd8 --- /dev/null +++ b/examples/projects/std-import-vec_i32/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-import-vec-i32" +source_root = "src" +entry = "main" diff --git a/examples/projects/std-import-vec_i32/src/main.slo b/examples/projects/std-import-vec_i32/src/main.slo new file mode 100644 index 0000000..6546e21 --- /dev/null +++ b/examples/projects/std-import-vec_i32/src/main.slo @@ -0,0 +1,415 @@ +(module main) + +(import std.vec_i32 (empty append len at singleton append2 append3 pair triple repeat range range_from_zero is_empty index_or first_or last_or index_option first_option last_option index_of_option last_index_of_option count_of contains sum concat take starts_with without_prefix ends_with without_suffix drop reverse subvec insert_at insert_range replace_at replace_range remove_at remove_range)) + +(fn imported_empty_length () -> i32 + (len (empty))) + +(fn imported_pair_index () -> i32 + (at (pair 40 41) 1)) + +(fn imported_builder_helpers_ok () -> bool + (if (= (len (singleton 42)) 1) + (if (= (len (append2 (empty) 10 20)) 2) + (if (= (len (append3 (singleton 10) 20 30 40)) 4) + (= (pair 5 6) (append2 (empty) 5 6)) + false) + false) + false)) + +(fn imported_constructor_helpers_ok () -> bool + (if (= (repeat 7 -4) (empty)) + (if (= (repeat 7 0) (empty)) + (if (= (repeat 7 3) (triple 7 7 7)) + (if (= (range_from_zero -1) (empty)) + (if (= (range_from_zero 0) (empty)) + (if (= (range_from_zero 4) (append (triple 0 1 2) 3)) + (= (range_from_zero 2) (pair 0 1)) + false) + false) + false) + false) + false) + false)) + +(fn imported_range_helpers_ok () -> bool + (if (= (range -3 2) (append3 (pair -3 -2) -1 0 1)) + (if (= (range 3 7) (append2 (pair 3 4) 5 6)) + (if (= (range -2 -2) (empty)) + (= (range 7 3) (empty)) + false) + false) + false)) + +(fn option_is_some_value ((value (option i32)) (expected i32)) -> bool + (if (is_some value) + (= (unwrap_some value) expected) + false)) + +(fn imported_option_query_helpers_ok () -> bool + (if (is_none (first_option (empty))) + (if (option_is_some_value (first_option (pair 40 41)) 40) + (if (is_none (last_option (empty))) + (if (option_is_some_value (last_option (triple 10 20 30)) 30) + (if (is_none (index_option (pair 40 41) -1)) + (if (is_none (index_option (pair 40 41) 9)) + (option_is_some_value (index_option (pair 40 41) 1) 41) + false) + false) + false) + false) + false) + false)) + +(fn imported_query_helpers_ok () -> bool + (if (is_empty (empty)) + (if (= (first_or (empty) 9) 9) + (if (= (first_or (pair 40 41) 9) 40) + (if (= (last_or (triple 10 20 30) 9) 30) + (if (= (index_or (pair 40 41) -1 7) 7) + (if (= (index_or (pair 40 41) 9 7) 7) + (if (= (index_or (pair 40 41) 1 7) 41) + (if (is_none (first_option (empty))) + (if (option_is_some_value (first_option (pair 40 41)) 40) + (if (option_is_some_value (last_option (triple 10 20 30)) 30) + (if (is_none (index_option (pair 40 41) -1)) + (if (is_none (index_option (pair 40 41) 9)) + (option_is_some_value (index_option (pair 40 41) 1) 41) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_starts_with_helper_ok () -> bool + (let values (vec i32) (append2 (triple 10 20 30) 40 50)) + (let prefix (vec i32) (pair 10 20)) + (let longer_prefix (vec i32) (append2 values 60 70)) + (let mismatched_prefix (vec i32) (pair 20 30)) + (if (starts_with values (empty)) + (if (starts_with values prefix) + (if (starts_with values values) + (if (starts_with values longer_prefix) + false + (if (starts_with values mismatched_prefix) + false + (if (= values (append2 (triple 10 20 30) 40 50)) + (= prefix (pair 10 20)) + false))) + false) + false) + false)) + +(fn imported_ends_with_helper_ok () -> bool + (let values (vec i32) (append2 (triple 10 20 30) 40 50)) + (let suffix (vec i32) (pair 40 50)) + (let longer_suffix (vec i32) (append2 values 60 70)) + (let mismatched_suffix (vec i32) (pair 40 51)) + (if (ends_with values (empty)) + (if (ends_with values suffix) + (if (ends_with values values) + (if (ends_with values longer_suffix) + false + (if (ends_with values mismatched_suffix) + false + (if (= values (append2 (triple 10 20 30) 40 50)) + (= suffix (pair 40 50)) + false))) + false) + false) + false)) + +(fn imported_without_suffix_helper_ok () -> bool + (let values (vec i32) (append2 (triple 10 20 30) 40 50)) + (let suffix (vec i32) (pair 40 50)) + (let longer_suffix (vec i32) (append2 values 60 70)) + (let mismatched_suffix (vec i32) (pair 40 51)) + (if (= (without_suffix values (empty)) values) + (if (= (without_suffix values values) (empty)) + (if (= (without_suffix values suffix) (triple 10 20 30)) + (if (= (without_suffix values longer_suffix) values) + (if (= (without_suffix values mismatched_suffix) values) + (if (= values (append2 (triple 10 20 30) 40 50)) + (= suffix (pair 40 50)) + false) + false) + false) + false) + false) + false)) + +(fn imported_without_prefix_helper_ok () -> bool + (let values (vec i32) (append2 (triple 10 20 30) 40 50)) + (let prefix (vec i32) (pair 10 20)) + (let longer_prefix (vec i32) (append2 values 60 70)) + (let mismatched_prefix (vec i32) (pair 10 21)) + (if (= (without_prefix values (empty)) values) + (if (= (without_prefix values values) (empty)) + (if (= (without_prefix values prefix) (triple 30 40 50)) + (if (= (without_prefix values longer_prefix) values) + (if (= (without_prefix values mismatched_prefix) values) + (if (= values (append2 (triple 10 20 30) 40 50)) + (= prefix (pair 10 20)) + false) + false) + false) + false) + false) + false)) + +(fn imported_transform_helpers_ok () -> bool + (if (= (concat (empty) (pair 7 8)) (pair 7 8)) + (if (= (concat (pair 1 2) (triple 3 4 5)) (append3 (pair 1 2) 3 4 5)) + (if (= (take (triple 10 20 30) -4) (empty)) + (if (= (take (triple 10 20 30) 2) (pair 10 20)) + (if (= (take (pair 10 20) 9) (pair 10 20)) + (if (= (drop (triple 10 20 30) -4) (triple 10 20 30)) + (if (= (drop (triple 10 20 30) 2) (singleton 30)) + (if (= (drop (pair 10 20) 9) (empty)) + (if (= (reverse (empty)) (empty)) + (= (reverse (append3 (pair 10 20) 30 40 50)) (append3 (pair 50 40) 30 20 10)) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_subvec_helper_ok () -> bool + (let values (vec i32) (append (triple 10 20 30) 40)) + (if (= (subvec values 1 3) (pair 20 30)) + (if (= (subvec values 2 9) (pair 30 40)) + (if (= (subvec values 2 2) (empty)) + (if (= (subvec values 4 9) (empty)) + (if (= (subvec values -1 2) (empty)) + (= values (append (triple 10 20 30) 40)) + false) + false) + false) + false) + false)) + +(fn imported_insert_helper_ok () -> bool + (let values (vec i32) (triple 10 20 30)) + (if (= (insert_at values 1 99) (append2 (pair 10 99) 20 30)) + (if (= (insert_at values 3 99) (append values 99)) + (if (= values (triple 10 20 30)) + (if (= (insert_at values -1 99) values) + (= (insert_at values 4 99) values) + false) + false) + false) + false)) + +(fn imported_insert_range_helper_ok () -> bool + (let values (vec i32) (triple 10 20 30)) + (let inserted (vec i32) (pair 70 80)) + (if (= (insert_range values 1 inserted) (append3 (pair 10 70) 80 20 30)) + (if (= (len (insert_range values 1 inserted)) (+ (len values) (len inserted))) + (if (= (insert_range values 3 inserted) (append2 values 70 80)) + (if (= values (triple 10 20 30)) + (if (= inserted (pair 70 80)) + (if (= (insert_range values -1 inserted) values) + (= (insert_range values 4 inserted) values) + false) + false) + false) + false) + false) + false)) + +(fn imported_replace_helper_ok () -> bool + (if (= (replace_at (triple 10 20 30) 1 99) (triple 10 99 30)) + (if (= (replace_at (triple 10 20 30) -1 99) (triple 10 20 30)) + (= (replace_at (pair 10 20) 2 99) (pair 10 20)) + false) + false)) + +(fn imported_replace_range_helper_ok () -> bool + (let values (vec i32) (append2 (triple 10 20 30) 40 50)) + (let replacement (vec i32) (pair 77 88)) + (if (= (replace_range values 1 3 replacement) (append3 (pair 10 77) 88 40 50)) + (if (= (replace_range values 3 99 replacement) (append2 (triple 10 20 30) 77 88)) + (if (= (replace_range values 2 2 replacement) values) + (if (= (replace_range values 5 7 replacement) values) + (if (= (replace_range values -1 2 replacement) values) + (if (= values (append2 (triple 10 20 30) 40 50)) + (= replacement (pair 77 88)) + false) + false) + false) + false) + false) + false)) + +(fn imported_remove_helper_ok () -> bool + (if (= (remove_at (triple 10 20 30) 1) (pair 10 30)) + (if (= (remove_at (triple 10 20 30) -1) (triple 10 20 30)) + (= (remove_at (pair 10 20) 2) (pair 10 20)) + false) + false)) + +(fn imported_remove_range_helper_ok () -> bool + (let values (vec i32) (append2 (triple 10 20 30) 40 50)) + (if (= (remove_range values 1 3) (append2 (singleton 10) 40 50)) + (if (= (remove_range values 3 5) (triple 10 20 30)) + (if (= (remove_range values 2 2) values) + (if (= (remove_range values 5 7) values) + (if (= (remove_range values -1 2) values) + (= values (append2 (triple 10 20 30) 40 50)) + false) + false) + false) + false) + false)) + +(fn imported_count_of_helper_ok () -> bool + (let values (vec i32) (append3 (pair 10 20) 10 30 10)) + (if (= (count_of (empty) 10) 0) + (if (= (count_of values 10) 3) + (if (= (count_of values 20) 1) + (if (= (count_of values 99) 0) + (= values (append3 (pair 10 20) 10 30 10)) + false) + false) + false) + false)) + +(fn imported_real_program_helpers_ok () -> bool + (if (contains (triple 10 20 30) 20) + (if (contains (triple 10 20 30) 99) + false + (if (option_is_some_value (index_of_option (append3 (pair 10 20) 30 20 10) 20) 1) + (if (option_is_some_value (last_index_of_option (append3 (pair 10 20) 30 20 10) 20) 3) + (if (is_none (index_of_option (append3 (pair 10 20) 30 20 10) 99)) + (if (is_none (last_index_of_option (append3 (pair 10 20) 30 20 10) 99)) + (= (sum (append3 (empty) 10 20 12)) 42) + false) + false) + false) + false)) + false)) + +(fn imported_vec_i32_helpers_ok () -> bool + (if (= (imported_empty_length) 0) + (if (= (imported_pair_index) 41) + (if (imported_builder_helpers_ok) + (if (imported_constructor_helpers_ok) + (if (imported_range_helpers_ok) + (if (imported_query_helpers_ok) + (if (imported_option_query_helpers_ok) + (if (imported_starts_with_helper_ok) + (if (imported_ends_with_helper_ok) + (if (imported_without_suffix_helper_ok) + (if (imported_without_prefix_helper_ok) + (if (imported_transform_helpers_ok) + (if (imported_subvec_helper_ok) + (if (imported_insert_helper_ok) + (if (imported_insert_range_helper_ok) + (if (imported_replace_helper_ok) + (if (imported_replace_range_helper_ok) + (if (imported_remove_helper_ok) + (if (imported_remove_range_helper_ok) + (if (imported_count_of_helper_ok) + (imported_real_program_helpers_ok) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn main () -> i32 + (if (imported_vec_i32_helpers_ok) + 42 + 1)) + +(test "explicit std vec_i32 empty len facade" + (= (imported_empty_length) 0)) + +(test "explicit std vec_i32 direct at facade" + (= (imported_pair_index) 41)) + +(test "explicit std vec_i32 builder helpers" + (imported_builder_helpers_ok)) + +(test "explicit std vec_i32 constructor helpers" + (imported_constructor_helpers_ok)) + +(test "explicit std vec_i32 range helper" + (imported_range_helpers_ok)) + +(test "explicit std vec_i32 query helpers" + (imported_query_helpers_ok)) + +(test "explicit std vec_i32 option query helpers" + (imported_option_query_helpers_ok)) + +(test "explicit std vec_i32 starts_with helper" + (imported_starts_with_helper_ok)) + +(test "explicit std vec_i32 ends_with helper" + (imported_ends_with_helper_ok)) + +(test "explicit std vec_i32 without_suffix helper" + (imported_without_suffix_helper_ok)) + +(test "explicit std vec_i32 without_prefix helper" + (imported_without_prefix_helper_ok)) + +(test "explicit std vec_i32 transform helpers" + (imported_transform_helpers_ok)) + +(test "explicit std vec_i32 subvec helper" + (imported_subvec_helper_ok)) + +(test "explicit std vec_i32 insert helper" + (imported_insert_helper_ok)) + +(test "explicit std vec_i32 insert range helper" + (imported_insert_range_helper_ok)) + +(test "explicit std vec_i32 replace helper" + (imported_replace_helper_ok)) + +(test "explicit std vec_i32 replace range helper" + (imported_replace_range_helper_ok)) + +(test "explicit std vec_i32 remove helper" + (imported_remove_helper_ok)) + +(test "explicit std vec_i32 remove range helper" + (imported_remove_range_helper_ok)) + +(test "explicit std vec_i32 count_of helper" + (imported_count_of_helper_ok)) + +(test "explicit std vec_i32 real program helpers" + (imported_real_program_helpers_ok)) + +(test "explicit std vec_i32 helpers all" + (= (main) 42)) diff --git a/examples/projects/std-import-vec_i64/slovo.toml b/examples/projects/std-import-vec_i64/slovo.toml new file mode 100644 index 0000000..4a8318b --- /dev/null +++ b/examples/projects/std-import-vec_i64/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-import-vec-i64" +source_root = "src" +entry = "main" diff --git a/examples/projects/std-import-vec_i64/src/main.slo b/examples/projects/std-import-vec_i64/src/main.slo new file mode 100644 index 0000000..21e9b91 --- /dev/null +++ b/examples/projects/std-import-vec_i64/src/main.slo @@ -0,0 +1,283 @@ +(module main) + +(import std.vec_i64 (empty append len at singleton append2 append3 pair triple is_empty index_or first_or last_or index_option first_option last_option index_of_option last_index_of_option contains sum concat take drop reverse subvec insert_at insert_range replace_at replace_range remove_at remove_range)) + +(fn imported_empty_length () -> i32 + (len (empty))) + +(fn imported_pair_index () -> i64 + (at (pair 40i64 41i64) 1)) + +(fn original_len_after_append () -> i32 + (let values (vec i64) (empty)) + (let more (vec i64) (append values 9i64)) + (len values)) + +(fn option_i64_is_some_value ((value (option i64)) (expected i64)) -> bool + (if (is_some value) + (= (unwrap_some value) expected) + false)) + +(fn option_i32_is_some_value ((value (option i32)) (expected i32)) -> bool + (if (is_some value) + (= (unwrap_some value) expected) + false)) + +(fn imported_builder_helpers_ok () -> bool + (if (= (len (singleton 42i64)) 1) + (if (= (len (append2 (empty) 10i64 20i64)) 2) + (if (= (len (append3 (singleton 10i64) 20i64 30i64 40i64)) 4) + (if (= (pair 5i64 6i64) (append2 (empty) 5i64 6i64)) + (= (triple 7i64 8i64 9i64) (append3 (empty) 7i64 8i64 9i64)) + false) + false) + false) + false)) + +(fn imported_query_helpers_ok () -> bool + (if (is_empty (empty)) + (if (= (first_or (empty) 9i64) 9i64) + (if (= (first_or (pair 40i64 41i64) 9i64) 40i64) + (if (= (last_or (triple 10i64 20i64 30i64) 9i64) 30i64) + (if (= (index_or (pair 40i64 41i64) -1 7i64) 7i64) + (if (= (index_or (pair 40i64 41i64) 9 7i64) 7i64) + (= (index_or (pair 40i64 41i64) 1 7i64) 41i64) + false) + false) + false) + false) + false) + false)) + +(fn imported_option_query_helpers_ok () -> bool + (if (is_none (first_option (empty))) + (if (option_i64_is_some_value (first_option (pair 40i64 41i64)) 40i64) + (if (is_none (last_option (empty))) + (if (option_i64_is_some_value (last_option (triple 10i64 20i64 30i64)) 30i64) + (if (is_none (index_option (pair 40i64 41i64) -1)) + (if (is_none (index_option (pair 40i64 41i64) 9)) + (if (option_i64_is_some_value (index_option (pair 40i64 41i64) 1) 41i64) + (if (is_none (index_of_option (append3 (pair 10i64 20i64) 30i64 20i64 10i64) 99i64)) + (if (option_i32_is_some_value (index_of_option (append3 (pair 10i64 20i64) 30i64 20i64 10i64) 20i64) 1) + (if (is_none (last_index_of_option (append3 (pair 10i64 20i64) 30i64 20i64 10i64) 99i64)) + (option_i32_is_some_value (last_index_of_option (append3 (pair 10i64 20i64) 30i64 20i64 10i64) 20i64) 3) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_transform_helpers_ok () -> bool + (if (= (concat (empty) (pair 7i64 8i64)) (pair 7i64 8i64)) + (if (= (concat (pair 1i64 2i64) (triple 3i64 4i64 5i64)) (append3 (pair 1i64 2i64) 3i64 4i64 5i64)) + (if (= (take (triple 10i64 20i64 30i64) -4) (empty)) + (if (= (take (triple 10i64 20i64 30i64) 2) (pair 10i64 20i64)) + (if (= (take (pair 10i64 20i64) 9) (pair 10i64 20i64)) + (if (= (drop (triple 10i64 20i64 30i64) -4) (triple 10i64 20i64 30i64)) + (if (= (drop (triple 10i64 20i64 30i64) 2) (singleton 30i64)) + (if (= (drop (pair 10i64 20i64) 9) (empty)) + (if (= (reverse (empty)) (empty)) + (= (reverse (append3 (pair 10i64 20i64) 30i64 40i64 50i64)) (append3 (pair 50i64 40i64) 30i64 20i64 10i64)) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_subvec_helper_ok () -> bool + (let original (vec i64) (append2 (triple 10i64 20i64 30i64) 40i64 50i64)) + (if (= (subvec original -1 2) (empty)) + (if (= (subvec original 3 3) (empty)) + (if (= (subvec original 9 10) (empty)) + (if (= (subvec original 2 99) (triple 30i64 40i64 50i64)) + (if (= (subvec original 1 4) (triple 20i64 30i64 40i64)) + (= original (append2 (triple 10i64 20i64 30i64) 40i64 50i64)) + false) + false) + false) + false) + false)) + +(fn imported_insert_helper_ok () -> bool + (let values (vec i64) (triple 10i64 20i64 30i64)) + (if (= (insert_at values 1 99i64) (append2 (pair 10i64 99i64) 20i64 30i64)) + (if (= (insert_at values 3 99i64) (append values 99i64)) + (if (= values (triple 10i64 20i64 30i64)) + (if (= (insert_at values -1 99i64) values) + (= (insert_at values 4 99i64) values) + false) + false) + false) + false)) + +(fn imported_insert_range_helper_ok () -> bool + (let values (vec i64) (append2 (triple 10i64 20i64 30i64) 40i64 50i64)) + (let inserted (vec i64) (pair 77i64 88i64)) + (if (= (insert_range values -1 inserted) values) + (if (= (insert_range values 2 inserted) (append3 (append2 (pair 10i64 20i64) 77i64 88i64) 30i64 40i64 50i64)) + (if (= (insert_range values 5 inserted) (append2 (append2 (triple 10i64 20i64 30i64) 40i64 50i64) 77i64 88i64)) + (if (= values (append2 (triple 10i64 20i64 30i64) 40i64 50i64)) + (if (= inserted (pair 77i64 88i64)) + (= (insert_range values 6 inserted) values) + false) + false) + false) + false) + false)) + +(fn imported_replace_helper_ok () -> bool + (if (= (replace_at (triple 10i64 20i64 30i64) 1 99i64) (triple 10i64 99i64 30i64)) + (if (= (replace_at (triple 10i64 20i64 30i64) -1 99i64) (triple 10i64 20i64 30i64)) + (= (replace_at (pair 10i64 20i64) 2 99i64) (pair 10i64 20i64)) + false) + false)) + +(fn imported_replace_range_helper_ok () -> bool + (let original (vec i64) (append2 (triple 10i64 20i64 30i64) 40i64 50i64)) + (let replacement (vec i64) (pair 77i64 88i64)) + (if (= (replace_range original -1 2 replacement) original) + (if (= (replace_range original 3 3 replacement) original) + (if (= (replace_range original 4 2 replacement) original) + (if (= (replace_range original 5 9 replacement) original) + (if (= (replace_range original 2 99 replacement) (append2 (pair 10i64 20i64) 77i64 88i64)) + (if (= (replace_range original 1 4 replacement) (append2 (pair 10i64 77i64) 88i64 50i64)) + (if (= original (append2 (triple 10i64 20i64 30i64) 40i64 50i64)) + (= replacement (pair 77i64 88i64)) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_remove_helper_ok () -> bool + (let original (vec i64) (triple 10i64 20i64 30i64)) + (let removed_middle (vec i64) (remove_at original 1)) + (if (= removed_middle (pair 10i64 30i64)) + (if (= (len removed_middle) 2) + (if (= (remove_at original 0) (pair 20i64 30i64)) + (if (= (remove_at original 2) (pair 10i64 20i64)) + (if (= original (triple 10i64 20i64 30i64)) + (if (= (remove_at original -1) original) + (= (remove_at original 3) original) + false) + false) + false) + false) + false) + false)) + +(fn imported_remove_range_helper_ok () -> bool + (let original (vec i64) (append2 (triple 10i64 20i64 30i64) 40i64 50i64)) + (if (= (remove_range original -1 2) original) + (if (= (remove_range original 3 3) original) + (if (= (remove_range original 4 2) original) + (if (= (remove_range original 5 9) original) + (if (= (remove_range original 2 99) (pair 10i64 20i64)) + (if (= (remove_range original 1 4) (pair 10i64 50i64)) + (= original (append2 (triple 10i64 20i64 30i64) 40i64 50i64)) + false) + false) + false) + false) + false) + false)) + +(fn imported_real_program_helpers_ok () -> bool + (let values (vec i64) (append2 (triple 10i64 20i64 30i64) 40i64 50i64)) + (if (= (original_len_after_append) 0) + (if (contains values 20i64) + (if (contains values 99i64) + false + (= (sum values) 150i64)) + false) + false)) + +(fn helpers_all_ok () -> bool + (if (= (imported_empty_length) 0) + (if (= (imported_pair_index) 41i64) + (if (imported_builder_helpers_ok) + (if (imported_query_helpers_ok) + (if (imported_option_query_helpers_ok) + (if (imported_transform_helpers_ok) + (if (imported_subvec_helper_ok) + (if (imported_insert_helper_ok) + (if (imported_insert_range_helper_ok) + (if (imported_replace_helper_ok) + (if (imported_replace_range_helper_ok) + (if (imported_remove_helper_ok) + (if (imported_remove_range_helper_ok) + (imported_real_program_helpers_ok) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(test "explicit std vec_i64 empty len facade" + (= (imported_empty_length) 0)) + +(test "explicit std vec_i64 direct at facade" + (= (imported_pair_index) 41i64)) + +(test "explicit std vec_i64 builder helpers" + (imported_builder_helpers_ok)) + +(test "explicit std vec_i64 query helpers" + (imported_query_helpers_ok)) + +(test "explicit std vec_i64 option query helpers" + (imported_option_query_helpers_ok)) + +(test "explicit std vec_i64 transform helpers" + (imported_transform_helpers_ok)) + +(test "explicit std vec_i64 subvec helper" + (imported_subvec_helper_ok)) + +(test "explicit std vec_i64 insert helper" + (imported_insert_helper_ok)) + +(test "explicit std vec_i64 insert range helper" + (imported_insert_range_helper_ok)) + +(test "explicit std vec_i64 replace helper" + (imported_replace_helper_ok)) + +(test "explicit std vec_i64 replace range helper" + (imported_replace_range_helper_ok)) + +(test "explicit std vec_i64 remove helper" + (imported_remove_helper_ok)) + +(test "explicit std vec_i64 remove range helper" + (imported_remove_range_helper_ok)) + +(test "explicit std vec_i64 real program helpers" + (imported_real_program_helpers_ok)) + +(test "explicit std vec_i64 helpers all" + (helpers_all_ok)) + +(fn main () -> i32 + (if (helpers_all_ok) + 0 + 1)) diff --git a/examples/projects/std-import-vec_string/slovo.toml b/examples/projects/std-import-vec_string/slovo.toml new file mode 100644 index 0000000..2be8303 --- /dev/null +++ b/examples/projects/std-import-vec_string/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-import-vec-string" +source_root = "src" +entry = "main" diff --git a/examples/projects/std-import-vec_string/src/main.slo b/examples/projects/std-import-vec_string/src/main.slo new file mode 100644 index 0000000..7fc5011 --- /dev/null +++ b/examples/projects/std-import-vec_string/src/main.slo @@ -0,0 +1,381 @@ +(module main) + +(import std.vec_string (empty append len at singleton append2 append3 pair triple is_empty index_or first_or last_or index_option first_option last_option index_of_option last_index_of_option contains count_of concat take starts_with without_prefix ends_with without_suffix drop reverse subvec insert_at insert_range replace_at replace_range remove_at remove_range)) + +(fn imported_empty_length () -> i32 + (len (empty))) + +(fn imported_pair_index () -> string + (at (pair "slovo" "tree") 1)) + +(fn original_len_after_append () -> i32 + (let values (vec string) (empty)) + (let more (vec string) (append values "branch")) + (len values)) + +(fn option_string_is_some_value ((value (option string)) (expected string)) -> bool + (if (is_some value) + (= (unwrap_some value) expected) + false)) + +(fn option_i32_is_some_value ((value (option i32)) (expected i32)) -> bool + (if (is_some value) + (= (unwrap_some value) expected) + false)) + +(fn imported_builder_helpers_ok () -> bool + (if (= (len (singleton "seed")) 1) + (if (= (len (append2 (empty) "alpha" "beta")) 2) + (if (= (len (append3 (singleton "alpha") "beta" "gamma" "delta")) 4) + (if (= (pair "left" "right") (append2 (empty) "left" "right")) + (= (triple "red" "green" "blue") (append3 (empty) "red" "green" "blue")) + false) + false) + false) + false)) + +(fn imported_query_helpers_ok () -> bool + (if (is_empty (empty)) + (if (= (first_or (empty) "fallback") "fallback") + (if (= (first_or (pair "alpha" "beta") "fallback") "alpha") + (if (= (last_or (triple "red" "green" "blue") "fallback") "blue") + (if (= (index_or (pair "alpha" "beta") -1 "fallback") "fallback") + (if (= (index_or (pair "alpha" "beta") 9 "fallback") "fallback") + (= (index_or (pair "alpha" "beta") 1 "fallback") "beta") + false) + false) + false) + false) + false) + false)) + +(fn imported_option_query_helpers_ok () -> bool + (if (is_none (first_option (empty))) + (if (option_string_is_some_value (first_option (pair "alpha" "beta")) "alpha") + (if (is_none (last_option (empty))) + (if (option_string_is_some_value (last_option (triple "red" "green" "blue")) "blue") + (if (is_none (index_option (pair "alpha" "beta") -1)) + (if (is_none (index_option (pair "alpha" "beta") 9)) + (if (option_string_is_some_value (index_option (pair "alpha" "beta") 1) "beta") + (if (is_none (index_of_option (append3 (pair "oak" "pine") "birch" "pine" "oak") "cedar")) + (if (option_i32_is_some_value (index_of_option (append3 (pair "oak" "pine") "birch" "pine" "oak") "pine") 1) + (if (is_none (last_index_of_option (append3 (pair "oak" "pine") "birch" "pine" "oak") "cedar")) + (option_i32_is_some_value (last_index_of_option (append3 (pair "oak" "pine") "birch" "pine" "oak") "pine") 3) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_starts_with_helper_ok () -> bool + (let values (vec string) (append2 (triple "oak" "pine" "birch") "cedar" "maple")) + (let prefix (vec string) (pair "oak" "pine")) + (let longer_prefix (vec string) (append2 values "spruce" "elm")) + (let mismatched_prefix (vec string) (pair "pine" "birch")) + (if (starts_with values (empty)) + (if (starts_with values prefix) + (if (starts_with values values) + (if (starts_with values longer_prefix) + false + (if (starts_with values mismatched_prefix) + false + (if (= values (append2 (triple "oak" "pine" "birch") "cedar" "maple")) + (= prefix (pair "oak" "pine")) + false))) + false) + false) + false)) + +(fn imported_ends_with_helper_ok () -> bool + (let values (vec string) (append2 (triple "oak" "pine" "birch") "cedar" "maple")) + (let suffix (vec string) (pair "cedar" "maple")) + (let longer_suffix (vec string) (append2 values "spruce" "elm")) + (let mismatched_suffix (vec string) (pair "cedar" "elm")) + (if (ends_with values (empty)) + (if (ends_with values suffix) + (if (ends_with values values) + (if (ends_with values longer_suffix) + false + (if (ends_with values mismatched_suffix) + false + (if (= values (append2 (triple "oak" "pine" "birch") "cedar" "maple")) + (= suffix (pair "cedar" "maple")) + false))) + false) + false) + false)) + +(fn imported_without_suffix_helper_ok () -> bool + (let values (vec string) (append2 (triple "oak" "pine" "birch") "cedar" "maple")) + (let suffix (vec string) (pair "cedar" "maple")) + (let longer_suffix (vec string) (append2 values "spruce" "elm")) + (let mismatched_suffix (vec string) (pair "cedar" "elm")) + (if (= (without_suffix values (empty)) values) + (if (= (without_suffix values values) (empty)) + (if (= (without_suffix values suffix) (triple "oak" "pine" "birch")) + (if (= (without_suffix values longer_suffix) values) + (if (= (without_suffix values mismatched_suffix) values) + (if (= values (append2 (triple "oak" "pine" "birch") "cedar" "maple")) + (= suffix (pair "cedar" "maple")) + false) + false) + false) + false) + false) + false)) + +(fn imported_without_prefix_helper_ok () -> bool + (let values (vec string) (append2 (triple "oak" "pine" "birch") "cedar" "maple")) + (let prefix (vec string) (pair "oak" "pine")) + (let longer_prefix (vec string) (append2 values "spruce" "elm")) + (let mismatched_prefix (vec string) (pair "oak" "birch")) + (if (= (without_prefix values (empty)) values) + (if (= (without_prefix values values) (empty)) + (if (= (without_prefix values prefix) (triple "birch" "cedar" "maple")) + (if (= (without_prefix values longer_prefix) values) + (if (= (without_prefix values mismatched_prefix) values) + (if (= values (append2 (triple "oak" "pine" "birch") "cedar" "maple")) + (= prefix (pair "oak" "pine")) + false) + false) + false) + false) + false) + false)) + +(fn imported_transform_helpers_ok () -> bool + (if (= (concat (empty) (pair "oak" "pine")) (pair "oak" "pine")) + (if (= (concat (pair "a" "b") (triple "c" "d" "e")) (append3 (pair "a" "b") "c" "d" "e")) + (if (= (take (triple "red" "green" "blue") -4) (empty)) + (if (= (take (triple "red" "green" "blue") 2) (pair "red" "green")) + (if (= (take (pair "red" "green") 9) (pair "red" "green")) + (if (= (drop (triple "red" "green" "blue") -4) (triple "red" "green" "blue")) + (if (= (drop (triple "red" "green" "blue") 2) (singleton "blue")) + (if (= (drop (pair "red" "green") 9) (empty)) + (if (= (reverse (empty)) (empty)) + (= (reverse (append3 (pair "oak" "pine") "birch" "cedar" "maple")) (append3 (pair "maple" "cedar") "birch" "pine" "oak")) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_subvec_helper_ok () -> bool + (let original (vec string) (append2 (triple "oak" "pine" "birch") "cedar" "maple")) + (if (= (subvec original -1 2) (empty)) + (if (= (subvec original 3 3) (empty)) + (if (= (subvec original 9 10) (empty)) + (if (= (subvec original 2 99) (triple "birch" "cedar" "maple")) + (if (= (subvec original 1 4) (triple "pine" "birch" "cedar")) + (= original (append2 (triple "oak" "pine" "birch") "cedar" "maple")) + false) + false) + false) + false) + false)) + +(fn imported_insert_helper_ok () -> bool + (let values (vec string) (triple "oak" "pine" "birch")) + (if (= (insert_at values 1 "cedar") (append2 (pair "oak" "cedar") "pine" "birch")) + (if (= (insert_at values 3 "cedar") (append values "cedar")) + (if (= values (triple "oak" "pine" "birch")) + (if (= (insert_at values -1 "cedar") values) + (= (insert_at values 4 "cedar") values) + false) + false) + false) + false)) + +(fn imported_insert_range_helper_ok () -> bool + (let values (vec string) (append2 (triple "oak" "pine" "birch") "cedar" "maple")) + (let inserted (vec string) (pair "elm" "fir")) + (if (= (insert_range values -1 inserted) values) + (if (= (insert_range values 2 inserted) (append3 (append2 (pair "oak" "pine") "elm" "fir") "birch" "cedar" "maple")) + (if (= (insert_range values 5 inserted) (append2 (append2 (triple "oak" "pine" "birch") "cedar" "maple") "elm" "fir")) + (if (= values (append2 (triple "oak" "pine" "birch") "cedar" "maple")) + (if (= inserted (pair "elm" "fir")) + (= (insert_range values 6 inserted) values) + false) + false) + false) + false) + false)) + +(fn imported_replace_helper_ok () -> bool + (if (= (replace_at (triple "oak" "pine" "birch") 1 "cedar") (triple "oak" "cedar" "birch")) + (if (= (replace_at (triple "oak" "pine" "birch") -1 "cedar") (triple "oak" "pine" "birch")) + (= (replace_at (pair "oak" "pine") 2 "cedar") (pair "oak" "pine")) + false) + false)) + +(fn imported_replace_range_helper_ok () -> bool + (let original (vec string) (append2 (triple "oak" "pine" "birch") "cedar" "maple")) + (let replacement (vec string) (pair "elm" "fir")) + (if (= (replace_range original -1 2 replacement) original) + (if (= (replace_range original 3 3 replacement) original) + (if (= (replace_range original 4 2 replacement) original) + (if (= (replace_range original 5 9 replacement) original) + (if (= (replace_range original 2 99 replacement) (append2 (pair "oak" "pine") "elm" "fir")) + (if (= (replace_range original 1 4 replacement) (append2 (pair "oak" "elm") "fir" "maple")) + (if (= original (append2 (triple "oak" "pine" "birch") "cedar" "maple")) + (= replacement (pair "elm" "fir")) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_remove_helper_ok () -> bool + (let original (vec string) (triple "oak" "pine" "birch")) + (let removed_middle (vec string) (remove_at original 1)) + (if (= removed_middle (pair "oak" "birch")) + (if (= (len removed_middle) 2) + (if (= (remove_at original 0) (pair "pine" "birch")) + (if (= (remove_at original 2) (pair "oak" "pine")) + (if (= original (triple "oak" "pine" "birch")) + (if (= (remove_at original -1) original) + (= (remove_at original 3) original) + false) + false) + false) + false) + false) + false)) + +(fn imported_remove_range_helper_ok () -> bool + (let original (vec string) (append2 (triple "oak" "pine" "birch") "cedar" "maple")) + (if (= (remove_range original -1 2) original) + (if (= (remove_range original 3 3) original) + (if (= (remove_range original 4 2) original) + (if (= (remove_range original 5 9) original) + (if (= (remove_range original 2 99) (pair "oak" "pine")) + (if (= (remove_range original 1 4) (pair "oak" "maple")) + (= original (append2 (triple "oak" "pine" "birch") "cedar" "maple")) + false) + false) + false) + false) + false) + false)) + +(fn imported_real_program_helpers_ok () -> bool + (let values (vec string) (append2 (triple "oak" "pine" "oak") "birch" "oak")) + (if (= (original_len_after_append) 0) + (if (contains values "oak") + (if (contains values "cedar") + false + (if (= (count_of values "oak") 3) + (= (count_of values "cedar") 0) + false)) + false) + false)) + +(fn helpers_all_ok () -> bool + (if (= (imported_empty_length) 0) + (if (= (imported_pair_index) "tree") + (if (imported_builder_helpers_ok) + (if (imported_query_helpers_ok) + (if (imported_option_query_helpers_ok) + (if (imported_starts_with_helper_ok) + (if (imported_ends_with_helper_ok) + (if (imported_without_suffix_helper_ok) + (if (imported_without_prefix_helper_ok) + (if (imported_transform_helpers_ok) + (if (imported_subvec_helper_ok) + (if (imported_insert_helper_ok) + (if (imported_insert_range_helper_ok) + (if (imported_replace_helper_ok) + (if (imported_replace_range_helper_ok) + (if (imported_remove_helper_ok) + (if (imported_remove_range_helper_ok) + (imported_real_program_helpers_ok) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn main () -> i32 + (if (helpers_all_ok) + 42 + 1)) + +(test "explicit std vec_string empty len facade" + (= (imported_empty_length) 0)) + +(test "explicit std vec_string direct at facade" + (= (imported_pair_index) "tree")) + +(test "explicit std vec_string builder helpers" + (imported_builder_helpers_ok)) + +(test "explicit std vec_string query helpers" + (imported_query_helpers_ok)) + +(test "explicit std vec_string option query helpers" + (imported_option_query_helpers_ok)) + +(test "explicit std vec_string starts_with helper" + (imported_starts_with_helper_ok)) + +(test "explicit std vec_string ends_with helper" + (imported_ends_with_helper_ok)) + +(test "explicit std vec_string without_suffix helper" + (imported_without_suffix_helper_ok)) + +(test "explicit std vec_string without_prefix helper" + (imported_without_prefix_helper_ok)) + +(test "explicit std vec_string transform helpers" + (imported_transform_helpers_ok)) + +(test "explicit std vec_string subvec helper" + (imported_subvec_helper_ok)) + +(test "explicit std vec_string insert helper" + (imported_insert_helper_ok)) + +(test "explicit std vec_string insert range helper" + (imported_insert_range_helper_ok)) + +(test "explicit std vec_string replace helper" + (imported_replace_helper_ok)) + +(test "explicit std vec_string replace range helper" + (imported_replace_range_helper_ok)) + +(test "explicit std vec_string remove helper" + (imported_remove_helper_ok)) + +(test "explicit std vec_string remove range helper" + (imported_remove_range_helper_ok)) + +(test "explicit std vec_string real-program helpers" + (imported_real_program_helpers_ok)) + +(test "explicit std vec_string helpers all" + (= (main) 42)) diff --git a/examples/projects/std-layout-local-cli/slovo.toml b/examples/projects/std-layout-local-cli/slovo.toml new file mode 100644 index 0000000..408999d --- /dev/null +++ b/examples/projects/std-layout-local-cli/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-layout-local-cli" +source_root = "src" +entry = "main" diff --git a/examples/projects/std-layout-local-cli/src/cli.slo b/examples/projects/std-layout-local-cli/src/cli.slo new file mode 100644 index 0000000..f080624 --- /dev/null +++ b/examples/projects/std-layout-local-cli/src/cli.slo @@ -0,0 +1,157 @@ +(module cli (export arg_text_result arg_text_option arg_i32_result arg_i32_option arg_i32_or_zero arg_i32_or arg_u32_result arg_u32_option arg_u32_or_zero arg_u32_or arg_i64_result arg_i64_option arg_i64_or_zero arg_i64_or arg_u64_result arg_u64_option arg_u64_or_zero arg_u64_or arg_f64_result arg_f64_option arg_f64_or_zero arg_f64_or arg_bool_result arg_bool_option arg_bool_or_false arg_bool_or)) + +(import process (arg_result)) + +(import result (ok_or_none_string ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool)) + +(import string (parse_i32_result parse_u32_result parse_i64_result parse_u64_result parse_f64_result parse_bool_result)) + +(fn arg_text_result ((index i32)) -> (result string i32) + (arg_result index)) + +(fn arg_text_option ((index i32)) -> (option string) + (ok_or_none_string (arg_text_result index))) + +(fn arg_i32_result ((index i32)) -> (result i32 i32) + (match (arg_result index) + ((ok text) + (parse_i32_result text)) + ((err code) + (err i32 i32 code)))) + +(fn arg_i32_option ((index i32)) -> (option i32) + (ok_or_none_i32 (arg_i32_result index))) + +(fn arg_i32_or_zero ((index i32)) -> i32 + (match (arg_i32_result index) + ((ok value) + value) + ((err code) + 0))) + +(fn arg_i32_or ((index i32) (fallback i32)) -> i32 + (match (arg_i32_result index) + ((ok value) + value) + ((err code) + fallback))) + +(fn arg_u32_result ((index i32)) -> (result u32 i32) + (match (arg_result index) + ((ok text) + (parse_u32_result text)) + ((err code) + (err u32 i32 code)))) + +(fn arg_u32_option ((index i32)) -> (option u32) + (ok_or_none_u32 (arg_u32_result index))) + +(fn arg_u32_or_zero ((index i32)) -> u32 + (match (arg_u32_result index) + ((ok value) + value) + ((err code) + 0u32))) + +(fn arg_u32_or ((index i32) (fallback u32)) -> u32 + (match (arg_u32_result index) + ((ok value) + value) + ((err code) + fallback))) + +(fn arg_i64_result ((index i32)) -> (result i64 i32) + (match (arg_result index) + ((ok text) + (parse_i64_result text)) + ((err code) + (err i64 i32 code)))) + +(fn arg_i64_option ((index i32)) -> (option i64) + (ok_or_none_i64 (arg_i64_result index))) + +(fn arg_i64_or_zero ((index i32)) -> i64 + (match (arg_i64_result index) + ((ok value) + value) + ((err code) + 0i64))) + +(fn arg_i64_or ((index i32) (fallback i64)) -> i64 + (match (arg_i64_result index) + ((ok value) + value) + ((err code) + fallback))) + +(fn arg_u64_result ((index i32)) -> (result u64 i32) + (match (arg_result index) + ((ok text) + (parse_u64_result text)) + ((err code) + (err u64 i32 code)))) + +(fn arg_u64_option ((index i32)) -> (option u64) + (ok_or_none_u64 (arg_u64_result index))) + +(fn arg_u64_or_zero ((index i32)) -> u64 + (match (arg_u64_result index) + ((ok value) + value) + ((err code) + 0u64))) + +(fn arg_u64_or ((index i32) (fallback u64)) -> u64 + (match (arg_u64_result index) + ((ok value) + value) + ((err code) + fallback))) + +(fn arg_f64_result ((index i32)) -> (result f64 i32) + (match (arg_result index) + ((ok text) + (parse_f64_result text)) + ((err code) + (err f64 i32 code)))) + +(fn arg_f64_option ((index i32)) -> (option f64) + (ok_or_none_f64 (arg_f64_result index))) + +(fn arg_f64_or_zero ((index i32)) -> f64 + (match (arg_f64_result index) + ((ok value) + value) + ((err code) + 0.0))) + +(fn arg_f64_or ((index i32) (fallback f64)) -> f64 + (match (arg_f64_result index) + ((ok value) + value) + ((err code) + fallback))) + +(fn arg_bool_result ((index i32)) -> (result bool i32) + (match (arg_result index) + ((ok text) + (parse_bool_result text)) + ((err code) + (err bool i32 code)))) + +(fn arg_bool_option ((index i32)) -> (option bool) + (ok_or_none_bool (arg_bool_result index))) + +(fn arg_bool_or_false ((index i32)) -> bool + (match (arg_bool_result index) + ((ok value) + value) + ((err code) + false))) + +(fn arg_bool_or ((index i32) (fallback bool)) -> bool + (match (arg_bool_result index) + ((ok value) + value) + ((err code) + fallback))) diff --git a/examples/projects/std-layout-local-cli/src/main.slo b/examples/projects/std-layout-local-cli/src/main.slo new file mode 100644 index 0000000..48d41d4 --- /dev/null +++ b/examples/projects/std-layout-local-cli/src/main.slo @@ -0,0 +1,278 @@ +(module main) + +(import cli (arg_text_result arg_text_option arg_i32_result arg_i32_option arg_i32_or_zero arg_i32_or arg_u32_result arg_u32_option arg_u32_or_zero arg_u32_or arg_i64_result arg_i64_option arg_i64_or_zero arg_i64_or arg_u64_result arg_u64_option arg_u64_or_zero arg_u64_or arg_f64_result arg_f64_option arg_f64_or_zero arg_f64_or arg_bool_result arg_bool_option arg_bool_or_false arg_bool_or)) + +(fn impossible_index () -> i32 + 99999) + +(fn first_index () -> i32 + 0) + +(fn imported_cli_arg_text_missing () -> bool + (match (arg_text_result (impossible_index)) + ((ok text) + false) + ((err code) + (= code 1)))) + +(fn imported_cli_arg_text_option_ok () -> bool + (if (match (arg_text_option (first_index)) + ((some text) + true) + ((none) + false)) + (match (arg_text_option (impossible_index)) + ((some text) + false) + ((none) + true)) + false)) + +(fn imported_cli_arg_i32_missing () -> bool + (match (arg_i32_result (impossible_index)) + ((ok value) + false) + ((err code) + (= code 1)))) + +(fn imported_cli_arg_i32_or_zero_missing () -> bool + (= (arg_i32_or_zero (impossible_index)) 0)) + +(fn imported_cli_arg_u32_missing () -> bool + (match (arg_u32_result (impossible_index)) + ((ok value) + false) + ((err code) + (= code 1)))) + +(fn imported_cli_arg_u32_or_zero_missing () -> bool + (= (arg_u32_or_zero (impossible_index)) 0u32)) + +(fn imported_cli_arg_i64_missing () -> bool + (match (arg_i64_result (impossible_index)) + ((ok value) + false) + ((err code) + (= code 1)))) + +(fn imported_cli_arg_i64_or_zero_missing () -> bool + (= (arg_i64_or_zero (impossible_index)) 0i64)) + +(fn imported_cli_arg_u64_missing () -> bool + (match (arg_u64_result (impossible_index)) + ((ok value) + false) + ((err code) + (= code 1)))) + +(fn imported_cli_arg_u64_or_zero_missing () -> bool + (= (arg_u64_or_zero (impossible_index)) 0u64)) + +(fn imported_cli_arg_f64_missing () -> bool + (match (arg_f64_result (impossible_index)) + ((ok value) + false) + ((err code) + (= code 1)))) + +(fn imported_cli_arg_f64_or_zero_missing () -> bool + (= (arg_f64_or_zero (impossible_index)) 0.0)) + +(fn imported_cli_arg_bool_missing () -> bool + (match (arg_bool_result (impossible_index)) + ((ok value) + false) + ((err code) + (= code 1)))) + +(fn imported_cli_arg_bool_or_false_missing () -> bool + (if (arg_bool_or_false (impossible_index)) + false + true)) + +(fn imported_cli_typed_options_ok () -> bool + (if (match (arg_i32_option (impossible_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_i32_option (first_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_u32_option (impossible_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_u32_option (first_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_i64_option (impossible_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_i64_option (first_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_u64_option (impossible_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_u64_option (first_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_f64_option (impossible_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_f64_option (first_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_bool_option (impossible_index)) + ((some value) + false) + ((none) + true)) + (match (arg_bool_option (first_index)) + ((some value) + false) + ((none) + true)) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_cli_typed_custom_fallbacks_ok () -> bool + (if (= (arg_i32_or (impossible_index) 7) 7) + (if (= (arg_i32_or (first_index) 7) 7) + (if (= (arg_u32_or (impossible_index) 7u32) 7u32) + (if (= (arg_u32_or (first_index) 7u32) 7u32) + (if (= (arg_i64_or (impossible_index) 9i64) 9i64) + (if (= (arg_i64_or (first_index) 9i64) 9i64) + (if (= (arg_u64_or (impossible_index) 9u64) 9u64) + (if (= (arg_u64_or (first_index) 9u64) 9u64) + (if (= (arg_f64_or (impossible_index) 1.5) 1.5) + (if (= (arg_f64_or (first_index) 1.5) 1.5) + (if (arg_bool_or (impossible_index) true) + (arg_bool_or (first_index) true) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_cli_facade_ok () -> bool + (if (imported_cli_arg_text_missing) + (if (imported_cli_arg_text_option_ok) + (if (imported_cli_arg_i32_missing) + (if (imported_cli_arg_i32_or_zero_missing) + (if (imported_cli_arg_u32_missing) + (if (imported_cli_arg_u32_or_zero_missing) + (if (imported_cli_arg_i64_missing) + (if (imported_cli_arg_i64_or_zero_missing) + (if (imported_cli_arg_u64_missing) + (if (imported_cli_arg_u64_or_zero_missing) + (if (imported_cli_arg_f64_missing) + (if (imported_cli_arg_f64_or_zero_missing) + (if (imported_cli_arg_bool_missing) + (if (imported_cli_arg_bool_or_false_missing) + (if (imported_cli_typed_options_ok) + (imported_cli_typed_custom_fallbacks_ok) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn main () -> i32 + (if (imported_cli_facade_ok) + 42 + 1)) + +(test "explicit local cli arg text missing" + (imported_cli_arg_text_missing)) + +(test "explicit local cli arg text option" + (imported_cli_arg_text_option_ok)) + +(test "explicit local cli arg i32 missing" + (imported_cli_arg_i32_missing)) + +(test "explicit local cli arg i32 fallback missing" + (imported_cli_arg_i32_or_zero_missing)) + +(test "explicit local cli arg u32 missing" + (imported_cli_arg_u32_missing)) + +(test "explicit local cli arg u32 fallback missing" + (imported_cli_arg_u32_or_zero_missing)) + +(test "explicit local cli arg i64 missing" + (imported_cli_arg_i64_missing)) + +(test "explicit local cli arg i64 fallback missing" + (imported_cli_arg_i64_or_zero_missing)) + +(test "explicit local cli arg u64 missing" + (imported_cli_arg_u64_missing)) + +(test "explicit local cli arg u64 fallback missing" + (imported_cli_arg_u64_or_zero_missing)) + +(test "explicit local cli arg f64 missing" + (imported_cli_arg_f64_missing)) + +(test "explicit local cli arg f64 fallback missing" + (imported_cli_arg_f64_or_zero_missing)) + +(test "explicit local cli arg bool missing" + (imported_cli_arg_bool_missing)) + +(test "explicit local cli arg bool fallback missing" + (imported_cli_arg_bool_or_false_missing)) + +(test "explicit local cli typed option" + (imported_cli_typed_options_ok)) + +(test "explicit local cli typed custom fallback" + (imported_cli_typed_custom_fallbacks_ok)) + +(test "explicit local cli facade all" + (= (main) 42)) diff --git a/examples/projects/std-layout-local-cli/src/process.slo b/examples/projects/std-layout-local-cli/src/process.slo new file mode 100644 index 0000000..56be5ff --- /dev/null +++ b/examples/projects/std-layout-local-cli/src/process.slo @@ -0,0 +1,174 @@ +(module process (export argc arg arg_result arg_option has_arg arg_or arg_or_empty arg_i32_result arg_i32_option arg_i32_or_zero arg_i32_or arg_u32_result arg_u32_option arg_u32_or_zero arg_u32_or arg_i64_result arg_i64_option arg_i64_or_zero arg_i64_or arg_u64_result arg_u64_option arg_u64_or_zero arg_u64_or arg_f64_result arg_f64_option arg_f64_or_zero arg_f64_or arg_bool_result arg_bool_option arg_bool_or_false arg_bool_or)) + +(import result (ok_or_none_string ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool)) + +(fn argc () -> i32 + (std.process.argc)) + +(fn arg ((index i32)) -> string + (std.process.arg index)) + +(fn arg_result ((index i32)) -> (result string i32) + (std.process.arg_result index)) + +(fn arg_option ((index i32)) -> (option string) + (ok_or_none_string (arg_result index))) + +(fn has_arg ((index i32)) -> bool + (if (< index 0) + false + (< index (std.process.argc)))) + +(fn arg_or ((index i32) (fallback string)) -> string + (match (arg_result index) + ((ok payload) + payload) + ((err code) + fallback))) + +(fn arg_or_empty ((index i32)) -> string + (arg_or index "")) + +(fn arg_i32_result ((index i32)) -> (result i32 i32) + (match (arg_result index) + ((ok text) + (std.string.parse_i32_result text)) + ((err code) + (err i32 i32 code)))) + +(fn arg_i32_option ((index i32)) -> (option i32) + (ok_or_none_i32 (arg_i32_result index))) + +(fn arg_i32_or_zero ((index i32)) -> i32 + (match (arg_i32_result index) + ((ok value) + value) + ((err code) + 0))) + +(fn arg_i32_or ((index i32) (fallback i32)) -> i32 + (match (arg_i32_result index) + ((ok value) + value) + ((err code) + fallback))) + +(fn arg_u32_result ((index i32)) -> (result u32 i32) + (match (arg_result index) + ((ok text) + (std.string.parse_u32_result text)) + ((err code) + (err u32 i32 code)))) + +(fn arg_u32_option ((index i32)) -> (option u32) + (ok_or_none_u32 (arg_u32_result index))) + +(fn arg_u32_or_zero ((index i32)) -> u32 + (match (arg_u32_result index) + ((ok value) + value) + ((err code) + 0u32))) + +(fn arg_u32_or ((index i32) (fallback u32)) -> u32 + (match (arg_u32_result index) + ((ok value) + value) + ((err code) + fallback))) + +(fn arg_i64_result ((index i32)) -> (result i64 i32) + (match (arg_result index) + ((ok text) + (std.string.parse_i64_result text)) + ((err code) + (err i64 i32 code)))) + +(fn arg_i64_option ((index i32)) -> (option i64) + (ok_or_none_i64 (arg_i64_result index))) + +(fn arg_i64_or_zero ((index i32)) -> i64 + (match (arg_i64_result index) + ((ok value) + value) + ((err code) + 0i64))) + +(fn arg_i64_or ((index i32) (fallback i64)) -> i64 + (match (arg_i64_result index) + ((ok value) + value) + ((err code) + fallback))) + +(fn arg_u64_result ((index i32)) -> (result u64 i32) + (match (arg_result index) + ((ok text) + (std.string.parse_u64_result text)) + ((err code) + (err u64 i32 code)))) + +(fn arg_u64_option ((index i32)) -> (option u64) + (ok_or_none_u64 (arg_u64_result index))) + +(fn arg_u64_or_zero ((index i32)) -> u64 + (match (arg_u64_result index) + ((ok value) + value) + ((err code) + 0u64))) + +(fn arg_u64_or ((index i32) (fallback u64)) -> u64 + (match (arg_u64_result index) + ((ok value) + value) + ((err code) + fallback))) + +(fn arg_f64_result ((index i32)) -> (result f64 i32) + (match (arg_result index) + ((ok text) + (std.string.parse_f64_result text)) + ((err code) + (err f64 i32 code)))) + +(fn arg_f64_option ((index i32)) -> (option f64) + (ok_or_none_f64 (arg_f64_result index))) + +(fn arg_f64_or_zero ((index i32)) -> f64 + (match (arg_f64_result index) + ((ok value) + value) + ((err code) + 0.0))) + +(fn arg_f64_or ((index i32) (fallback f64)) -> f64 + (match (arg_f64_result index) + ((ok value) + value) + ((err code) + fallback))) + +(fn arg_bool_result ((index i32)) -> (result bool i32) + (match (arg_result index) + ((ok text) + (std.string.parse_bool_result text)) + ((err code) + (err bool i32 code)))) + +(fn arg_bool_option ((index i32)) -> (option bool) + (ok_or_none_bool (arg_bool_result index))) + +(fn arg_bool_or_false ((index i32)) -> bool + (match (arg_bool_result index) + ((ok value) + value) + ((err code) + false))) + +(fn arg_bool_or ((index i32) (fallback bool)) -> bool + (match (arg_bool_result index) + ((ok value) + value) + ((err code) + fallback))) diff --git a/examples/projects/std-layout-local-cli/src/result.slo b/examples/projects/std-layout-local-cli/src/result.slo new file mode 100644 index 0000000..4a7b77b --- /dev/null +++ b/examples/projects/std-layout-local-cli/src/result.slo @@ -0,0 +1,199 @@ +(module result (export ok_i32 err_i32 is_ok_i32 is_err_i32 unwrap_ok_i32 unwrap_err_i32 unwrap_or_i32 ok_or_none_i32 ok_u32 err_u32 is_ok_u32 is_err_u32 unwrap_ok_u32 unwrap_err_u32 unwrap_or_u32 ok_or_none_u32 ok_i64 err_i64 is_ok_i64 is_err_i64 unwrap_ok_i64 unwrap_err_i64 unwrap_or_i64 ok_or_none_i64 ok_u64 err_u64 is_ok_u64 is_err_u64 unwrap_ok_u64 unwrap_err_u64 unwrap_or_u64 ok_or_none_u64 ok_string err_string is_ok_string is_err_string unwrap_ok_string unwrap_err_string unwrap_or_string ok_or_none_string ok_f64 err_f64 is_ok_f64 is_err_f64 unwrap_ok_f64 unwrap_err_f64 unwrap_or_f64 ok_or_none_f64 ok_bool err_bool is_ok_bool is_err_bool unwrap_ok_bool unwrap_err_bool unwrap_or_bool ok_or_none_bool)) + +(fn ok_i32 ((value i32)) -> (result i32 i32) + (ok i32 i32 value)) + +(fn err_i32 ((code i32)) -> (result i32 i32) + (err i32 i32 code)) + +(fn is_ok_i32 ((value (result i32 i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_i32 ((value (result i32 i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_i32 ((value (result i32 i32))) -> i32 + (std.result.unwrap_ok value)) + +(fn unwrap_err_i32 ((value (result i32 i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_i32 ((value (result i32 i32)) (fallback i32)) -> i32 + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + fallback)) + +(fn ok_or_none_i32 ((value (result i32 i32))) -> (option i32) + (if (std.result.is_ok value) + (some i32 (std.result.unwrap_ok value)) + (none i32))) + +(fn ok_u32 ((value u32)) -> (result u32 i32) + (ok u32 i32 value)) + +(fn err_u32 ((code i32)) -> (result u32 i32) + (err u32 i32 code)) + +(fn is_ok_u32 ((value (result u32 i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_u32 ((value (result u32 i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_u32 ((value (result u32 i32))) -> u32 + (std.result.unwrap_ok value)) + +(fn unwrap_err_u32 ((value (result u32 i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_u32 ((value (result u32 i32)) (fallback u32)) -> u32 + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + fallback)) + +(fn ok_or_none_u32 ((value (result u32 i32))) -> (option u32) + (if (std.result.is_ok value) + (some u32 (std.result.unwrap_ok value)) + (none u32))) + +(fn ok_i64 ((value i64)) -> (result i64 i32) + (ok i64 i32 value)) + +(fn err_i64 ((code i32)) -> (result i64 i32) + (err i64 i32 code)) + +(fn is_ok_i64 ((value (result i64 i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_i64 ((value (result i64 i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_i64 ((value (result i64 i32))) -> i64 + (std.result.unwrap_ok value)) + +(fn unwrap_err_i64 ((value (result i64 i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_i64 ((value (result i64 i32)) (fallback i64)) -> i64 + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + fallback)) + +(fn ok_or_none_i64 ((value (result i64 i32))) -> (option i64) + (if (std.result.is_ok value) + (some i64 (std.result.unwrap_ok value)) + (none i64))) + +(fn ok_u64 ((value u64)) -> (result u64 i32) + (ok u64 i32 value)) + +(fn err_u64 ((code i32)) -> (result u64 i32) + (err u64 i32 code)) + +(fn is_ok_u64 ((value (result u64 i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_u64 ((value (result u64 i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_u64 ((value (result u64 i32))) -> u64 + (std.result.unwrap_ok value)) + +(fn unwrap_err_u64 ((value (result u64 i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_u64 ((value (result u64 i32)) (fallback u64)) -> u64 + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + fallback)) + +(fn ok_or_none_u64 ((value (result u64 i32))) -> (option u64) + (if (std.result.is_ok value) + (some u64 (std.result.unwrap_ok value)) + (none u64))) + +(fn ok_string ((value string)) -> (result string i32) + (ok string i32 value)) + +(fn err_string ((code i32)) -> (result string i32) + (err string i32 code)) + +(fn is_ok_string ((value (result string i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_string ((value (result string i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_string ((value (result string i32))) -> string + (std.result.unwrap_ok value)) + +(fn unwrap_err_string ((value (result string i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_string ((value (result string i32)) (fallback string)) -> string + (match value + ((ok payload) + payload) + ((err code) + fallback))) + +(fn ok_or_none_string ((value (result string i32))) -> (option string) + (if (std.result.is_ok value) + (some string (std.result.unwrap_ok value)) + (none string))) + +(fn ok_f64 ((value f64)) -> (result f64 i32) + (ok f64 i32 value)) + +(fn err_f64 ((code i32)) -> (result f64 i32) + (err f64 i32 code)) + +(fn is_ok_f64 ((value (result f64 i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_f64 ((value (result f64 i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_f64 ((value (result f64 i32))) -> f64 + (std.result.unwrap_ok value)) + +(fn unwrap_err_f64 ((value (result f64 i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_f64 ((value (result f64 i32)) (fallback f64)) -> f64 + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + fallback)) + +(fn ok_or_none_f64 ((value (result f64 i32))) -> (option f64) + (if (std.result.is_ok value) + (some f64 (std.result.unwrap_ok value)) + (none f64))) + +(fn ok_bool ((value bool)) -> (result bool i32) + (ok bool i32 value)) + +(fn err_bool ((code i32)) -> (result bool i32) + (err bool i32 code)) + +(fn is_ok_bool ((value (result bool i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_bool ((value (result bool i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_bool ((value (result bool i32))) -> bool + (std.result.unwrap_ok value)) + +(fn unwrap_err_bool ((value (result bool i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_bool ((value (result bool i32)) (fallback bool)) -> bool + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + fallback)) + +(fn ok_or_none_bool ((value (result bool i32))) -> (option bool) + (if (std.result.is_ok value) + (some bool (std.result.unwrap_ok value)) + (none bool))) diff --git a/examples/projects/std-layout-local-cli/src/string.slo b/examples/projects/std-layout-local-cli/src/string.slo new file mode 100644 index 0000000..6191345 --- /dev/null +++ b/examples/projects/std-layout-local-cli/src/string.slo @@ -0,0 +1,129 @@ +(module string (export len concat parse_i32_result parse_i32_option parse_u32_result parse_u32_option parse_i64_result parse_i64_option parse_u64_result parse_u64_option parse_f64_result parse_f64_option parse_bool_result parse_bool_option parse_i32_or_zero parse_u32_or_zero parse_i64_or_zero parse_u64_or_zero parse_f64_or_zero parse_bool_or_false parse_i32_or parse_u32_or parse_i64_or parse_u64_or parse_f64_or parse_bool_or)) + +(import result (ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool)) + +(fn len ((value string)) -> i32 + (std.string.len value)) + +(fn concat ((left string) (right string)) -> string + (std.string.concat left right)) + +(fn parse_i32_result ((value string)) -> (result i32 i32) + (std.string.parse_i32_result value)) + +(fn parse_i32_option ((value string)) -> (option i32) + (ok_or_none_i32 (parse_i32_result value))) + +(fn parse_u32_result ((value string)) -> (result u32 i32) + (std.string.parse_u32_result value)) + +(fn parse_u32_option ((value string)) -> (option u32) + (ok_or_none_u32 (parse_u32_result value))) + +(fn parse_i64_result ((value string)) -> (result i64 i32) + (std.string.parse_i64_result value)) + +(fn parse_i64_option ((value string)) -> (option i64) + (ok_or_none_i64 (parse_i64_result value))) + +(fn parse_u64_result ((value string)) -> (result u64 i32) + (std.string.parse_u64_result value)) + +(fn parse_u64_option ((value string)) -> (option u64) + (ok_or_none_u64 (parse_u64_result value))) + +(fn parse_f64_result ((value string)) -> (result f64 i32) + (std.string.parse_f64_result value)) + +(fn parse_f64_option ((value string)) -> (option f64) + (ok_or_none_f64 (parse_f64_result value))) + +(fn parse_bool_result ((value string)) -> (result bool i32) + (std.string.parse_bool_result value)) + +(fn parse_bool_option ((value string)) -> (option bool) + (ok_or_none_bool (parse_bool_result value))) + +(fn parse_i32_or_zero ((value string)) -> i32 + (match (parse_i32_result value) + ((ok payload) + payload) + ((err code) + 0))) + +(fn parse_u32_or_zero ((value string)) -> u32 + (match (parse_u32_result value) + ((ok payload) + payload) + ((err code) + 0u32))) + +(fn parse_i64_or_zero ((value string)) -> i64 + (match (parse_i64_result value) + ((ok payload) + payload) + ((err code) + 0i64))) + +(fn parse_u64_or_zero ((value string)) -> u64 + (match (parse_u64_result value) + ((ok payload) + payload) + ((err code) + 0u64))) + +(fn parse_f64_or_zero ((value string)) -> f64 + (match (parse_f64_result value) + ((ok payload) + payload) + ((err code) + 0.0))) + +(fn parse_bool_or_false ((value string)) -> bool + (match (parse_bool_result value) + ((ok payload) + payload) + ((err code) + false))) + +(fn parse_i32_or ((value string) (fallback i32)) -> i32 + (match (parse_i32_result value) + ((ok payload) + payload) + ((err code) + fallback))) + +(fn parse_u32_or ((value string) (fallback u32)) -> u32 + (match (parse_u32_result value) + ((ok payload) + payload) + ((err code) + fallback))) + +(fn parse_i64_or ((value string) (fallback i64)) -> i64 + (match (parse_i64_result value) + ((ok payload) + payload) + ((err code) + fallback))) + +(fn parse_u64_or ((value string) (fallback u64)) -> u64 + (match (parse_u64_result value) + ((ok payload) + payload) + ((err code) + fallback))) + +(fn parse_f64_or ((value string) (fallback f64)) -> f64 + (match (parse_f64_result value) + ((ok payload) + payload) + ((err code) + fallback))) + +(fn parse_bool_or ((value string) (fallback bool)) -> bool + (match (parse_bool_result value) + ((ok payload) + payload) + ((err code) + fallback))) diff --git a/examples/projects/std-layout-local-env/slovo.toml b/examples/projects/std-layout-local-env/slovo.toml new file mode 100644 index 0000000..0fe5b80 --- /dev/null +++ b/examples/projects/std-layout-local-env/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-layout-local-env" +source_root = "src" +entry = "main" diff --git a/examples/projects/std-layout-local-env/src/env.slo b/examples/projects/std-layout-local-env/src/env.slo new file mode 100644 index 0000000..fd210e5 --- /dev/null +++ b/examples/projects/std-layout-local-env/src/env.slo @@ -0,0 +1,172 @@ +(module env (export get get_result get_option has get_or get_i32_result get_i32_option get_i32_or_zero get_i32_or get_u32_result get_u32_option get_u32_or_zero get_u32_or get_i64_result get_i64_option get_i64_or_zero get_i64_or get_u64_result get_u64_option get_u64_or_zero get_u64_or get_f64_result get_f64_option get_f64_or_zero get_f64_or get_bool_result get_bool_option get_bool_or_false get_bool_or)) + +(import result (ok_or_none_string ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool)) + +(import string (parse_i32_result parse_u32_result parse_i64_result parse_u64_result parse_f64_result parse_bool_result)) + +(fn get ((name string)) -> string + (std.env.get name)) + +(fn get_result ((name string)) -> (result string i32) + (std.env.get_result name)) + +(fn get_option ((name string)) -> (option string) + (ok_or_none_string (get_result name))) + +(fn has ((name string)) -> bool + (match (get_result name) + ((ok payload) + true) + ((err code) + false))) + +(fn get_or ((name string) (fallback string)) -> string + (match (get_result name) + ((ok payload) + payload) + ((err code) + fallback))) + +(fn get_i32_result ((name string)) -> (result i32 i32) + (match (get_result name) + ((ok text) + (parse_i32_result text)) + ((err code) + (err i32 i32 code)))) + +(fn get_i32_option ((name string)) -> (option i32) + (ok_or_none_i32 (get_i32_result name))) + +(fn get_i32_or_zero ((name string)) -> i32 + (match (get_i32_result name) + ((ok value) + value) + ((err code) + 0))) + +(fn get_i32_or ((name string) (fallback i32)) -> i32 + (match (get_i32_result name) + ((ok value) + value) + ((err code) + fallback))) + +(fn get_u32_result ((name string)) -> (result u32 i32) + (match (get_result name) + ((ok text) + (parse_u32_result text)) + ((err code) + (err u32 i32 code)))) + +(fn get_u32_option ((name string)) -> (option u32) + (ok_or_none_u32 (get_u32_result name))) + +(fn get_u32_or_zero ((name string)) -> u32 + (match (get_u32_result name) + ((ok value) + value) + ((err code) + 0u32))) + +(fn get_u32_or ((name string) (fallback u32)) -> u32 + (match (get_u32_result name) + ((ok value) + value) + ((err code) + fallback))) + +(fn get_i64_result ((name string)) -> (result i64 i32) + (match (get_result name) + ((ok text) + (parse_i64_result text)) + ((err code) + (err i64 i32 code)))) + +(fn get_i64_option ((name string)) -> (option i64) + (ok_or_none_i64 (get_i64_result name))) + +(fn get_i64_or_zero ((name string)) -> i64 + (match (get_i64_result name) + ((ok value) + value) + ((err code) + 0i64))) + +(fn get_i64_or ((name string) (fallback i64)) -> i64 + (match (get_i64_result name) + ((ok value) + value) + ((err code) + fallback))) + +(fn get_u64_result ((name string)) -> (result u64 i32) + (match (get_result name) + ((ok text) + (parse_u64_result text)) + ((err code) + (err u64 i32 code)))) + +(fn get_u64_option ((name string)) -> (option u64) + (ok_or_none_u64 (get_u64_result name))) + +(fn get_u64_or_zero ((name string)) -> u64 + (match (get_u64_result name) + ((ok value) + value) + ((err code) + 0u64))) + +(fn get_u64_or ((name string) (fallback u64)) -> u64 + (match (get_u64_result name) + ((ok value) + value) + ((err code) + fallback))) + +(fn get_f64_result ((name string)) -> (result f64 i32) + (match (get_result name) + ((ok text) + (parse_f64_result text)) + ((err code) + (err f64 i32 code)))) + +(fn get_f64_option ((name string)) -> (option f64) + (ok_or_none_f64 (get_f64_result name))) + +(fn get_f64_or_zero ((name string)) -> f64 + (match (get_f64_result name) + ((ok value) + value) + ((err code) + 0.0))) + +(fn get_f64_or ((name string) (fallback f64)) -> f64 + (match (get_f64_result name) + ((ok value) + value) + ((err code) + fallback))) + +(fn get_bool_result ((name string)) -> (result bool i32) + (match (get_result name) + ((ok text) + (parse_bool_result text)) + ((err code) + (err bool i32 code)))) + +(fn get_bool_option ((name string)) -> (option bool) + (ok_or_none_bool (get_bool_result name))) + +(fn get_bool_or_false ((name string)) -> bool + (match (get_bool_result name) + ((ok value) + value) + ((err code) + false))) + +(fn get_bool_or ((name string) (fallback bool)) -> bool + (match (get_bool_result name) + ((ok value) + value) + ((err code) + fallback))) diff --git a/examples/projects/std-layout-local-env/src/main.slo b/examples/projects/std-layout-local-env/src/main.slo new file mode 100644 index 0000000..75cb8aa --- /dev/null +++ b/examples/projects/std-layout-local-env/src/main.slo @@ -0,0 +1,364 @@ +(module main) + +(import env (get get_result get_option has get_or get_i32_result get_i32_option get_i32_or_zero get_i32_or get_u32_result get_u32_option get_u32_or_zero get_u32_or get_i64_result get_i64_option get_i64_or_zero get_i64_or get_u64_result get_u64_option get_u64_or_zero get_u64_or get_f64_result get_f64_option get_f64_or_zero get_f64_or get_bool_result get_bool_option get_bool_or_false get_bool_or)) + +(fn missing_env_name () -> string + "GLAGOL_STD_LAYOUT_LOCAL_ENV_ALPHA_UNLIKELY_MISSING") + +(fn present_env_name () -> string + "GLAGOL_STD_LAYOUT_LOCAL_ENV_ALPHA_PRESENT") + +(fn present_env_value () -> string + "glagol-std-layout-local-env-alpha-value") + +(fn present_i32_env_name () -> string + "GLAGOL_STD_LAYOUT_LOCAL_ENV_ALPHA_PRESENT_I32") + +(fn present_i64_env_name () -> string + "GLAGOL_STD_LAYOUT_LOCAL_ENV_ALPHA_PRESENT_I64") + +(fn present_u32_env_name () -> string + "GLAGOL_STD_LAYOUT_LOCAL_ENV_ALPHA_PRESENT_U32") + +(fn present_u64_env_name () -> string + "GLAGOL_STD_LAYOUT_LOCAL_ENV_ALPHA_PRESENT_U64") + +(fn present_f64_env_name () -> string + "GLAGOL_STD_LAYOUT_LOCAL_ENV_ALPHA_PRESENT_F64") + +(fn present_bool_env_name () -> string + "GLAGOL_STD_LAYOUT_LOCAL_ENV_ALPHA_PRESENT_BOOL") + +(fn invalid_env_name () -> string + "GLAGOL_STD_LAYOUT_LOCAL_ENV_ALPHA_INVALID") + +(fn imported_env_missing_get_empty () -> bool + (= (get (missing_env_name)) "")) + +(fn imported_env_missing_result_err_one () -> bool + (match (get_result (missing_env_name)) + ((ok payload) + false) + ((err code) + (= code 1)))) + +(fn imported_env_has_missing_false () -> bool + (if (has (missing_env_name)) + false + true)) + +(fn imported_env_get_or_missing_fallback () -> bool + (= (get_or (missing_env_name) "fallback") "fallback")) + +(fn imported_env_get_option_missing_none () -> bool + (match (get_option (missing_env_name)) + ((some payload) + false) + ((none) + true))) + +(fn imported_env_has_present_true () -> bool + (has (present_env_name))) + +(fn imported_env_get_or_present_value () -> bool + (if (= (get_or (present_env_name) "fallback") (present_env_value)) + (= (get (present_env_name)) (present_env_value)) + false)) + +(fn imported_env_get_option_present_some () -> bool + (match (get_option (present_env_name)) + ((some payload) + (= payload (present_env_value))) + ((none) + false))) + +(fn imported_env_get_i32_result_present () -> bool + (match (get_i32_result (present_i32_env_name)) + ((ok value) + (= value 42)) + ((err code) + false))) + +(fn imported_env_get_i32_or_zero_invalid () -> bool + (= (get_i32_or_zero (invalid_env_name)) 0)) + +(fn imported_env_get_u32_result_present () -> bool + (match (get_u32_result (present_u32_env_name)) + ((ok value) + (= value 42u32)) + ((err code) + false))) + +(fn imported_env_get_u32_or_zero_invalid () -> bool + (= (get_u32_or_zero (invalid_env_name)) 0u32)) + +(fn imported_env_get_i64_result_present () -> bool + (match (get_i64_result (present_i64_env_name)) + ((ok value) + (= value 42000000000i64)) + ((err code) + false))) + +(fn imported_env_get_i64_or_zero_missing () -> bool + (= (get_i64_or_zero (missing_env_name)) 0i64)) + +(fn imported_env_get_u64_result_present () -> bool + (match (get_u64_result (present_u64_env_name)) + ((ok value) + (= value 4294967296u64)) + ((err code) + false))) + +(fn imported_env_get_u64_or_zero_missing () -> bool + (= (get_u64_or_zero (missing_env_name)) 0u64)) + +(fn imported_env_get_f64_result_present () -> bool + (match (get_f64_result (present_f64_env_name)) + ((ok value) + (= value 42.5)) + ((err code) + false))) + +(fn imported_env_get_f64_or_zero_invalid () -> bool + (= (get_f64_or_zero (invalid_env_name)) 0.0)) + +(fn imported_env_get_bool_result_present () -> bool + (match (get_bool_result (present_bool_env_name)) + ((ok value) + value) + ((err code) + false))) + +(fn imported_env_get_bool_or_false_invalid () -> bool + (if (get_bool_or_false (invalid_env_name)) + false + true)) + +(fn imported_env_typed_options_ok () -> bool + (let i32_present bool (match (get_i32_option (present_i32_env_name)) + ((some payload) + (= payload 42)) + ((none) + false))) + (let i32_invalid bool (match (get_i32_option (invalid_env_name)) + ((some payload) + false) + ((none) + true))) + (let u32_present bool (match (get_u32_option (present_u32_env_name)) + ((some payload) + (= payload 42u32)) + ((none) + false))) + (let u32_invalid bool (match (get_u32_option (invalid_env_name)) + ((some payload) + false) + ((none) + true))) + (let i64_present bool (match (get_i64_option (present_i64_env_name)) + ((some payload) + (= payload 42000000000i64)) + ((none) + false))) + (let i64_missing bool (match (get_i64_option (missing_env_name)) + ((some payload) + false) + ((none) + true))) + (let u64_present bool (match (get_u64_option (present_u64_env_name)) + ((some payload) + (= payload 4294967296u64)) + ((none) + false))) + (let u64_missing bool (match (get_u64_option (missing_env_name)) + ((some payload) + false) + ((none) + true))) + (let f64_present bool (match (get_f64_option (present_f64_env_name)) + ((some payload) + (= payload 42.5)) + ((none) + false))) + (let f64_invalid bool (match (get_f64_option (invalid_env_name)) + ((some payload) + false) + ((none) + true))) + (let bool_present bool (match (get_bool_option (present_bool_env_name)) + ((some payload) + payload) + ((none) + false))) + (let bool_invalid bool (match (get_bool_option (invalid_env_name)) + ((some payload) + false) + ((none) + true))) + (if i32_present + (if i32_invalid + (if u32_present + (if u32_invalid + (if i64_present + (if i64_missing + (if u64_present + (if u64_missing + (if f64_present + (if f64_invalid + (if bool_present + bool_invalid + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_env_typed_custom_fallbacks_ok () -> bool + (if (= (get_i32_or (present_i32_env_name) 7) 42) + (if (= (get_i32_or (invalid_env_name) 7) 7) + (if (= (get_u32_or (present_u32_env_name) 7u32) 42u32) + (if (= (get_u32_or (invalid_env_name) 7u32) 7u32) + (if (= (get_i64_or (present_i64_env_name) 9i64) 42000000000i64) + (if (= (get_i64_or (invalid_env_name) 9i64) 9i64) + (if (= (get_u64_or (present_u64_env_name) 9u64) 4294967296u64) + (if (= (get_u64_or (invalid_env_name) 9u64) 9u64) + (if (= (get_f64_or (present_f64_env_name) 1.5) 42.5) + (if (= (get_f64_or (invalid_env_name) 1.5) 1.5) + (if (get_bool_or (present_bool_env_name) false) + (get_bool_or (invalid_env_name) true) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_env_facade_ok () -> bool + (if (imported_env_missing_get_empty) + (if (imported_env_missing_result_err_one) + (if (imported_env_has_missing_false) + (if (imported_env_get_or_missing_fallback) + (if (imported_env_get_option_missing_none) + (if (imported_env_has_present_true) + (if (imported_env_get_or_present_value) + (if (imported_env_get_option_present_some) + (if (imported_env_get_i32_result_present) + (if (imported_env_get_i32_or_zero_invalid) + (if (imported_env_get_u32_result_present) + (if (imported_env_get_u32_or_zero_invalid) + (if (imported_env_get_i64_result_present) + (if (imported_env_get_i64_or_zero_missing) + (if (imported_env_get_u64_result_present) + (if (imported_env_get_u64_or_zero_missing) + (if (imported_env_get_f64_result_present) + (if (imported_env_get_f64_or_zero_invalid) + (if (imported_env_get_bool_result_present) + (if (imported_env_get_bool_or_false_invalid) + (if (imported_env_typed_options_ok) + (imported_env_typed_custom_fallbacks_ok) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn main () -> i32 + (if (imported_env_facade_ok) + 42 + 1)) + +(test "explicit local env get missing facade" + (imported_env_missing_get_empty)) + +(test "explicit local env get result missing facade" + (imported_env_missing_result_err_one)) + +(test "explicit local env has missing facade" + (imported_env_has_missing_false)) + +(test "explicit local env get or missing facade" + (imported_env_get_or_missing_fallback)) + +(test "explicit local env get option missing facade" + (imported_env_get_option_missing_none)) + +(test "explicit local env has present facade" + (imported_env_has_present_true)) + +(test "explicit local env get or present facade" + (imported_env_get_or_present_value)) + +(test "explicit local env get option present facade" + (imported_env_get_option_present_some)) + +(test "explicit local env get i32 result present facade" + (imported_env_get_i32_result_present)) + +(test "explicit local env get i32 or zero invalid facade" + (imported_env_get_i32_or_zero_invalid)) + +(test "explicit local env get u32 result present facade" + (imported_env_get_u32_result_present)) + +(test "explicit local env get u32 or zero invalid facade" + (imported_env_get_u32_or_zero_invalid)) + +(test "explicit local env get i64 result present facade" + (imported_env_get_i64_result_present)) + +(test "explicit local env get i64 or zero missing facade" + (imported_env_get_i64_or_zero_missing)) + +(test "explicit local env get u64 result present facade" + (imported_env_get_u64_result_present)) + +(test "explicit local env get u64 or zero missing facade" + (imported_env_get_u64_or_zero_missing)) + +(test "explicit local env get f64 result present facade" + (imported_env_get_f64_result_present)) + +(test "explicit local env get f64 or zero invalid facade" + (imported_env_get_f64_or_zero_invalid)) + +(test "explicit local env get bool result present facade" + (imported_env_get_bool_result_present)) + +(test "explicit local env get bool or false invalid facade" + (imported_env_get_bool_or_false_invalid)) + +(test "explicit local env typed option facade" + (imported_env_typed_options_ok)) + +(test "explicit local env typed custom fallback facade" + (imported_env_typed_custom_fallbacks_ok)) + +(test "explicit local env facade all" + (= (main) 42)) diff --git a/examples/projects/std-layout-local-env/src/result.slo b/examples/projects/std-layout-local-env/src/result.slo new file mode 100644 index 0000000..4a7b77b --- /dev/null +++ b/examples/projects/std-layout-local-env/src/result.slo @@ -0,0 +1,199 @@ +(module result (export ok_i32 err_i32 is_ok_i32 is_err_i32 unwrap_ok_i32 unwrap_err_i32 unwrap_or_i32 ok_or_none_i32 ok_u32 err_u32 is_ok_u32 is_err_u32 unwrap_ok_u32 unwrap_err_u32 unwrap_or_u32 ok_or_none_u32 ok_i64 err_i64 is_ok_i64 is_err_i64 unwrap_ok_i64 unwrap_err_i64 unwrap_or_i64 ok_or_none_i64 ok_u64 err_u64 is_ok_u64 is_err_u64 unwrap_ok_u64 unwrap_err_u64 unwrap_or_u64 ok_or_none_u64 ok_string err_string is_ok_string is_err_string unwrap_ok_string unwrap_err_string unwrap_or_string ok_or_none_string ok_f64 err_f64 is_ok_f64 is_err_f64 unwrap_ok_f64 unwrap_err_f64 unwrap_or_f64 ok_or_none_f64 ok_bool err_bool is_ok_bool is_err_bool unwrap_ok_bool unwrap_err_bool unwrap_or_bool ok_or_none_bool)) + +(fn ok_i32 ((value i32)) -> (result i32 i32) + (ok i32 i32 value)) + +(fn err_i32 ((code i32)) -> (result i32 i32) + (err i32 i32 code)) + +(fn is_ok_i32 ((value (result i32 i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_i32 ((value (result i32 i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_i32 ((value (result i32 i32))) -> i32 + (std.result.unwrap_ok value)) + +(fn unwrap_err_i32 ((value (result i32 i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_i32 ((value (result i32 i32)) (fallback i32)) -> i32 + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + fallback)) + +(fn ok_or_none_i32 ((value (result i32 i32))) -> (option i32) + (if (std.result.is_ok value) + (some i32 (std.result.unwrap_ok value)) + (none i32))) + +(fn ok_u32 ((value u32)) -> (result u32 i32) + (ok u32 i32 value)) + +(fn err_u32 ((code i32)) -> (result u32 i32) + (err u32 i32 code)) + +(fn is_ok_u32 ((value (result u32 i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_u32 ((value (result u32 i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_u32 ((value (result u32 i32))) -> u32 + (std.result.unwrap_ok value)) + +(fn unwrap_err_u32 ((value (result u32 i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_u32 ((value (result u32 i32)) (fallback u32)) -> u32 + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + fallback)) + +(fn ok_or_none_u32 ((value (result u32 i32))) -> (option u32) + (if (std.result.is_ok value) + (some u32 (std.result.unwrap_ok value)) + (none u32))) + +(fn ok_i64 ((value i64)) -> (result i64 i32) + (ok i64 i32 value)) + +(fn err_i64 ((code i32)) -> (result i64 i32) + (err i64 i32 code)) + +(fn is_ok_i64 ((value (result i64 i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_i64 ((value (result i64 i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_i64 ((value (result i64 i32))) -> i64 + (std.result.unwrap_ok value)) + +(fn unwrap_err_i64 ((value (result i64 i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_i64 ((value (result i64 i32)) (fallback i64)) -> i64 + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + fallback)) + +(fn ok_or_none_i64 ((value (result i64 i32))) -> (option i64) + (if (std.result.is_ok value) + (some i64 (std.result.unwrap_ok value)) + (none i64))) + +(fn ok_u64 ((value u64)) -> (result u64 i32) + (ok u64 i32 value)) + +(fn err_u64 ((code i32)) -> (result u64 i32) + (err u64 i32 code)) + +(fn is_ok_u64 ((value (result u64 i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_u64 ((value (result u64 i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_u64 ((value (result u64 i32))) -> u64 + (std.result.unwrap_ok value)) + +(fn unwrap_err_u64 ((value (result u64 i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_u64 ((value (result u64 i32)) (fallback u64)) -> u64 + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + fallback)) + +(fn ok_or_none_u64 ((value (result u64 i32))) -> (option u64) + (if (std.result.is_ok value) + (some u64 (std.result.unwrap_ok value)) + (none u64))) + +(fn ok_string ((value string)) -> (result string i32) + (ok string i32 value)) + +(fn err_string ((code i32)) -> (result string i32) + (err string i32 code)) + +(fn is_ok_string ((value (result string i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_string ((value (result string i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_string ((value (result string i32))) -> string + (std.result.unwrap_ok value)) + +(fn unwrap_err_string ((value (result string i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_string ((value (result string i32)) (fallback string)) -> string + (match value + ((ok payload) + payload) + ((err code) + fallback))) + +(fn ok_or_none_string ((value (result string i32))) -> (option string) + (if (std.result.is_ok value) + (some string (std.result.unwrap_ok value)) + (none string))) + +(fn ok_f64 ((value f64)) -> (result f64 i32) + (ok f64 i32 value)) + +(fn err_f64 ((code i32)) -> (result f64 i32) + (err f64 i32 code)) + +(fn is_ok_f64 ((value (result f64 i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_f64 ((value (result f64 i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_f64 ((value (result f64 i32))) -> f64 + (std.result.unwrap_ok value)) + +(fn unwrap_err_f64 ((value (result f64 i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_f64 ((value (result f64 i32)) (fallback f64)) -> f64 + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + fallback)) + +(fn ok_or_none_f64 ((value (result f64 i32))) -> (option f64) + (if (std.result.is_ok value) + (some f64 (std.result.unwrap_ok value)) + (none f64))) + +(fn ok_bool ((value bool)) -> (result bool i32) + (ok bool i32 value)) + +(fn err_bool ((code i32)) -> (result bool i32) + (err bool i32 code)) + +(fn is_ok_bool ((value (result bool i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_bool ((value (result bool i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_bool ((value (result bool i32))) -> bool + (std.result.unwrap_ok value)) + +(fn unwrap_err_bool ((value (result bool i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_bool ((value (result bool i32)) (fallback bool)) -> bool + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + fallback)) + +(fn ok_or_none_bool ((value (result bool i32))) -> (option bool) + (if (std.result.is_ok value) + (some bool (std.result.unwrap_ok value)) + (none bool))) diff --git a/examples/projects/std-layout-local-env/src/string.slo b/examples/projects/std-layout-local-env/src/string.slo new file mode 100644 index 0000000..6191345 --- /dev/null +++ b/examples/projects/std-layout-local-env/src/string.slo @@ -0,0 +1,129 @@ +(module string (export len concat parse_i32_result parse_i32_option parse_u32_result parse_u32_option parse_i64_result parse_i64_option parse_u64_result parse_u64_option parse_f64_result parse_f64_option parse_bool_result parse_bool_option parse_i32_or_zero parse_u32_or_zero parse_i64_or_zero parse_u64_or_zero parse_f64_or_zero parse_bool_or_false parse_i32_or parse_u32_or parse_i64_or parse_u64_or parse_f64_or parse_bool_or)) + +(import result (ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool)) + +(fn len ((value string)) -> i32 + (std.string.len value)) + +(fn concat ((left string) (right string)) -> string + (std.string.concat left right)) + +(fn parse_i32_result ((value string)) -> (result i32 i32) + (std.string.parse_i32_result value)) + +(fn parse_i32_option ((value string)) -> (option i32) + (ok_or_none_i32 (parse_i32_result value))) + +(fn parse_u32_result ((value string)) -> (result u32 i32) + (std.string.parse_u32_result value)) + +(fn parse_u32_option ((value string)) -> (option u32) + (ok_or_none_u32 (parse_u32_result value))) + +(fn parse_i64_result ((value string)) -> (result i64 i32) + (std.string.parse_i64_result value)) + +(fn parse_i64_option ((value string)) -> (option i64) + (ok_or_none_i64 (parse_i64_result value))) + +(fn parse_u64_result ((value string)) -> (result u64 i32) + (std.string.parse_u64_result value)) + +(fn parse_u64_option ((value string)) -> (option u64) + (ok_or_none_u64 (parse_u64_result value))) + +(fn parse_f64_result ((value string)) -> (result f64 i32) + (std.string.parse_f64_result value)) + +(fn parse_f64_option ((value string)) -> (option f64) + (ok_or_none_f64 (parse_f64_result value))) + +(fn parse_bool_result ((value string)) -> (result bool i32) + (std.string.parse_bool_result value)) + +(fn parse_bool_option ((value string)) -> (option bool) + (ok_or_none_bool (parse_bool_result value))) + +(fn parse_i32_or_zero ((value string)) -> i32 + (match (parse_i32_result value) + ((ok payload) + payload) + ((err code) + 0))) + +(fn parse_u32_or_zero ((value string)) -> u32 + (match (parse_u32_result value) + ((ok payload) + payload) + ((err code) + 0u32))) + +(fn parse_i64_or_zero ((value string)) -> i64 + (match (parse_i64_result value) + ((ok payload) + payload) + ((err code) + 0i64))) + +(fn parse_u64_or_zero ((value string)) -> u64 + (match (parse_u64_result value) + ((ok payload) + payload) + ((err code) + 0u64))) + +(fn parse_f64_or_zero ((value string)) -> f64 + (match (parse_f64_result value) + ((ok payload) + payload) + ((err code) + 0.0))) + +(fn parse_bool_or_false ((value string)) -> bool + (match (parse_bool_result value) + ((ok payload) + payload) + ((err code) + false))) + +(fn parse_i32_or ((value string) (fallback i32)) -> i32 + (match (parse_i32_result value) + ((ok payload) + payload) + ((err code) + fallback))) + +(fn parse_u32_or ((value string) (fallback u32)) -> u32 + (match (parse_u32_result value) + ((ok payload) + payload) + ((err code) + fallback))) + +(fn parse_i64_or ((value string) (fallback i64)) -> i64 + (match (parse_i64_result value) + ((ok payload) + payload) + ((err code) + fallback))) + +(fn parse_u64_or ((value string) (fallback u64)) -> u64 + (match (parse_u64_result value) + ((ok payload) + payload) + ((err code) + fallback))) + +(fn parse_f64_or ((value string) (fallback f64)) -> f64 + (match (parse_f64_result value) + ((ok payload) + payload) + ((err code) + fallback))) + +(fn parse_bool_or ((value string) (fallback bool)) -> bool + (match (parse_bool_result value) + ((ok payload) + payload) + ((err code) + fallback))) diff --git a/examples/projects/std-layout-local-fs/slovo.toml b/examples/projects/std-layout-local-fs/slovo.toml new file mode 100644 index 0000000..76945b4 --- /dev/null +++ b/examples/projects/std-layout-local-fs/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-layout-local-fs" +source_root = "src" +entry = "main" diff --git a/examples/projects/std-layout-local-fs/src/fs.slo b/examples/projects/std-layout-local-fs/src/fs.slo new file mode 100644 index 0000000..bf3852b --- /dev/null +++ b/examples/projects/std-layout-local-fs/src/fs.slo @@ -0,0 +1,178 @@ +(module fs (export read_text read_text_result read_text_option write_text_status write_text_result read_text_or write_text_ok read_i32_result read_i32_option read_i32_or_zero read_i32_or read_u32_result read_u32_option read_u32_or_zero read_u32_or read_i64_result read_i64_option read_i64_or_zero read_i64_or read_u64_result read_u64_option read_u64_or_zero read_u64_or read_f64_result read_f64_option read_f64_or_zero read_f64_or read_bool_result read_bool_option read_bool_or_false read_bool_or)) + +(import result (ok_or_none_string ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool)) + +(import string (parse_i32_result parse_u32_result parse_i64_result parse_u64_result parse_f64_result parse_bool_result)) + +(fn read_text ((path string)) -> string + (std.fs.read_text path)) + +(fn read_text_result ((path string)) -> (result string i32) + (std.fs.read_text_result path)) + +(fn read_text_option ((path string)) -> (option string) + (ok_or_none_string (read_text_result path))) + +(fn write_text_status ((path string) (text string)) -> i32 + (std.fs.write_text path text)) + +(fn write_text_result ((path string) (text string)) -> (result i32 i32) + (std.fs.write_text_result path text)) + +(fn read_text_or ((path string) (fallback string)) -> string + (match (read_text_result path) + ((ok text) + text) + ((err code) + fallback))) + +(fn write_text_ok ((path string) (text string)) -> bool + (match (write_text_result path text) + ((ok status) + true) + ((err code) + false))) + +(fn read_i32_result ((path string)) -> (result i32 i32) + (match (read_text_result path) + ((ok text) + (parse_i32_result text)) + ((err code) + (err i32 i32 code)))) + +(fn read_i32_option ((path string)) -> (option i32) + (ok_or_none_i32 (read_i32_result path))) + +(fn read_i32_or_zero ((path string)) -> i32 + (match (read_i32_result path) + ((ok value) + value) + ((err code) + 0))) + +(fn read_i32_or ((path string) (fallback i32)) -> i32 + (match (read_i32_result path) + ((ok value) + value) + ((err code) + fallback))) + +(fn read_u32_result ((path string)) -> (result u32 i32) + (match (read_text_result path) + ((ok text) + (parse_u32_result text)) + ((err code) + (err u32 i32 code)))) + +(fn read_u32_option ((path string)) -> (option u32) + (ok_or_none_u32 (read_u32_result path))) + +(fn read_u32_or_zero ((path string)) -> u32 + (match (read_u32_result path) + ((ok value) + value) + ((err code) + 0u32))) + +(fn read_u32_or ((path string) (fallback u32)) -> u32 + (match (read_u32_result path) + ((ok value) + value) + ((err code) + fallback))) + +(fn read_i64_result ((path string)) -> (result i64 i32) + (match (read_text_result path) + ((ok text) + (parse_i64_result text)) + ((err code) + (err i64 i32 code)))) + +(fn read_i64_option ((path string)) -> (option i64) + (ok_or_none_i64 (read_i64_result path))) + +(fn read_i64_or_zero ((path string)) -> i64 + (match (read_i64_result path) + ((ok value) + value) + ((err code) + 0i64))) + +(fn read_i64_or ((path string) (fallback i64)) -> i64 + (match (read_i64_result path) + ((ok value) + value) + ((err code) + fallback))) + +(fn read_u64_result ((path string)) -> (result u64 i32) + (match (read_text_result path) + ((ok text) + (parse_u64_result text)) + ((err code) + (err u64 i32 code)))) + +(fn read_u64_option ((path string)) -> (option u64) + (ok_or_none_u64 (read_u64_result path))) + +(fn read_u64_or_zero ((path string)) -> u64 + (match (read_u64_result path) + ((ok value) + value) + ((err code) + 0u64))) + +(fn read_u64_or ((path string) (fallback u64)) -> u64 + (match (read_u64_result path) + ((ok value) + value) + ((err code) + fallback))) + +(fn read_f64_result ((path string)) -> (result f64 i32) + (match (read_text_result path) + ((ok text) + (parse_f64_result text)) + ((err code) + (err f64 i32 code)))) + +(fn read_f64_option ((path string)) -> (option f64) + (ok_or_none_f64 (read_f64_result path))) + +(fn read_f64_or_zero ((path string)) -> f64 + (match (read_f64_result path) + ((ok value) + value) + ((err code) + 0.0))) + +(fn read_f64_or ((path string) (fallback f64)) -> f64 + (match (read_f64_result path) + ((ok value) + value) + ((err code) + fallback))) + +(fn read_bool_result ((path string)) -> (result bool i32) + (match (read_text_result path) + ((ok text) + (parse_bool_result text)) + ((err code) + (err bool i32 code)))) + +(fn read_bool_option ((path string)) -> (option bool) + (ok_or_none_bool (read_bool_result path))) + +(fn read_bool_or_false ((path string)) -> bool + (match (read_bool_result path) + ((ok value) + value) + ((err code) + false))) + +(fn read_bool_or ((path string) (fallback bool)) -> bool + (match (read_bool_result path) + ((ok value) + value) + ((err code) + fallback))) diff --git a/examples/projects/std-layout-local-fs/src/main.slo b/examples/projects/std-layout-local-fs/src/main.slo new file mode 100644 index 0000000..2aaca83 --- /dev/null +++ b/examples/projects/std-layout-local-fs/src/main.slo @@ -0,0 +1,387 @@ +(module main) + +(import fs (read_text read_text_result read_text_option write_text_status write_text_result read_text_or write_text_ok read_i32_result read_i32_option read_i32_or_zero read_i32_or read_u32_result read_u32_option read_u32_or_zero read_u32_or read_i64_result read_i64_option read_i64_or_zero read_i64_or read_u64_result read_u64_option read_u64_or_zero read_u64_or read_f64_result read_f64_option read_f64_or_zero read_f64_or read_bool_result read_bool_option read_bool_or_false read_bool_or)) + +(fn fixture_path () -> string + "glagol-std-layout-local-fs-alpha.txt") + +(fn fixture_text () -> string + "std fs source search alpha") + +(fn missing_fixture_path () -> string + "glagol-std-layout-local-fs-alpha-missing.txt") + +(fn fallback_text () -> string + "std fs source fallback alpha") + +(fn i32_fixture_path () -> string + "glagol-std-layout-local-fs-alpha-i32.txt") + +(fn i64_fixture_path () -> string + "glagol-std-layout-local-fs-alpha-i64.txt") + +(fn u32_fixture_path () -> string + "glagol-std-layout-local-fs-alpha-u32.txt") + +(fn u64_fixture_path () -> string + "glagol-std-layout-local-fs-alpha-u64.txt") + +(fn f64_fixture_path () -> string + "glagol-std-layout-local-fs-alpha-f64.txt") + +(fn bool_fixture_path () -> string + "glagol-std-layout-local-fs-alpha-bool.txt") + +(fn invalid_fixture_path () -> string + "glagol-std-layout-local-fs-alpha-invalid.txt") + +(fn imported_write_text_status () -> i32 + (write_text_status (fixture_path) (fixture_text))) + +(fn imported_read_text () -> string + (write_text_status (fixture_path) (fixture_text)) + (read_text (fixture_path))) + +(fn imported_write_text_result () -> (result i32 i32) + (write_text_result (fixture_path) (fixture_text))) + +(fn imported_read_text_result () -> (result string i32) + (write_text_status (fixture_path) (fixture_text)) + (read_text_result (fixture_path))) + +(fn imported_read_text_option_missing_none () -> bool + (match (read_text_option (missing_fixture_path)) + ((some text) + false) + ((none) + true))) + +(fn imported_read_text_option_present_some () -> bool + (write_text_status (fixture_path) (fixture_text)) + (match (read_text_option (fixture_path)) + ((some text) + (= text (fixture_text))) + ((none) + false))) + +(fn imported_read_text_or_missing_fallback () -> bool + (= (read_text_or (missing_fixture_path) (fallback_text)) (fallback_text))) + +(fn imported_read_text_or_present_value () -> bool + (write_text_status (fixture_path) (fixture_text)) + (= (read_text_or (fixture_path) (fallback_text)) (fixture_text))) + +(fn imported_write_text_ok () -> bool + (write_text_ok (fixture_path) (fixture_text))) + +(fn imported_read_i32_result_present () -> bool + (write_text_status (i32_fixture_path) "42") + (match (read_i32_result (i32_fixture_path)) + ((ok value) + (= value 42)) + ((err code) + false))) + +(fn imported_read_i32_or_zero_invalid () -> bool + (write_text_status (invalid_fixture_path) "bad") + (= (read_i32_or_zero (invalid_fixture_path)) 0)) + +(fn imported_read_u32_result_present () -> bool + (write_text_status (u32_fixture_path) "42") + (match (read_u32_result (u32_fixture_path)) + ((ok value) + (= value 42u32)) + ((err code) + false))) + +(fn imported_read_u32_or_zero_invalid () -> bool + (write_text_status (invalid_fixture_path) "bad") + (= (read_u32_or_zero (invalid_fixture_path)) 0u32)) + +(fn imported_read_i64_result_present () -> bool + (write_text_status (i64_fixture_path) "42000000000") + (match (read_i64_result (i64_fixture_path)) + ((ok value) + (= value 42000000000i64)) + ((err code) + false))) + +(fn imported_read_i64_or_zero_missing () -> bool + (= (read_i64_or_zero (missing_fixture_path)) 0i64)) + +(fn imported_read_u64_result_present () -> bool + (write_text_status (u64_fixture_path) "4294967296") + (match (read_u64_result (u64_fixture_path)) + ((ok value) + (= value 4294967296u64)) + ((err code) + false))) + +(fn imported_read_u64_or_zero_missing () -> bool + (= (read_u64_or_zero (missing_fixture_path)) 0u64)) + +(fn imported_read_f64_result_present () -> bool + (write_text_status (f64_fixture_path) "42.5") + (match (read_f64_result (f64_fixture_path)) + ((ok value) + (= value 42.5)) + ((err code) + false))) + +(fn imported_read_f64_or_zero_invalid () -> bool + (write_text_status (invalid_fixture_path) "bad") + (= (read_f64_or_zero (invalid_fixture_path)) 0.0)) + +(fn imported_read_bool_result_present () -> bool + (write_text_status (bool_fixture_path) "true") + (match (read_bool_result (bool_fixture_path)) + ((ok value) + value) + ((err code) + false))) + +(fn imported_read_bool_or_false_invalid () -> bool + (write_text_status (invalid_fixture_path) "bad") + (if (read_bool_or_false (invalid_fixture_path)) + false + true)) + +(fn imported_typed_options_ok () -> bool + (write_text_status (i32_fixture_path) "42") + (write_text_status (u32_fixture_path) "42") + (write_text_status (i64_fixture_path) "42000000000") + (write_text_status (u64_fixture_path) "4294967296") + (write_text_status (f64_fixture_path) "42.5") + (write_text_status (bool_fixture_path) "true") + (write_text_status (invalid_fixture_path) "bad") + (if (match (read_i32_option (i32_fixture_path)) + ((some value) + (= value 42)) + ((none) + false)) + (if (match (read_i32_option (invalid_fixture_path)) + ((some value) + false) + ((none) + true)) + (if (match (read_u32_option (u32_fixture_path)) + ((some value) + (= value 42u32)) + ((none) + false)) + (if (match (read_u32_option (invalid_fixture_path)) + ((some value) + false) + ((none) + true)) + (if (match (read_i64_option (i64_fixture_path)) + ((some value) + (= value 42000000000i64)) + ((none) + false)) + (if (match (read_i64_option (missing_fixture_path)) + ((some value) + false) + ((none) + true)) + (if (match (read_u64_option (u64_fixture_path)) + ((some value) + (= value 4294967296u64)) + ((none) + false)) + (if (match (read_u64_option (missing_fixture_path)) + ((some value) + false) + ((none) + true)) + (if (match (read_f64_option (f64_fixture_path)) + ((some value) + (= value 42.5)) + ((none) + false)) + (if (match (read_f64_option (invalid_fixture_path)) + ((some value) + false) + ((none) + true)) + (if (match (read_bool_option (bool_fixture_path)) + ((some value) + value) + ((none) + false)) + (match (read_bool_option (invalid_fixture_path)) + ((some value) + false) + ((none) + true)) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_typed_custom_fallbacks_ok () -> bool + (write_text_status (i32_fixture_path) "42") + (write_text_status (u32_fixture_path) "42") + (write_text_status (i64_fixture_path) "42000000000") + (write_text_status (u64_fixture_path) "4294967296") + (write_text_status (f64_fixture_path) "42.5") + (write_text_status (bool_fixture_path) "true") + (write_text_status (invalid_fixture_path) "bad") + (if (= (read_i32_or (i32_fixture_path) 7) 42) + (if (= (read_i32_or (invalid_fixture_path) 7) 7) + (if (= (read_u32_or (u32_fixture_path) 7u32) 42u32) + (if (= (read_u32_or (invalid_fixture_path) 7u32) 7u32) + (if (= (read_i64_or (i64_fixture_path) 9i64) 42000000000i64) + (if (= (read_i64_or (invalid_fixture_path) 9i64) 9i64) + (if (= (read_u64_or (u64_fixture_path) 9u64) 4294967296u64) + (if (= (read_u64_or (invalid_fixture_path) 9u64) 9u64) + (if (= (read_f64_or (f64_fixture_path) 1.5) 42.5) + (if (= (read_f64_or (invalid_fixture_path) 1.5) 1.5) + (if (read_bool_or (bool_fixture_path) false) + (read_bool_or (invalid_fixture_path) true) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_status_roundtrip_ok () -> bool + (write_text_status (fixture_path) (fixture_text)) + (= (read_text (fixture_path)) (fixture_text))) + +(fn imported_result_roundtrip_ok () -> bool + (write_text_result (fixture_path) (fixture_text)) + (= (unwrap_ok (read_text_result (fixture_path))) (fixture_text))) + +(fn imported_fs_facade_ok () -> bool + (if (imported_status_roundtrip_ok) + (if (imported_result_roundtrip_ok) + (if (imported_read_text_option_missing_none) + (if (imported_read_text_option_present_some) + (if (imported_read_text_or_missing_fallback) + (if (imported_read_text_or_present_value) + (if (imported_write_text_ok) + (if (imported_read_i32_result_present) + (if (imported_read_i32_or_zero_invalid) + (if (imported_read_u32_result_present) + (if (imported_read_u32_or_zero_invalid) + (if (imported_read_i64_result_present) + (if (imported_read_i64_or_zero_missing) + (if (imported_read_u64_result_present) + (if (imported_read_u64_or_zero_missing) + (if (imported_read_f64_result_present) + (if (imported_read_f64_or_zero_invalid) + (if (imported_read_bool_result_present) + (if (imported_read_bool_or_false_invalid) + (if (imported_typed_options_ok) + (imported_typed_custom_fallbacks_ok) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn main () -> i32 + (if (imported_fs_facade_ok) + 42 + 1)) + +(test "explicit local fs write text status facade" + (= (imported_write_text_status) 0)) + +(test "explicit local fs read text facade" + (= (imported_read_text) (fixture_text))) + +(test "explicit local fs write text result facade" + (= (unwrap_ok (imported_write_text_result)) 0)) + +(test "explicit local fs read text result facade" + (= (unwrap_ok (imported_read_text_result)) (fixture_text))) + +(test "explicit local fs read text option missing facade" + (imported_read_text_option_missing_none)) + +(test "explicit local fs read text option present facade" + (imported_read_text_option_present_some)) + +(test "explicit local fs read text or missing facade" + (imported_read_text_or_missing_fallback)) + +(test "explicit local fs read text or present facade" + (imported_read_text_or_present_value)) + +(test "explicit local fs write text ok facade" + (imported_write_text_ok)) + +(test "explicit local fs read i32 result present facade" + (imported_read_i32_result_present)) + +(test "explicit local fs read i32 or zero invalid facade" + (imported_read_i32_or_zero_invalid)) + +(test "explicit local fs read u32 result present facade" + (imported_read_u32_result_present)) + +(test "explicit local fs read u32 or zero invalid facade" + (imported_read_u32_or_zero_invalid)) + +(test "explicit local fs read i64 result present facade" + (imported_read_i64_result_present)) + +(test "explicit local fs read i64 or zero missing facade" + (imported_read_i64_or_zero_missing)) + +(test "explicit local fs read u64 result present facade" + (imported_read_u64_result_present)) + +(test "explicit local fs read u64 or zero missing facade" + (imported_read_u64_or_zero_missing)) + +(test "explicit local fs read f64 result present facade" + (imported_read_f64_result_present)) + +(test "explicit local fs read f64 or zero invalid facade" + (imported_read_f64_or_zero_invalid)) + +(test "explicit local fs read bool result present facade" + (imported_read_bool_result_present)) + +(test "explicit local fs read bool or false invalid facade" + (imported_read_bool_or_false_invalid)) + +(test "explicit local fs typed option facade" + (imported_typed_options_ok)) + +(test "explicit local fs typed custom fallback facade" + (imported_typed_custom_fallbacks_ok)) + +(test "explicit local fs facade all" + (= (main) 42)) diff --git a/examples/projects/std-layout-local-fs/src/result.slo b/examples/projects/std-layout-local-fs/src/result.slo new file mode 100644 index 0000000..4a7b77b --- /dev/null +++ b/examples/projects/std-layout-local-fs/src/result.slo @@ -0,0 +1,199 @@ +(module result (export ok_i32 err_i32 is_ok_i32 is_err_i32 unwrap_ok_i32 unwrap_err_i32 unwrap_or_i32 ok_or_none_i32 ok_u32 err_u32 is_ok_u32 is_err_u32 unwrap_ok_u32 unwrap_err_u32 unwrap_or_u32 ok_or_none_u32 ok_i64 err_i64 is_ok_i64 is_err_i64 unwrap_ok_i64 unwrap_err_i64 unwrap_or_i64 ok_or_none_i64 ok_u64 err_u64 is_ok_u64 is_err_u64 unwrap_ok_u64 unwrap_err_u64 unwrap_or_u64 ok_or_none_u64 ok_string err_string is_ok_string is_err_string unwrap_ok_string unwrap_err_string unwrap_or_string ok_or_none_string ok_f64 err_f64 is_ok_f64 is_err_f64 unwrap_ok_f64 unwrap_err_f64 unwrap_or_f64 ok_or_none_f64 ok_bool err_bool is_ok_bool is_err_bool unwrap_ok_bool unwrap_err_bool unwrap_or_bool ok_or_none_bool)) + +(fn ok_i32 ((value i32)) -> (result i32 i32) + (ok i32 i32 value)) + +(fn err_i32 ((code i32)) -> (result i32 i32) + (err i32 i32 code)) + +(fn is_ok_i32 ((value (result i32 i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_i32 ((value (result i32 i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_i32 ((value (result i32 i32))) -> i32 + (std.result.unwrap_ok value)) + +(fn unwrap_err_i32 ((value (result i32 i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_i32 ((value (result i32 i32)) (fallback i32)) -> i32 + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + fallback)) + +(fn ok_or_none_i32 ((value (result i32 i32))) -> (option i32) + (if (std.result.is_ok value) + (some i32 (std.result.unwrap_ok value)) + (none i32))) + +(fn ok_u32 ((value u32)) -> (result u32 i32) + (ok u32 i32 value)) + +(fn err_u32 ((code i32)) -> (result u32 i32) + (err u32 i32 code)) + +(fn is_ok_u32 ((value (result u32 i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_u32 ((value (result u32 i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_u32 ((value (result u32 i32))) -> u32 + (std.result.unwrap_ok value)) + +(fn unwrap_err_u32 ((value (result u32 i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_u32 ((value (result u32 i32)) (fallback u32)) -> u32 + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + fallback)) + +(fn ok_or_none_u32 ((value (result u32 i32))) -> (option u32) + (if (std.result.is_ok value) + (some u32 (std.result.unwrap_ok value)) + (none u32))) + +(fn ok_i64 ((value i64)) -> (result i64 i32) + (ok i64 i32 value)) + +(fn err_i64 ((code i32)) -> (result i64 i32) + (err i64 i32 code)) + +(fn is_ok_i64 ((value (result i64 i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_i64 ((value (result i64 i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_i64 ((value (result i64 i32))) -> i64 + (std.result.unwrap_ok value)) + +(fn unwrap_err_i64 ((value (result i64 i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_i64 ((value (result i64 i32)) (fallback i64)) -> i64 + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + fallback)) + +(fn ok_or_none_i64 ((value (result i64 i32))) -> (option i64) + (if (std.result.is_ok value) + (some i64 (std.result.unwrap_ok value)) + (none i64))) + +(fn ok_u64 ((value u64)) -> (result u64 i32) + (ok u64 i32 value)) + +(fn err_u64 ((code i32)) -> (result u64 i32) + (err u64 i32 code)) + +(fn is_ok_u64 ((value (result u64 i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_u64 ((value (result u64 i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_u64 ((value (result u64 i32))) -> u64 + (std.result.unwrap_ok value)) + +(fn unwrap_err_u64 ((value (result u64 i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_u64 ((value (result u64 i32)) (fallback u64)) -> u64 + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + fallback)) + +(fn ok_or_none_u64 ((value (result u64 i32))) -> (option u64) + (if (std.result.is_ok value) + (some u64 (std.result.unwrap_ok value)) + (none u64))) + +(fn ok_string ((value string)) -> (result string i32) + (ok string i32 value)) + +(fn err_string ((code i32)) -> (result string i32) + (err string i32 code)) + +(fn is_ok_string ((value (result string i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_string ((value (result string i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_string ((value (result string i32))) -> string + (std.result.unwrap_ok value)) + +(fn unwrap_err_string ((value (result string i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_string ((value (result string i32)) (fallback string)) -> string + (match value + ((ok payload) + payload) + ((err code) + fallback))) + +(fn ok_or_none_string ((value (result string i32))) -> (option string) + (if (std.result.is_ok value) + (some string (std.result.unwrap_ok value)) + (none string))) + +(fn ok_f64 ((value f64)) -> (result f64 i32) + (ok f64 i32 value)) + +(fn err_f64 ((code i32)) -> (result f64 i32) + (err f64 i32 code)) + +(fn is_ok_f64 ((value (result f64 i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_f64 ((value (result f64 i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_f64 ((value (result f64 i32))) -> f64 + (std.result.unwrap_ok value)) + +(fn unwrap_err_f64 ((value (result f64 i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_f64 ((value (result f64 i32)) (fallback f64)) -> f64 + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + fallback)) + +(fn ok_or_none_f64 ((value (result f64 i32))) -> (option f64) + (if (std.result.is_ok value) + (some f64 (std.result.unwrap_ok value)) + (none f64))) + +(fn ok_bool ((value bool)) -> (result bool i32) + (ok bool i32 value)) + +(fn err_bool ((code i32)) -> (result bool i32) + (err bool i32 code)) + +(fn is_ok_bool ((value (result bool i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_bool ((value (result bool i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_bool ((value (result bool i32))) -> bool + (std.result.unwrap_ok value)) + +(fn unwrap_err_bool ((value (result bool i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_bool ((value (result bool i32)) (fallback bool)) -> bool + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + fallback)) + +(fn ok_or_none_bool ((value (result bool i32))) -> (option bool) + (if (std.result.is_ok value) + (some bool (std.result.unwrap_ok value)) + (none bool))) diff --git a/examples/projects/std-layout-local-fs/src/string.slo b/examples/projects/std-layout-local-fs/src/string.slo new file mode 100644 index 0000000..6191345 --- /dev/null +++ b/examples/projects/std-layout-local-fs/src/string.slo @@ -0,0 +1,129 @@ +(module string (export len concat parse_i32_result parse_i32_option parse_u32_result parse_u32_option parse_i64_result parse_i64_option parse_u64_result parse_u64_option parse_f64_result parse_f64_option parse_bool_result parse_bool_option parse_i32_or_zero parse_u32_or_zero parse_i64_or_zero parse_u64_or_zero parse_f64_or_zero parse_bool_or_false parse_i32_or parse_u32_or parse_i64_or parse_u64_or parse_f64_or parse_bool_or)) + +(import result (ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool)) + +(fn len ((value string)) -> i32 + (std.string.len value)) + +(fn concat ((left string) (right string)) -> string + (std.string.concat left right)) + +(fn parse_i32_result ((value string)) -> (result i32 i32) + (std.string.parse_i32_result value)) + +(fn parse_i32_option ((value string)) -> (option i32) + (ok_or_none_i32 (parse_i32_result value))) + +(fn parse_u32_result ((value string)) -> (result u32 i32) + (std.string.parse_u32_result value)) + +(fn parse_u32_option ((value string)) -> (option u32) + (ok_or_none_u32 (parse_u32_result value))) + +(fn parse_i64_result ((value string)) -> (result i64 i32) + (std.string.parse_i64_result value)) + +(fn parse_i64_option ((value string)) -> (option i64) + (ok_or_none_i64 (parse_i64_result value))) + +(fn parse_u64_result ((value string)) -> (result u64 i32) + (std.string.parse_u64_result value)) + +(fn parse_u64_option ((value string)) -> (option u64) + (ok_or_none_u64 (parse_u64_result value))) + +(fn parse_f64_result ((value string)) -> (result f64 i32) + (std.string.parse_f64_result value)) + +(fn parse_f64_option ((value string)) -> (option f64) + (ok_or_none_f64 (parse_f64_result value))) + +(fn parse_bool_result ((value string)) -> (result bool i32) + (std.string.parse_bool_result value)) + +(fn parse_bool_option ((value string)) -> (option bool) + (ok_or_none_bool (parse_bool_result value))) + +(fn parse_i32_or_zero ((value string)) -> i32 + (match (parse_i32_result value) + ((ok payload) + payload) + ((err code) + 0))) + +(fn parse_u32_or_zero ((value string)) -> u32 + (match (parse_u32_result value) + ((ok payload) + payload) + ((err code) + 0u32))) + +(fn parse_i64_or_zero ((value string)) -> i64 + (match (parse_i64_result value) + ((ok payload) + payload) + ((err code) + 0i64))) + +(fn parse_u64_or_zero ((value string)) -> u64 + (match (parse_u64_result value) + ((ok payload) + payload) + ((err code) + 0u64))) + +(fn parse_f64_or_zero ((value string)) -> f64 + (match (parse_f64_result value) + ((ok payload) + payload) + ((err code) + 0.0))) + +(fn parse_bool_or_false ((value string)) -> bool + (match (parse_bool_result value) + ((ok payload) + payload) + ((err code) + false))) + +(fn parse_i32_or ((value string) (fallback i32)) -> i32 + (match (parse_i32_result value) + ((ok payload) + payload) + ((err code) + fallback))) + +(fn parse_u32_or ((value string) (fallback u32)) -> u32 + (match (parse_u32_result value) + ((ok payload) + payload) + ((err code) + fallback))) + +(fn parse_i64_or ((value string) (fallback i64)) -> i64 + (match (parse_i64_result value) + ((ok payload) + payload) + ((err code) + fallback))) + +(fn parse_u64_or ((value string) (fallback u64)) -> u64 + (match (parse_u64_result value) + ((ok payload) + payload) + ((err code) + fallback))) + +(fn parse_f64_or ((value string) (fallback f64)) -> f64 + (match (parse_f64_result value) + ((ok payload) + payload) + ((err code) + fallback))) + +(fn parse_bool_or ((value string) (fallback bool)) -> bool + (match (parse_bool_result value) + ((ok payload) + payload) + ((err code) + fallback))) diff --git a/examples/projects/std-layout-local-io/slovo.toml b/examples/projects/std-layout-local-io/slovo.toml new file mode 100644 index 0000000..05e3acd --- /dev/null +++ b/examples/projects/std-layout-local-io/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-layout-local-io" +source_root = "src" +entry = "main" diff --git a/examples/projects/std-layout-local-io/src/io.slo b/examples/projects/std-layout-local-io/src/io.slo new file mode 100644 index 0000000..4d7cdf7 --- /dev/null +++ b/examples/projects/std-layout-local-io/src/io.slo @@ -0,0 +1,218 @@ +(module io (export print_i32_zero print_u32_zero print_i64_zero print_u64_zero print_f64_zero print_string_zero print_bool_zero print_i32_value print_u32_value print_i64_value print_u64_value print_f64_value print_string_value print_bool_value read_stdin_result read_stdin_option read_stdin_or read_stdin_i32_result read_stdin_i32_option read_stdin_i32_or_zero read_stdin_i32_or read_stdin_u32_result read_stdin_u32_option read_stdin_u32_or_zero read_stdin_u32_or read_stdin_i64_result read_stdin_i64_option read_stdin_i64_or_zero read_stdin_i64_or read_stdin_u64_result read_stdin_u64_option read_stdin_u64_or_zero read_stdin_u64_or read_stdin_f64_result read_stdin_f64_option read_stdin_f64_or_zero read_stdin_f64_or read_stdin_bool_result read_stdin_bool_option read_stdin_bool_or_false read_stdin_bool_or)) + +(import result (ok_or_none_string ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool)) + +(import string (parse_i32_result parse_u32_result parse_i64_result parse_u64_result parse_f64_result parse_bool_result)) + +(fn print_i32_zero ((value i32)) -> i32 + (std.io.print_i32 value) + 0) + +(fn print_u32_zero ((value u32)) -> i32 + (std.io.print_u32 value) + 0) + +(fn print_i64_zero ((value i64)) -> i32 + (std.io.print_i64 value) + 0) + +(fn print_u64_zero ((value u64)) -> i32 + (std.io.print_u64 value) + 0) + +(fn print_f64_zero ((value f64)) -> i32 + (std.io.print_f64 value) + 0) + +(fn print_string_zero ((value string)) -> i32 + (std.io.print_string value) + 0) + +(fn print_bool_zero ((value bool)) -> i32 + (std.io.print_bool value) + 0) + +(fn print_i32_value ((value i32)) -> i32 + (std.io.print_i32 value) + value) + +(fn print_u32_value ((value u32)) -> u32 + (std.io.print_u32 value) + value) + +(fn print_i64_value ((value i64)) -> i64 + (std.io.print_i64 value) + value) + +(fn print_u64_value ((value u64)) -> u64 + (std.io.print_u64 value) + value) + +(fn print_f64_value ((value f64)) -> f64 + (std.io.print_f64 value) + value) + +(fn print_string_value ((value string)) -> string + (std.io.print_string value) + value) + +(fn print_bool_value ((value bool)) -> bool + (std.io.print_bool value) + value) + +(fn read_stdin_result () -> (result string i32) + (std.io.read_stdin_result)) + +(fn read_stdin_option () -> (option string) + (ok_or_none_string (read_stdin_result))) + +(fn read_stdin_or ((fallback string)) -> string + (match (read_stdin_result) + ((ok payload) + payload) + ((err code) + fallback))) + +(fn read_stdin_i32_result () -> (result i32 i32) + (match (read_stdin_result) + ((ok text) + (parse_i32_result text)) + ((err code) + (err i32 i32 code)))) + +(fn read_stdin_i32_option () -> (option i32) + (ok_or_none_i32 (read_stdin_i32_result))) + +(fn read_stdin_i32_or_zero () -> i32 + (match (read_stdin_i32_result) + ((ok value) + value) + ((err code) + 0))) + +(fn read_stdin_i32_or ((fallback i32)) -> i32 + (match (read_stdin_i32_result) + ((ok value) + value) + ((err code) + fallback))) + +(fn read_stdin_u32_result () -> (result u32 i32) + (match (read_stdin_result) + ((ok text) + (parse_u32_result text)) + ((err code) + (err u32 i32 code)))) + +(fn read_stdin_u32_option () -> (option u32) + (ok_or_none_u32 (read_stdin_u32_result))) + +(fn read_stdin_u32_or_zero () -> u32 + (match (read_stdin_u32_result) + ((ok value) + value) + ((err code) + 0u32))) + +(fn read_stdin_u32_or ((fallback u32)) -> u32 + (match (read_stdin_u32_result) + ((ok value) + value) + ((err code) + fallback))) + +(fn read_stdin_i64_result () -> (result i64 i32) + (match (read_stdin_result) + ((ok text) + (parse_i64_result text)) + ((err code) + (err i64 i32 code)))) + +(fn read_stdin_i64_option () -> (option i64) + (ok_or_none_i64 (read_stdin_i64_result))) + +(fn read_stdin_i64_or_zero () -> i64 + (match (read_stdin_i64_result) + ((ok value) + value) + ((err code) + 0i64))) + +(fn read_stdin_i64_or ((fallback i64)) -> i64 + (match (read_stdin_i64_result) + ((ok value) + value) + ((err code) + fallback))) + +(fn read_stdin_u64_result () -> (result u64 i32) + (match (read_stdin_result) + ((ok text) + (parse_u64_result text)) + ((err code) + (err u64 i32 code)))) + +(fn read_stdin_u64_option () -> (option u64) + (ok_or_none_u64 (read_stdin_u64_result))) + +(fn read_stdin_u64_or_zero () -> u64 + (match (read_stdin_u64_result) + ((ok value) + value) + ((err code) + 0u64))) + +(fn read_stdin_u64_or ((fallback u64)) -> u64 + (match (read_stdin_u64_result) + ((ok value) + value) + ((err code) + fallback))) + +(fn read_stdin_f64_result () -> (result f64 i32) + (match (read_stdin_result) + ((ok text) + (parse_f64_result text)) + ((err code) + (err f64 i32 code)))) + +(fn read_stdin_f64_option () -> (option f64) + (ok_or_none_f64 (read_stdin_f64_result))) + +(fn read_stdin_f64_or_zero () -> f64 + (match (read_stdin_f64_result) + ((ok value) + value) + ((err code) + 0.0))) + +(fn read_stdin_f64_or ((fallback f64)) -> f64 + (match (read_stdin_f64_result) + ((ok value) + value) + ((err code) + fallback))) + +(fn read_stdin_bool_result () -> (result bool i32) + (match (read_stdin_result) + ((ok text) + (parse_bool_result text)) + ((err code) + (err bool i32 code)))) + +(fn read_stdin_bool_option () -> (option bool) + (ok_or_none_bool (read_stdin_bool_result))) + +(fn read_stdin_bool_or_false () -> bool + (match (read_stdin_bool_result) + ((ok value) + value) + ((err code) + false))) + +(fn read_stdin_bool_or ((fallback bool)) -> bool + (match (read_stdin_bool_result) + ((ok value) + value) + ((err code) + fallback))) diff --git a/examples/projects/std-layout-local-io/src/main.slo b/examples/projects/std-layout-local-io/src/main.slo new file mode 100644 index 0000000..710c977 --- /dev/null +++ b/examples/projects/std-layout-local-io/src/main.slo @@ -0,0 +1,221 @@ +(module main) + +(import io (print_i32_zero print_u32_zero print_i64_zero print_u64_zero print_f64_zero print_string_zero print_bool_zero print_i32_value print_u32_value print_i64_value print_u64_value print_f64_value print_string_value print_bool_value read_stdin_result read_stdin_option read_stdin_or read_stdin_i32_result read_stdin_i32_option read_stdin_i32_or_zero read_stdin_i32_or read_stdin_u32_result read_stdin_u32_option read_stdin_u32_or_zero read_stdin_u32_or read_stdin_i64_result read_stdin_i64_option read_stdin_i64_or_zero read_stdin_i64_or read_stdin_u64_result read_stdin_u64_option read_stdin_u64_or_zero read_stdin_u64_or read_stdin_f64_result read_stdin_f64_option read_stdin_f64_or_zero read_stdin_f64_or read_stdin_bool_result read_stdin_bool_option read_stdin_bool_or_false read_stdin_bool_or)) + +(fn imported_print_i32_status () -> i32 + (print_i32_zero 42)) + +(fn imported_print_i64_status () -> i32 + (print_i64_zero 42i64)) + +(fn imported_print_u32_status () -> i32 + (print_u32_zero 42u32)) + +(fn imported_print_u64_status () -> i32 + (print_u64_zero 42u64)) + +(fn imported_print_f64_status () -> i32 + (print_f64_zero 42.5)) + +(fn imported_print_string_status () -> i32 + (print_string_zero "slovo")) + +(fn imported_print_bool_status () -> i32 + (print_bool_zero true)) + +(fn imported_print_value_helpers_ok () -> bool + (if (= (print_u32_value 42u32) 42u32) + (if (= (print_i64_value 42i64) 42i64) + (if (= (print_u64_value 42u64) 42u64) + (if (= (print_f64_value 42.5) 42.5) + true + false) + false) + false) + false)) + +(fn imported_stdin_result_ok () -> bool + (match (read_stdin_result) + ((ok payload) + (= payload "")) + ((err code) + false))) + +(fn imported_stdin_option_ok () -> bool + (match (read_stdin_option) + ((some payload) + (= payload "")) + ((none) + false))) + +(fn imported_stdin_text_fallback_ok () -> bool + (= (read_stdin_or "fallback") "")) + +(fn imported_stdin_typed_results_ok () -> bool + (if (match (read_stdin_i32_result) + ((ok payload) + false) + ((err code) + true)) + (if (match (read_stdin_u32_result) + ((ok payload) + false) + ((err code) + true)) + (if (match (read_stdin_i64_result) + ((ok payload) + false) + ((err code) + true)) + (if (match (read_stdin_u64_result) + ((ok payload) + false) + ((err code) + true)) + (if (match (read_stdin_f64_result) + ((ok payload) + false) + ((err code) + true)) + (match (read_stdin_bool_result) + ((ok payload) + false) + ((err code) + true)) + false) + false) + false) + false) + false)) + +(fn imported_stdin_typed_options_ok () -> bool + (if (match (read_stdin_i32_option) + ((some payload) + false) + ((none) + true)) + (if (match (read_stdin_u32_option) + ((some payload) + false) + ((none) + true)) + (if (match (read_stdin_i64_option) + ((some payload) + false) + ((none) + true)) + (if (match (read_stdin_u64_option) + ((some payload) + false) + ((none) + true)) + (if (match (read_stdin_f64_option) + ((some payload) + false) + ((none) + true)) + (match (read_stdin_bool_option) + ((some payload) + false) + ((none) + true)) + false) + false) + false) + false) + false)) + +(fn imported_stdin_bool_fallbacks_ok () -> bool + (let empty_bool bool (read_stdin_bool_or_false)) + (let fallback_bool bool (read_stdin_bool_or true)) + (if empty_bool + false + fallback_bool)) + +(fn imported_stdin_typed_fallbacks_ok () -> bool + (if (= (read_stdin_i32_or_zero) 0) + (if (= (read_stdin_i32_or 7) 7) + (if (= (read_stdin_u32_or_zero) 0u32) + (if (= (read_stdin_u32_or 7u32) 7u32) + (if (= (read_stdin_i64_or_zero) 0i64) + (if (= (read_stdin_i64_or 9i64) 9i64) + (if (= (read_stdin_u64_or_zero) 0u64) + (if (= (read_stdin_u64_or 9u64) 9u64) + (if (= (read_stdin_f64_or_zero) 0.0) + (if (= (read_stdin_f64_or 1.5) 1.5) + (imported_stdin_bool_fallbacks_ok) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_io_status_all () -> i32 + (imported_print_u32_status) + (imported_print_i64_status) + (imported_print_u64_status) + (imported_print_f64_status) + 42) + +(fn imported_io_helpers_ok () -> bool + (if (= (imported_io_status_all) 42) + (if (imported_print_value_helpers_ok) + (if (imported_stdin_result_ok) + (if (imported_stdin_option_ok) + (if (imported_stdin_text_fallback_ok) + (if (imported_stdin_typed_results_ok) + (if (imported_stdin_typed_options_ok) + (imported_stdin_typed_fallbacks_ok) + false) + false) + false) + false) + false) + false) + false)) + +(fn main () -> i32 + (if (imported_io_helpers_ok) + 42 + 1)) + +(test "explicit local io i64 zero facade" + (= (imported_print_i64_status) 0)) + +(test "explicit local io u32 zero facade" + (= (imported_print_u32_status) 0)) + +(test "explicit local io u64 zero facade" + (= (imported_print_u64_status) 0)) + +(test "explicit local io f64 zero facade" + (= (imported_print_f64_status) 0)) + +(test "explicit local io value facade" + (imported_print_value_helpers_ok)) + +(test "explicit local io stdin result facade" + (imported_stdin_result_ok)) + +(test "explicit local io stdin option facade" + (imported_stdin_option_ok)) + +(test "explicit local io stdin text fallback facade" + (imported_stdin_text_fallback_ok)) + +(test "explicit local io stdin typed result facade" + (imported_stdin_typed_results_ok)) + +(test "explicit local io stdin typed option facade" + (imported_stdin_typed_options_ok)) + +(test "explicit local io stdin typed fallback facade" + (imported_stdin_typed_fallbacks_ok)) + +(test "explicit local io helpers all" + (= (main) 42)) diff --git a/examples/projects/std-layout-local-io/src/result.slo b/examples/projects/std-layout-local-io/src/result.slo new file mode 100644 index 0000000..4a7b77b --- /dev/null +++ b/examples/projects/std-layout-local-io/src/result.slo @@ -0,0 +1,199 @@ +(module result (export ok_i32 err_i32 is_ok_i32 is_err_i32 unwrap_ok_i32 unwrap_err_i32 unwrap_or_i32 ok_or_none_i32 ok_u32 err_u32 is_ok_u32 is_err_u32 unwrap_ok_u32 unwrap_err_u32 unwrap_or_u32 ok_or_none_u32 ok_i64 err_i64 is_ok_i64 is_err_i64 unwrap_ok_i64 unwrap_err_i64 unwrap_or_i64 ok_or_none_i64 ok_u64 err_u64 is_ok_u64 is_err_u64 unwrap_ok_u64 unwrap_err_u64 unwrap_or_u64 ok_or_none_u64 ok_string err_string is_ok_string is_err_string unwrap_ok_string unwrap_err_string unwrap_or_string ok_or_none_string ok_f64 err_f64 is_ok_f64 is_err_f64 unwrap_ok_f64 unwrap_err_f64 unwrap_or_f64 ok_or_none_f64 ok_bool err_bool is_ok_bool is_err_bool unwrap_ok_bool unwrap_err_bool unwrap_or_bool ok_or_none_bool)) + +(fn ok_i32 ((value i32)) -> (result i32 i32) + (ok i32 i32 value)) + +(fn err_i32 ((code i32)) -> (result i32 i32) + (err i32 i32 code)) + +(fn is_ok_i32 ((value (result i32 i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_i32 ((value (result i32 i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_i32 ((value (result i32 i32))) -> i32 + (std.result.unwrap_ok value)) + +(fn unwrap_err_i32 ((value (result i32 i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_i32 ((value (result i32 i32)) (fallback i32)) -> i32 + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + fallback)) + +(fn ok_or_none_i32 ((value (result i32 i32))) -> (option i32) + (if (std.result.is_ok value) + (some i32 (std.result.unwrap_ok value)) + (none i32))) + +(fn ok_u32 ((value u32)) -> (result u32 i32) + (ok u32 i32 value)) + +(fn err_u32 ((code i32)) -> (result u32 i32) + (err u32 i32 code)) + +(fn is_ok_u32 ((value (result u32 i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_u32 ((value (result u32 i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_u32 ((value (result u32 i32))) -> u32 + (std.result.unwrap_ok value)) + +(fn unwrap_err_u32 ((value (result u32 i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_u32 ((value (result u32 i32)) (fallback u32)) -> u32 + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + fallback)) + +(fn ok_or_none_u32 ((value (result u32 i32))) -> (option u32) + (if (std.result.is_ok value) + (some u32 (std.result.unwrap_ok value)) + (none u32))) + +(fn ok_i64 ((value i64)) -> (result i64 i32) + (ok i64 i32 value)) + +(fn err_i64 ((code i32)) -> (result i64 i32) + (err i64 i32 code)) + +(fn is_ok_i64 ((value (result i64 i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_i64 ((value (result i64 i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_i64 ((value (result i64 i32))) -> i64 + (std.result.unwrap_ok value)) + +(fn unwrap_err_i64 ((value (result i64 i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_i64 ((value (result i64 i32)) (fallback i64)) -> i64 + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + fallback)) + +(fn ok_or_none_i64 ((value (result i64 i32))) -> (option i64) + (if (std.result.is_ok value) + (some i64 (std.result.unwrap_ok value)) + (none i64))) + +(fn ok_u64 ((value u64)) -> (result u64 i32) + (ok u64 i32 value)) + +(fn err_u64 ((code i32)) -> (result u64 i32) + (err u64 i32 code)) + +(fn is_ok_u64 ((value (result u64 i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_u64 ((value (result u64 i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_u64 ((value (result u64 i32))) -> u64 + (std.result.unwrap_ok value)) + +(fn unwrap_err_u64 ((value (result u64 i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_u64 ((value (result u64 i32)) (fallback u64)) -> u64 + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + fallback)) + +(fn ok_or_none_u64 ((value (result u64 i32))) -> (option u64) + (if (std.result.is_ok value) + (some u64 (std.result.unwrap_ok value)) + (none u64))) + +(fn ok_string ((value string)) -> (result string i32) + (ok string i32 value)) + +(fn err_string ((code i32)) -> (result string i32) + (err string i32 code)) + +(fn is_ok_string ((value (result string i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_string ((value (result string i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_string ((value (result string i32))) -> string + (std.result.unwrap_ok value)) + +(fn unwrap_err_string ((value (result string i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_string ((value (result string i32)) (fallback string)) -> string + (match value + ((ok payload) + payload) + ((err code) + fallback))) + +(fn ok_or_none_string ((value (result string i32))) -> (option string) + (if (std.result.is_ok value) + (some string (std.result.unwrap_ok value)) + (none string))) + +(fn ok_f64 ((value f64)) -> (result f64 i32) + (ok f64 i32 value)) + +(fn err_f64 ((code i32)) -> (result f64 i32) + (err f64 i32 code)) + +(fn is_ok_f64 ((value (result f64 i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_f64 ((value (result f64 i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_f64 ((value (result f64 i32))) -> f64 + (std.result.unwrap_ok value)) + +(fn unwrap_err_f64 ((value (result f64 i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_f64 ((value (result f64 i32)) (fallback f64)) -> f64 + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + fallback)) + +(fn ok_or_none_f64 ((value (result f64 i32))) -> (option f64) + (if (std.result.is_ok value) + (some f64 (std.result.unwrap_ok value)) + (none f64))) + +(fn ok_bool ((value bool)) -> (result bool i32) + (ok bool i32 value)) + +(fn err_bool ((code i32)) -> (result bool i32) + (err bool i32 code)) + +(fn is_ok_bool ((value (result bool i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_bool ((value (result bool i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_bool ((value (result bool i32))) -> bool + (std.result.unwrap_ok value)) + +(fn unwrap_err_bool ((value (result bool i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_bool ((value (result bool i32)) (fallback bool)) -> bool + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + fallback)) + +(fn ok_or_none_bool ((value (result bool i32))) -> (option bool) + (if (std.result.is_ok value) + (some bool (std.result.unwrap_ok value)) + (none bool))) diff --git a/examples/projects/std-layout-local-io/src/string.slo b/examples/projects/std-layout-local-io/src/string.slo new file mode 100644 index 0000000..6191345 --- /dev/null +++ b/examples/projects/std-layout-local-io/src/string.slo @@ -0,0 +1,129 @@ +(module string (export len concat parse_i32_result parse_i32_option parse_u32_result parse_u32_option parse_i64_result parse_i64_option parse_u64_result parse_u64_option parse_f64_result parse_f64_option parse_bool_result parse_bool_option parse_i32_or_zero parse_u32_or_zero parse_i64_or_zero parse_u64_or_zero parse_f64_or_zero parse_bool_or_false parse_i32_or parse_u32_or parse_i64_or parse_u64_or parse_f64_or parse_bool_or)) + +(import result (ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool)) + +(fn len ((value string)) -> i32 + (std.string.len value)) + +(fn concat ((left string) (right string)) -> string + (std.string.concat left right)) + +(fn parse_i32_result ((value string)) -> (result i32 i32) + (std.string.parse_i32_result value)) + +(fn parse_i32_option ((value string)) -> (option i32) + (ok_or_none_i32 (parse_i32_result value))) + +(fn parse_u32_result ((value string)) -> (result u32 i32) + (std.string.parse_u32_result value)) + +(fn parse_u32_option ((value string)) -> (option u32) + (ok_or_none_u32 (parse_u32_result value))) + +(fn parse_i64_result ((value string)) -> (result i64 i32) + (std.string.parse_i64_result value)) + +(fn parse_i64_option ((value string)) -> (option i64) + (ok_or_none_i64 (parse_i64_result value))) + +(fn parse_u64_result ((value string)) -> (result u64 i32) + (std.string.parse_u64_result value)) + +(fn parse_u64_option ((value string)) -> (option u64) + (ok_or_none_u64 (parse_u64_result value))) + +(fn parse_f64_result ((value string)) -> (result f64 i32) + (std.string.parse_f64_result value)) + +(fn parse_f64_option ((value string)) -> (option f64) + (ok_or_none_f64 (parse_f64_result value))) + +(fn parse_bool_result ((value string)) -> (result bool i32) + (std.string.parse_bool_result value)) + +(fn parse_bool_option ((value string)) -> (option bool) + (ok_or_none_bool (parse_bool_result value))) + +(fn parse_i32_or_zero ((value string)) -> i32 + (match (parse_i32_result value) + ((ok payload) + payload) + ((err code) + 0))) + +(fn parse_u32_or_zero ((value string)) -> u32 + (match (parse_u32_result value) + ((ok payload) + payload) + ((err code) + 0u32))) + +(fn parse_i64_or_zero ((value string)) -> i64 + (match (parse_i64_result value) + ((ok payload) + payload) + ((err code) + 0i64))) + +(fn parse_u64_or_zero ((value string)) -> u64 + (match (parse_u64_result value) + ((ok payload) + payload) + ((err code) + 0u64))) + +(fn parse_f64_or_zero ((value string)) -> f64 + (match (parse_f64_result value) + ((ok payload) + payload) + ((err code) + 0.0))) + +(fn parse_bool_or_false ((value string)) -> bool + (match (parse_bool_result value) + ((ok payload) + payload) + ((err code) + false))) + +(fn parse_i32_or ((value string) (fallback i32)) -> i32 + (match (parse_i32_result value) + ((ok payload) + payload) + ((err code) + fallback))) + +(fn parse_u32_or ((value string) (fallback u32)) -> u32 + (match (parse_u32_result value) + ((ok payload) + payload) + ((err code) + fallback))) + +(fn parse_i64_or ((value string) (fallback i64)) -> i64 + (match (parse_i64_result value) + ((ok payload) + payload) + ((err code) + fallback))) + +(fn parse_u64_or ((value string) (fallback u64)) -> u64 + (match (parse_u64_result value) + ((ok payload) + payload) + ((err code) + fallback))) + +(fn parse_f64_or ((value string) (fallback f64)) -> f64 + (match (parse_f64_result value) + ((ok payload) + payload) + ((err code) + fallback))) + +(fn parse_bool_or ((value string) (fallback bool)) -> bool + (match (parse_bool_result value) + ((ok payload) + payload) + ((err code) + fallback))) diff --git a/examples/projects/std-layout-local-math/slovo.toml b/examples/projects/std-layout-local-math/slovo.toml new file mode 100644 index 0000000..0dfcffe --- /dev/null +++ b/examples/projects/std-layout-local-math/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-layout-local-math" +source_root = "src" +entry = "main" diff --git a/examples/projects/std-layout-local-math/src/main.slo b/examples/projects/std-layout-local-math/src/main.slo new file mode 100644 index 0000000..90599d1 --- /dev/null +++ b/examples/projects/std-layout-local-math/src/main.slo @@ -0,0 +1,102 @@ +(module main) + +(import math (abs_i32 neg_i32 rem_i32 bit_and_i32 bit_or_i32 bit_xor_i32 is_even_i32 is_odd_i32 min_i32 max_i32 clamp_i32 square_i32 cube_i32 is_zero_i32 is_positive_i32 is_negative_i32 in_range_i32 abs_i64 neg_i64 rem_i64 bit_and_i64 bit_or_i64 bit_xor_i64 is_even_i64 is_odd_i64 min_i64 max_i64 clamp_i64 square_i64 cube_i64 is_zero_i64 is_positive_i64 is_negative_i64 in_range_i64 abs_f64 neg_f64 min_f64 max_f64 clamp_f64 square_f64 cube_f64 is_zero_f64 is_positive_f64 is_negative_f64 in_range_f64)) + +(fn imported_i32_score () -> i32 + (clamp_i32 (+ (square_i32 (abs_i32 (neg_i32 6))) (max_i32 (min_i32 (cube_i32 1) 7) 6)) 0 42)) + +(fn imported_i64_score () -> i64 + (clamp_i64 (+ (square_i64 (abs_i64 (neg_i64 6i64))) (max_i64 (min_i64 (cube_i64 1i64) 7i64) 6i64)) 0i64 42i64)) + +(fn imported_f64_score () -> f64 + (clamp_f64 (+ (square_f64 (abs_f64 (neg_f64 6.0))) (max_f64 (min_f64 (cube_f64 1.0) 7.0) 6.0)) 0.0 42.0)) + +(fn imported_i32_predicates_ok () -> bool + (if (is_zero_i32 (neg_i32 0)) + (if (is_positive_i32 (abs_i32 -5)) + (if (is_negative_i32 (neg_i32 5)) + (if (= (rem_i32 17 5) 2) + (if (is_even_i32 42) + (if (is_odd_i32 -41) + (if (= (bit_and_i32 6 3) 2) + (if (= (bit_or_i32 4 2) 6) + (if (= (bit_xor_i32 7 3) 4) + (in_range_i32 (imported_i32_score) 0 42) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_i64_predicates_ok () -> bool + (if (is_zero_i64 (neg_i64 0i64)) + (if (is_positive_i64 (abs_i64 -5i64)) + (if (is_negative_i64 (neg_i64 5i64)) + (if (= (rem_i64 17i64 5i64) 2i64) + (if (is_even_i64 42i64) + (if (is_odd_i64 -41i64) + (if (= (bit_and_i64 6i64 3i64) 2i64) + (if (= (bit_or_i64 4i64 2i64) 6i64) + (if (= (bit_xor_i64 7i64 3i64) 4i64) + (in_range_i64 (imported_i64_score) 0i64 42i64) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_f64_predicates_ok () -> bool + (if (is_zero_f64 (neg_f64 0.0)) + (if (is_positive_f64 (abs_f64 -5.0)) + (if (is_negative_f64 (neg_f64 5.0)) + (in_range_f64 (imported_f64_score) 0.0 42.0) + false) + false) + false)) + +(fn imported_i32_helpers_ok () -> bool + (if (= (imported_i32_score) 42) + (imported_i32_predicates_ok) + false)) + +(fn imported_i64_helpers_ok () -> bool + (if (= (imported_i64_score) 42i64) + (imported_i64_predicates_ok) + false)) + +(fn imported_f64_helpers_ok () -> bool + (if (= (imported_f64_score) 42.0) + (imported_f64_predicates_ok) + false)) + +(fn imported_math_helpers_ok () -> bool + (if (imported_i32_helpers_ok) + (if (imported_i64_helpers_ok) + (imported_f64_helpers_ok) + false) + false)) + +(fn main () -> i32 + (if (imported_math_helpers_ok) + 42 + 1)) + +(test "explicit local math import i32 helpers" + (imported_i32_helpers_ok)) + +(test "explicit local math import i64 helpers" + (imported_i64_helpers_ok)) + +(test "explicit local math import f64 helpers" + (imported_f64_helpers_ok)) + +(test "explicit local math import all helpers" + (= (main) 42)) diff --git a/examples/projects/std-layout-local-math/src/math.slo b/examples/projects/std-layout-local-math/src/math.slo new file mode 100644 index 0000000..eec825c --- /dev/null +++ b/examples/projects/std-layout-local-math/src/math.slo @@ -0,0 +1,266 @@ +(module math (export abs_i32 neg_i32 rem_i32 bit_and_i32 bit_or_i32 bit_xor_i32 is_even_i32 is_odd_i32 min_i32 max_i32 clamp_i32 square_i32 cube_i32 is_zero_i32 is_positive_i32 is_negative_i32 in_range_i32 abs_i64 neg_i64 rem_i64 bit_and_i64 bit_or_i64 bit_xor_i64 is_even_i64 is_odd_i64 min_i64 max_i64 clamp_i64 square_i64 cube_i64 is_zero_i64 is_positive_i64 is_negative_i64 in_range_i64 abs_f64 neg_f64 min_f64 max_f64 clamp_f64 square_f64 cube_f64 is_zero_f64 is_positive_f64 is_negative_f64 in_range_f64)) + +(fn abs_i32 ((value i32)) -> i32 + (if (< value 0) + (- 0 value) + value)) + +(fn neg_i32 ((value i32)) -> i32 + (- 0 value)) + +(fn rem_i32 ((left i32) (right i32)) -> i32 + (% left right)) + +(fn bit_and_i32 ((left i32) (right i32)) -> i32 + (bit_and left right)) + +(fn bit_or_i32 ((left i32) (right i32)) -> i32 + (bit_or left right)) + +(fn bit_xor_i32 ((left i32) (right i32)) -> i32 + (bit_xor left right)) + +(fn is_even_i32 ((value i32)) -> bool + (= (rem_i32 value 2) 0)) + +(fn is_odd_i32 ((value i32)) -> bool + (if (= (rem_i32 value 2) 0) + false + true)) + +(fn min_i32 ((left i32) (right i32)) -> i32 + (if (< left right) + left + right)) + +(fn max_i32 ((left i32) (right i32)) -> i32 + (if (> left right) + left + right)) + +(fn clamp_i32 ((value i32) (low i32) (high i32)) -> i32 + (max_i32 low (min_i32 value high))) + +(fn square_i32 ((value i32)) -> i32 + (* value value)) + +(fn cube_i32 ((value i32)) -> i32 + (* (square_i32 value) value)) + +(fn is_zero_i32 ((value i32)) -> bool + (= value 0)) + +(fn is_positive_i32 ((value i32)) -> bool + (> value 0)) + +(fn is_negative_i32 ((value i32)) -> bool + (< value 0)) + +(fn in_range_i32 ((value i32) (low i32) (high i32)) -> bool + (if (>= value low) + (<= value high) + false)) + +(fn abs_i64 ((value i64)) -> i64 + (if (< value 0i64) + (- 0i64 value) + value)) + +(fn neg_i64 ((value i64)) -> i64 + (- 0i64 value)) + +(fn rem_i64 ((left i64) (right i64)) -> i64 + (% left right)) + +(fn bit_and_i64 ((left i64) (right i64)) -> i64 + (bit_and left right)) + +(fn bit_or_i64 ((left i64) (right i64)) -> i64 + (bit_or left right)) + +(fn bit_xor_i64 ((left i64) (right i64)) -> i64 + (bit_xor left right)) + +(fn is_even_i64 ((value i64)) -> bool + (= (rem_i64 value 2i64) 0i64)) + +(fn is_odd_i64 ((value i64)) -> bool + (if (= (rem_i64 value 2i64) 0i64) + false + true)) + +(fn min_i64 ((left i64) (right i64)) -> i64 + (if (< left right) + left + right)) + +(fn max_i64 ((left i64) (right i64)) -> i64 + (if (> left right) + left + right)) + +(fn clamp_i64 ((value i64) (low i64) (high i64)) -> i64 + (max_i64 low (min_i64 value high))) + +(fn square_i64 ((value i64)) -> i64 + (* value value)) + +(fn cube_i64 ((value i64)) -> i64 + (* (square_i64 value) value)) + +(fn is_zero_i64 ((value i64)) -> bool + (= value 0i64)) + +(fn is_positive_i64 ((value i64)) -> bool + (> value 0i64)) + +(fn is_negative_i64 ((value i64)) -> bool + (< value 0i64)) + +(fn in_range_i64 ((value i64) (low i64) (high i64)) -> bool + (if (>= value low) + (<= value high) + false)) + +(fn abs_f64 ((value f64)) -> f64 + (if (< value 0.0) + (- 0.0 value) + value)) + +(fn neg_f64 ((value f64)) -> f64 + (- 0.0 value)) + +(fn min_f64 ((left f64) (right f64)) -> f64 + (if (< left right) + left + right)) + +(fn max_f64 ((left f64) (right f64)) -> f64 + (if (> left right) + left + right)) + +(fn clamp_f64 ((value f64) (low f64) (high f64)) -> f64 + (max_f64 low (min_f64 value high))) + +(fn square_f64 ((value f64)) -> f64 + (* value value)) + +(fn cube_f64 ((value f64)) -> f64 + (* (square_f64 value) value)) + +(fn is_zero_f64 ((value f64)) -> bool + (= value 0.0)) + +(fn is_positive_f64 ((value f64)) -> bool + (> value 0.0)) + +(fn is_negative_f64 ((value f64)) -> bool + (< value 0.0)) + +(fn in_range_f64 ((value f64) (low f64) (high f64)) -> bool + (if (>= value low) + (<= value high) + false)) + +(fn i32_helpers_ok () -> bool + (if (= (abs_i32 -7) 7) + (if (= (neg_i32 7) -7) + (if (= (min_i32 4 9) 4) + (if (= (max_i32 4 9) 9) + (if (= (clamp_i32 15 0 10) 10) + (if (= (square_i32 6) 36) + (if (= (cube_i32 3) 27) + (if (is_zero_i32 0) + (if (is_positive_i32 5) + (if (is_negative_i32 -5) + (if (= (rem_i32 -17 5) -2) + (if (is_even_i32 42) + (if (is_odd_i32 -41) + (if (= (bit_and_i32 6 3) 2) + (if (= (bit_or_i32 4 2) 6) + (if (= (bit_xor_i32 7 3) 4) + (in_range_i32 5 0 10) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn i64_helpers_ok () -> bool + (if (= (abs_i64 -9i64) 9i64) + (if (= (neg_i64 7i64) -7i64) + (if (= (min_i64 4i64 9i64) 4i64) + (if (= (max_i64 4i64 9i64) 9i64) + (if (= (clamp_i64 -5i64 0i64 10i64) 0i64) + (if (= (square_i64 12i64) 144i64) + (if (= (cube_i64 3i64) 27i64) + (if (is_zero_i64 0i64) + (if (is_positive_i64 5i64) + (if (is_negative_i64 -5i64) + (if (= (rem_i64 -17i64 5i64) -2i64) + (if (is_even_i64 42i64) + (if (is_odd_i64 -41i64) + (if (= (bit_and_i64 6i64 3i64) 2i64) + (if (= (bit_or_i64 4i64 2i64) 6i64) + (if (= (bit_xor_i64 7i64 3i64) 4i64) + (in_range_i64 5i64 0i64 10i64) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn f64_helpers_ok () -> bool + (if (= (abs_f64 -2.5) 2.5) + (if (= (neg_f64 7.5) -7.5) + (if (= (min_f64 4.5 9.0) 4.5) + (if (= (max_f64 4.5 9.0) 9.0) + (if (= (clamp_f64 12.5 0.0 10.0) 10.0) + (if (= (square_f64 1.5) 2.25) + (if (= (cube_f64 2.0) 8.0) + (if (is_zero_f64 0.0) + (if (is_positive_f64 5.0) + (if (is_negative_f64 -5.0) + (in_range_f64 5.0 0.0 10.0) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(test "local math i32 helpers" + (i32_helpers_ok)) + +(test "local math i64 helpers" + (i64_helpers_ok)) + +(test "local math f64 helpers" + (f64_helpers_ok)) diff --git a/examples/projects/std-layout-local-num/slovo.toml b/examples/projects/std-layout-local-num/slovo.toml new file mode 100644 index 0000000..9834030 --- /dev/null +++ b/examples/projects/std-layout-local-num/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-layout-local-num" +source_root = "src" +entry = "main" diff --git a/examples/projects/std-layout-local-num/src/main.slo b/examples/projects/std-layout-local-num/src/main.slo new file mode 100644 index 0000000..e67feb5 --- /dev/null +++ b/examples/projects/std-layout-local-num/src/main.slo @@ -0,0 +1,70 @@ +(module main) + +(import num (i32_to_i64 i32_to_f64 i64_to_f64 i64_to_i32_result f64_to_i32_result f64_to_i64_result i32_to_string u32_to_string i64_to_string u64_to_string f64_to_string i64_to_i32_or f64_to_i32_or f64_to_i64_or)) + +(fn imported_num_widening_ok () -> bool + (if (= (i32_to_i64 42) 42i64) + (if (= (i32_to_f64 42) 42.0) + (= (i64_to_f64 42i64) 42.0) + false) + false)) + +(fn imported_num_checked_ok () -> bool + (if (= (unwrap_ok (i64_to_i32_result 42i64)) 42) + (if (= (unwrap_ok (f64_to_i32_result 42.0)) 42) + (= (unwrap_ok (f64_to_i64_result 42.0)) 42i64) + false) + false)) + +(fn imported_num_strings_ok () -> bool + (if (= (i32_to_string 42) "42") + (if (= (u32_to_string 42u32) "42") + (if (= (i64_to_string 42i64) "42") + (if (= (u64_to_string 42u64) "42") + (= (f64_to_string 42.0) "42.0") + false) + false) + false) + false)) + +(fn imported_num_fallbacks_ok () -> bool + (if (= (i64_to_i32_or 42i64 7) 42) + (if (= (i64_to_i32_or 2147483648i64 7) 7) + (if (= (f64_to_i32_or 42.0 7) 42) + (if (= (f64_to_i32_or 2147483648.0 7) 7) + (if (= (f64_to_i64_or 42.0 7i64) 42i64) + (= (f64_to_i64_or 100000000000000000000.0 7i64) 7i64) + false) + false) + false) + false) + false)) + +(fn imported_num_helpers_ok () -> bool + (if (imported_num_widening_ok) + (if (imported_num_checked_ok) + (if (imported_num_strings_ok) + (imported_num_fallbacks_ok) + false) + false) + false)) + +(fn main () -> i32 + (if (imported_num_helpers_ok) + 42 + 1)) + +(test "explicit local num widening" + (imported_num_widening_ok)) + +(test "explicit local num checked conversions" + (imported_num_checked_ok)) + +(test "explicit local num to string" + (imported_num_strings_ok)) + +(test "explicit local num checked fallbacks" + (imported_num_fallbacks_ok)) + +(test "explicit local num helpers all" + (= (main) 42)) diff --git a/examples/projects/std-layout-local-num/src/num.slo b/examples/projects/std-layout-local-num/src/num.slo new file mode 100644 index 0000000..d1ab07f --- /dev/null +++ b/examples/projects/std-layout-local-num/src/num.slo @@ -0,0 +1,55 @@ +(module num (export i32_to_i64 i32_to_f64 i64_to_f64 i64_to_i32_result f64_to_i32_result f64_to_i64_result i32_to_string u32_to_string i64_to_string u64_to_string f64_to_string i64_to_i32_or f64_to_i32_or f64_to_i64_or)) + +(fn i32_to_i64 ((value i32)) -> i64 + (std.num.i32_to_i64 value)) + +(fn i32_to_f64 ((value i32)) -> f64 + (std.num.i32_to_f64 value)) + +(fn i64_to_f64 ((value i64)) -> f64 + (std.num.i64_to_f64 value)) + +(fn i64_to_i32_result ((value i64)) -> (result i32 i32) + (std.num.i64_to_i32_result value)) + +(fn f64_to_i32_result ((value f64)) -> (result i32 i32) + (std.num.f64_to_i32_result value)) + +(fn f64_to_i64_result ((value f64)) -> (result i64 i32) + (std.num.f64_to_i64_result value)) + +(fn i32_to_string ((value i32)) -> string + (std.num.i32_to_string value)) + +(fn u32_to_string ((value u32)) -> string + (std.num.u32_to_string value)) + +(fn i64_to_string ((value i64)) -> string + (std.num.i64_to_string value)) + +(fn u64_to_string ((value u64)) -> string + (std.num.u64_to_string value)) + +(fn f64_to_string ((value f64)) -> string + (std.num.f64_to_string value)) + +(fn i64_to_i32_or ((value i64) (fallback i32)) -> i32 + (match (i64_to_i32_result value) + ((ok payload) + payload) + ((err code) + fallback))) + +(fn f64_to_i32_or ((value f64) (fallback i32)) -> i32 + (match (f64_to_i32_result value) + ((ok payload) + payload) + ((err code) + fallback))) + +(fn f64_to_i64_or ((value f64) (fallback i64)) -> i64 + (match (f64_to_i64_result value) + ((ok payload) + payload) + ((err code) + fallback))) diff --git a/examples/projects/std-layout-local-option/slovo.toml b/examples/projects/std-layout-local-option/slovo.toml new file mode 100644 index 0000000..9471597 --- /dev/null +++ b/examples/projects/std-layout-local-option/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-layout-local-option" +source_root = "src" +entry = "main" diff --git a/examples/projects/std-layout-local-option/src/main.slo b/examples/projects/std-layout-local-option/src/main.slo new file mode 100644 index 0000000..1d2ebbb --- /dev/null +++ b/examples/projects/std-layout-local-option/src/main.slo @@ -0,0 +1,408 @@ +(module main) + +(import option (some_i32 none_i32 is_some_i32 is_none_i32 unwrap_some_i32 unwrap_or_i32 some_or_err_i32 some_u32 none_u32 is_some_u32 is_none_u32 unwrap_some_u32 unwrap_or_u32 some_or_err_u32 some_i64 none_i64 is_some_i64 is_none_i64 unwrap_some_i64 unwrap_or_i64 some_or_err_i64 some_u64 none_u64 is_some_u64 is_none_u64 unwrap_some_u64 unwrap_or_u64 some_or_err_u64 some_f64 none_f64 is_some_f64 is_none_f64 unwrap_some_f64 unwrap_or_f64 some_or_err_f64 some_bool none_bool is_some_bool is_none_bool unwrap_some_bool unwrap_or_bool some_or_err_bool some_string none_string is_some_string is_none_string unwrap_some_string unwrap_or_string some_or_err_string)) + +(fn some_value_i32 ((value i32)) -> (option i32) + (some_i32 value)) + +(fn none_value_i32 () -> (option i32) + (none_i32)) + +(fn some_value_i64 ((value i64)) -> (option i64) + (some_i64 value)) + +(fn none_value_i64 () -> (option i64) + (none_i64)) + +(fn some_value_u32 ((value u32)) -> (option u32) + (some_u32 value)) + +(fn none_value_u32 () -> (option u32) + (none_u32)) + +(fn some_value_u64 ((value u64)) -> (option u64) + (some_u64 value)) + +(fn none_value_u64 () -> (option u64) + (none_u64)) + +(fn some_value_f64 ((value f64)) -> (option f64) + (some_f64 value)) + +(fn none_value_f64 () -> (option f64) + (none_f64)) + +(fn some_value_bool ((value bool)) -> (option bool) + (some_bool value)) + +(fn none_value_bool () -> (option bool) + (none_bool)) + +(fn some_value_string ((value string)) -> (option string) + (some_string value)) + +(fn none_value_string () -> (option string) + (none_string)) + +(fn imported_option_i32_observation_ok () -> bool + (if (is_some_i32 (some_value_i32 42)) + (is_none_i32 (none_value_i32)) + false)) + +(fn imported_option_i32_unwrap_some_score () -> i32 + (+ (unwrap_some_i32 (some_value_i32 20)) (unwrap_some_i32 (some_value_i32 22)))) + +(fn imported_option_i32_unwrap_or_score () -> i32 + (+ (unwrap_or_i32 (some_value_i32 40) 0) (unwrap_or_i32 (none_value_i32) 2))) + +(fn imported_option_i32_some_or_err_ok () -> bool + (match (some_or_err_i32 (some_value_i32 42) 5) + ((ok payload) + (= payload 42)) + ((err code) + false))) + +(fn imported_option_i32_some_or_err_err () -> bool + (match (some_or_err_i32 (none_value_i32) 5) + ((ok payload) + false) + ((err code) + (= code 5)))) + +(fn imported_option_i64_observation_ok () -> bool + (if (is_some_i64 (some_value_i64 2147483648i64)) + (is_none_i64 (none_value_i64)) + false)) + +(fn imported_option_i64_unwrap_some_score () -> i64 + (+ (unwrap_some_i64 (some_value_i64 2147483648i64)) (unwrap_some_i64 (some_value_i64 12i64)))) + +(fn imported_option_i64_unwrap_or_score () -> i64 + (+ (unwrap_or_i64 (some_value_i64 40i64) 0i64) (unwrap_or_i64 (none_value_i64) 2i64))) + +(fn imported_option_i64_some_or_err_ok () -> bool + (match (some_or_err_i64 (some_value_i64 2147483648i64) 6) + ((ok payload) + (= payload 2147483648i64)) + ((err code) + false))) + +(fn imported_option_i64_some_or_err_err () -> bool + (match (some_or_err_i64 (none_value_i64) 6) + ((ok payload) + false) + ((err code) + (= code 6)))) + +(fn imported_option_u32_observation_ok () -> bool + (if (is_some_u32 (some_value_u32 42u32)) + (is_none_u32 (none_value_u32)) + false)) + +(fn imported_option_u32_unwrap_some_score () -> u32 + (+ (unwrap_some_u32 (some_value_u32 20u32)) (unwrap_some_u32 (some_value_u32 22u32)))) + +(fn imported_option_u32_unwrap_or_score () -> u32 + (+ (unwrap_or_u32 (some_value_u32 40u32) 0u32) (unwrap_or_u32 (none_value_u32) 2u32))) + +(fn imported_option_u32_some_or_err_ok () -> bool + (match (some_or_err_u32 (some_value_u32 42u32) 10) + ((ok payload) + (= payload 42u32)) + ((err code) + false))) + +(fn imported_option_u32_some_or_err_err () -> bool + (match (some_or_err_u32 (none_value_u32) 10) + ((ok payload) + false) + ((err code) + (= code 10)))) + +(fn imported_option_u64_observation_ok () -> bool + (if (is_some_u64 (some_value_u64 4294967296u64)) + (is_none_u64 (none_value_u64)) + false)) + +(fn imported_option_u64_unwrap_some_score () -> u64 + (+ (unwrap_some_u64 (some_value_u64 4294967296u64)) (unwrap_some_u64 (some_value_u64 12u64)))) + +(fn imported_option_u64_unwrap_or_score () -> u64 + (+ (unwrap_or_u64 (some_value_u64 40u64) 0u64) (unwrap_or_u64 (none_value_u64) 2u64))) + +(fn imported_option_u64_some_or_err_ok () -> bool + (match (some_or_err_u64 (some_value_u64 4294967296u64) 11) + ((ok payload) + (= payload 4294967296u64)) + ((err code) + false))) + +(fn imported_option_u64_some_or_err_err () -> bool + (match (some_or_err_u64 (none_value_u64) 11) + ((ok payload) + false) + ((err code) + (= code 11)))) + +(fn imported_option_f64_observation_ok () -> bool + (if (is_some_f64 (some_value_f64 42.5)) + (is_none_f64 (none_value_f64)) + false)) + +(fn imported_option_f64_unwrap_some_score () -> f64 + (+ (unwrap_some_f64 (some_value_f64 20.5)) (unwrap_some_f64 (some_value_f64 21.5)))) + +(fn imported_option_f64_unwrap_or_score () -> f64 + (+ (unwrap_or_f64 (some_value_f64 40.0) 0.0) (unwrap_or_f64 (none_value_f64) 2.0))) + +(fn imported_option_f64_some_or_err_ok () -> bool + (match (some_or_err_f64 (some_value_f64 42.5) 7) + ((ok payload) + (= payload 42.5)) + ((err code) + false))) + +(fn imported_option_f64_some_or_err_err () -> bool + (match (some_or_err_f64 (none_value_f64) 7) + ((ok payload) + false) + ((err code) + (= code 7)))) + +(fn imported_option_bool_observation_ok () -> bool + (if (is_some_bool (some_value_bool true)) + (is_none_bool (none_value_bool)) + false)) + +(fn imported_option_bool_unwrap_some_ok () -> bool + (unwrap_some_bool (some_value_bool true))) + +(fn imported_option_bool_unwrap_or_ok () -> bool + (if (unwrap_or_bool (some_value_bool true) false) + (unwrap_or_bool (none_value_bool) true) + false)) + +(fn imported_option_bool_some_or_err_ok () -> bool + (match (some_or_err_bool (some_value_bool true) 8) + ((ok payload) + payload) + ((err code) + false))) + +(fn imported_option_bool_some_or_err_err () -> bool + (match (some_or_err_bool (none_value_bool) 8) + ((ok payload) + false) + ((err code) + (= code 8)))) + +(fn imported_option_string_observation_ok () -> bool + (if (is_some_string (some_value_string "slovo")) + (is_none_string (none_value_string)) + false)) + +(fn imported_option_string_unwrap_some_ok () -> bool + (= (unwrap_some_string (some_value_string "slovo")) "slovo")) + +(fn imported_option_string_unwrap_or_ok () -> bool + (if (= (unwrap_or_string (some_value_string "oak") "") "oak") + (= (unwrap_or_string (none_value_string) "fallback") "fallback") + false)) + +(fn imported_option_string_some_or_err_ok () -> bool + (match (some_or_err_string (some_value_string "slovo") 9) + ((ok payload) + (= payload "slovo")) + ((err code) + false))) + +(fn imported_option_string_some_or_err_err () -> bool + (match (some_or_err_string (none_value_string) 9) + ((ok payload) + false) + ((err code) + (= code 9)))) + +(fn imported_option_helpers_ok () -> bool + (if (imported_option_i32_observation_ok) + (if (= (imported_option_i32_unwrap_some_score) 42) + (if (= (imported_option_i32_unwrap_or_score) 42) + (if (imported_option_i32_some_or_err_ok) + (if (imported_option_i32_some_or_err_err) + (if (imported_option_u32_observation_ok) + (if (= (imported_option_u32_unwrap_some_score) 42u32) + (if (= (imported_option_u32_unwrap_or_score) 42u32) + (if (imported_option_u32_some_or_err_ok) + (if (imported_option_u32_some_or_err_err) + (if (imported_option_i64_observation_ok) + (if (= (imported_option_i64_unwrap_some_score) 2147483660i64) + (if (= (imported_option_i64_unwrap_or_score) 42i64) + (if (imported_option_i64_some_or_err_ok) + (if (imported_option_i64_some_or_err_err) + (if (imported_option_u64_observation_ok) + (if (= (imported_option_u64_unwrap_some_score) 4294967308u64) + (if (= (imported_option_u64_unwrap_or_score) 42u64) + (if (imported_option_u64_some_or_err_ok) + (if (imported_option_u64_some_or_err_err) + (if (imported_option_f64_observation_ok) + (if (= (imported_option_f64_unwrap_some_score) 42.0) + (if (= (imported_option_f64_unwrap_or_score) 42.0) + (if (imported_option_f64_some_or_err_ok) + (if (imported_option_f64_some_or_err_err) + (if (imported_option_bool_observation_ok) + (if (imported_option_bool_unwrap_some_ok) + (if (imported_option_bool_unwrap_or_ok) + (if (imported_option_bool_some_or_err_ok) + (if (imported_option_bool_some_or_err_err) + (if (imported_option_string_observation_ok) + (if (imported_option_string_unwrap_some_ok) + (if (imported_option_string_unwrap_or_ok) + (if (imported_option_string_some_or_err_ok) + (imported_option_string_some_or_err_err) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn main () -> i32 + (if (imported_option_helpers_ok) + 42 + 1)) + +(test "explicit local option i32 observation" + (imported_option_i32_observation_ok)) + +(test "explicit local option i32 unwrap_some" + (= (imported_option_i32_unwrap_some_score) 42)) + +(test "explicit local option i32 unwrap_or" + (= (imported_option_i32_unwrap_or_score) 42)) + +(test "explicit local option i32 some_or_err ok" + (imported_option_i32_some_or_err_ok)) + +(test "explicit local option i32 some_or_err err" + (imported_option_i32_some_or_err_err)) + +(test "explicit local option u32 observation" + (imported_option_u32_observation_ok)) + +(test "explicit local option u32 unwrap_some" + (= (imported_option_u32_unwrap_some_score) 42u32)) + +(test "explicit local option u32 unwrap_or" + (= (imported_option_u32_unwrap_or_score) 42u32)) + +(test "explicit local option u32 some_or_err ok" + (imported_option_u32_some_or_err_ok)) + +(test "explicit local option u32 some_or_err err" + (imported_option_u32_some_or_err_err)) + +(test "explicit local option i64 observation" + (imported_option_i64_observation_ok)) + +(test "explicit local option i64 unwrap_some" + (= (imported_option_i64_unwrap_some_score) 2147483660i64)) + +(test "explicit local option i64 unwrap_or" + (= (imported_option_i64_unwrap_or_score) 42i64)) + +(test "explicit local option i64 some_or_err ok" + (imported_option_i64_some_or_err_ok)) + +(test "explicit local option i64 some_or_err err" + (imported_option_i64_some_or_err_err)) + +(test "explicit local option u64 observation" + (imported_option_u64_observation_ok)) + +(test "explicit local option u64 unwrap_some" + (= (imported_option_u64_unwrap_some_score) 4294967308u64)) + +(test "explicit local option u64 unwrap_or" + (= (imported_option_u64_unwrap_or_score) 42u64)) + +(test "explicit local option u64 some_or_err ok" + (imported_option_u64_some_or_err_ok)) + +(test "explicit local option u64 some_or_err err" + (imported_option_u64_some_or_err_err)) + +(test "explicit local option f64 observation" + (imported_option_f64_observation_ok)) + +(test "explicit local option f64 unwrap_some" + (= (imported_option_f64_unwrap_some_score) 42.0)) + +(test "explicit local option f64 unwrap_or" + (= (imported_option_f64_unwrap_or_score) 42.0)) + +(test "explicit local option f64 some_or_err ok" + (imported_option_f64_some_or_err_ok)) + +(test "explicit local option f64 some_or_err err" + (imported_option_f64_some_or_err_err)) + +(test "explicit local option bool observation" + (imported_option_bool_observation_ok)) + +(test "explicit local option bool unwrap_some" + (imported_option_bool_unwrap_some_ok)) + +(test "explicit local option bool unwrap_or" + (imported_option_bool_unwrap_or_ok)) + +(test "explicit local option bool some_or_err ok" + (imported_option_bool_some_or_err_ok)) + +(test "explicit local option bool some_or_err err" + (imported_option_bool_some_or_err_err)) + +(test "explicit local option string observation" + (imported_option_string_observation_ok)) + +(test "explicit local option string unwrap_some" + (imported_option_string_unwrap_some_ok)) + +(test "explicit local option string unwrap_or" + (imported_option_string_unwrap_or_ok)) + +(test "explicit local option string some_or_err ok" + (imported_option_string_some_or_err_ok)) + +(test "explicit local option string some_or_err err" + (imported_option_string_some_or_err_err)) + +(test "explicit local option helpers all" + (= (main) 42)) diff --git a/examples/projects/std-layout-local-option/src/option.slo b/examples/projects/std-layout-local-option/src/option.slo new file mode 100644 index 0000000..1076cfd --- /dev/null +++ b/examples/projects/std-layout-local-option/src/option.slo @@ -0,0 +1,176 @@ +(module option (export some_i32 none_i32 is_some_i32 is_none_i32 unwrap_some_i32 unwrap_or_i32 some_or_err_i32 some_u32 none_u32 is_some_u32 is_none_u32 unwrap_some_u32 unwrap_or_u32 some_or_err_u32 some_i64 none_i64 is_some_i64 is_none_i64 unwrap_some_i64 unwrap_or_i64 some_or_err_i64 some_u64 none_u64 is_some_u64 is_none_u64 unwrap_some_u64 unwrap_or_u64 some_or_err_u64 some_f64 none_f64 is_some_f64 is_none_f64 unwrap_some_f64 unwrap_or_f64 some_or_err_f64 some_bool none_bool is_some_bool is_none_bool unwrap_some_bool unwrap_or_bool some_or_err_bool some_string none_string is_some_string is_none_string unwrap_some_string unwrap_or_string some_or_err_string)) + +(fn some_i32 ((value i32)) -> (option i32) + (some i32 value)) + +(fn none_i32 () -> (option i32) + (none i32)) + +(fn is_some_i32 ((value (option i32))) -> bool + (is_some value)) + +(fn is_none_i32 ((value (option i32))) -> bool + (is_none value)) + +(fn unwrap_some_i32 ((value (option i32))) -> i32 + (unwrap_some value)) + +(fn unwrap_or_i32 ((value (option i32)) (fallback i32)) -> i32 + (if (is_some value) + (unwrap_some value) + fallback)) + +(fn some_or_err_i32 ((value (option i32)) (err_code i32)) -> (result i32 i32) + (if (is_some value) + (ok i32 i32 (unwrap_some value)) + (err i32 i32 err_code))) + +(fn some_u32 ((value u32)) -> (option u32) + (some u32 value)) + +(fn none_u32 () -> (option u32) + (none u32)) + +(fn is_some_u32 ((value (option u32))) -> bool + (is_some value)) + +(fn is_none_u32 ((value (option u32))) -> bool + (is_none value)) + +(fn unwrap_some_u32 ((value (option u32))) -> u32 + (unwrap_some value)) + +(fn unwrap_or_u32 ((value (option u32)) (fallback u32)) -> u32 + (if (is_some value) + (unwrap_some value) + fallback)) + +(fn some_or_err_u32 ((value (option u32)) (err_code i32)) -> (result u32 i32) + (if (is_some value) + (ok u32 i32 (unwrap_some value)) + (err u32 i32 err_code))) + +(fn some_i64 ((value i64)) -> (option i64) + (some i64 value)) + +(fn none_i64 () -> (option i64) + (none i64)) + +(fn is_some_i64 ((value (option i64))) -> bool + (is_some value)) + +(fn is_none_i64 ((value (option i64))) -> bool + (is_none value)) + +(fn unwrap_some_i64 ((value (option i64))) -> i64 + (unwrap_some value)) + +(fn unwrap_or_i64 ((value (option i64)) (fallback i64)) -> i64 + (if (is_some value) + (unwrap_some value) + fallback)) + +(fn some_or_err_i64 ((value (option i64)) (err_code i32)) -> (result i64 i32) + (if (is_some value) + (ok i64 i32 (unwrap_some value)) + (err i64 i32 err_code))) + +(fn some_u64 ((value u64)) -> (option u64) + (some u64 value)) + +(fn none_u64 () -> (option u64) + (none u64)) + +(fn is_some_u64 ((value (option u64))) -> bool + (is_some value)) + +(fn is_none_u64 ((value (option u64))) -> bool + (is_none value)) + +(fn unwrap_some_u64 ((value (option u64))) -> u64 + (unwrap_some value)) + +(fn unwrap_or_u64 ((value (option u64)) (fallback u64)) -> u64 + (if (is_some value) + (unwrap_some value) + fallback)) + +(fn some_or_err_u64 ((value (option u64)) (err_code i32)) -> (result u64 i32) + (if (is_some value) + (ok u64 i32 (unwrap_some value)) + (err u64 i32 err_code))) + +(fn some_f64 ((value f64)) -> (option f64) + (some f64 value)) + +(fn none_f64 () -> (option f64) + (none f64)) + +(fn is_some_f64 ((value (option f64))) -> bool + (is_some value)) + +(fn is_none_f64 ((value (option f64))) -> bool + (is_none value)) + +(fn unwrap_some_f64 ((value (option f64))) -> f64 + (unwrap_some value)) + +(fn unwrap_or_f64 ((value (option f64)) (fallback f64)) -> f64 + (if (is_some value) + (unwrap_some value) + fallback)) + +(fn some_or_err_f64 ((value (option f64)) (err_code i32)) -> (result f64 i32) + (if (is_some value) + (ok f64 i32 (unwrap_some value)) + (err f64 i32 err_code))) + +(fn some_bool ((value bool)) -> (option bool) + (some bool value)) + +(fn none_bool () -> (option bool) + (none bool)) + +(fn is_some_bool ((value (option bool))) -> bool + (is_some value)) + +(fn is_none_bool ((value (option bool))) -> bool + (is_none value)) + +(fn unwrap_some_bool ((value (option bool))) -> bool + (unwrap_some value)) + +(fn unwrap_or_bool ((value (option bool)) (fallback bool)) -> bool + (if (is_some value) + (unwrap_some value) + fallback)) + +(fn some_or_err_bool ((value (option bool)) (err_code i32)) -> (result bool i32) + (if (is_some value) + (ok bool i32 (unwrap_some value)) + (err bool i32 err_code))) + +(fn some_string ((value string)) -> (option string) + (some string value)) + +(fn none_string () -> (option string) + (none string)) + +(fn is_some_string ((value (option string))) -> bool + (is_some value)) + +(fn is_none_string ((value (option string))) -> bool + (is_none value)) + +(fn unwrap_some_string ((value (option string))) -> string + (unwrap_some value)) + +(fn unwrap_or_string ((value (option string)) (fallback string)) -> string + (if (is_some value) + (unwrap_some value) + fallback)) + +(fn some_or_err_string ((value (option string)) (err_code i32)) -> (result string i32) + (if (is_some value) + (ok string i32 (unwrap_some value)) + (err string i32 err_code))) diff --git a/examples/projects/std-layout-local-process/slovo.toml b/examples/projects/std-layout-local-process/slovo.toml new file mode 100644 index 0000000..010f820 --- /dev/null +++ b/examples/projects/std-layout-local-process/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-layout-local-process" +source_root = "src" +entry = "main" diff --git a/examples/projects/std-layout-local-process/src/main.slo b/examples/projects/std-layout-local-process/src/main.slo new file mode 100644 index 0000000..f650249 --- /dev/null +++ b/examples/projects/std-layout-local-process/src/main.slo @@ -0,0 +1,319 @@ +(module main) + +(import process (argc arg arg_result arg_option has_arg arg_or arg_or_empty arg_i32_result arg_i32_option arg_i32_or_zero arg_i32_or arg_u32_result arg_u32_option arg_u32_or_zero arg_u32_or arg_i64_result arg_i64_option arg_i64_or_zero arg_i64_or arg_u64_result arg_u64_option arg_u64_or_zero arg_u64_or arg_f64_result arg_f64_option arg_f64_or_zero arg_f64_or arg_bool_result arg_bool_option arg_bool_or_false arg_bool_or)) + +(fn impossible_index () -> i32 + 99999) + +(fn first_arg () -> string + (arg 0)) + +(fn first_index () -> i32 + 0) + +(fn imported_arg_count_positive () -> bool + (< 0 (argc))) + +(fn imported_arg_result_oob_err_one () -> bool + (match (arg_result (impossible_index)) + ((ok payload) + false) + ((err code) + (= code 1)))) + +(fn imported_arg_option_flows_ok () -> bool + (if (match (arg_option (first_index)) + ((some payload) + (= payload (arg (first_index)))) + ((none) + false)) + (match (arg_option (impossible_index)) + ((some payload) + false) + ((none) + true)) + false)) + +(fn imported_has_arg_zero () -> bool + (has_arg 0)) + +(fn imported_arg_fallback_missing_ok () -> bool + (if (= (arg_or (impossible_index) "fallback") "fallback") + (= (arg_or_empty (impossible_index)) "") + false)) + +(fn imported_arg_fallback_present_ok () -> bool + (if (has_arg 0) + (if (= (arg_or 0 "fallback") (arg 0)) + (= (arg_or_empty 0) (arg 0)) + false) + false)) + +(fn imported_arg_i32_missing_err_one () -> bool + (match (arg_i32_result (impossible_index)) + ((ok value) + false) + ((err code) + (= code 1)))) + +(fn imported_arg_i32_present_invalid_zero () -> bool + (= (arg_i32_or_zero (first_index)) 0)) + +(fn imported_arg_u32_missing_err_one () -> bool + (match (arg_u32_result (impossible_index)) + ((ok value) + false) + ((err code) + (= code 1)))) + +(fn imported_arg_u32_present_invalid_zero () -> bool + (= (arg_u32_or_zero (first_index)) 0u32)) + +(fn imported_arg_i64_missing_err_one () -> bool + (match (arg_i64_result (impossible_index)) + ((ok value) + false) + ((err code) + (= code 1)))) + +(fn imported_arg_i64_present_invalid_zero () -> bool + (= (arg_i64_or_zero (first_index)) 0i64)) + +(fn imported_arg_u64_missing_err_one () -> bool + (match (arg_u64_result (impossible_index)) + ((ok value) + false) + ((err code) + (= code 1)))) + +(fn imported_arg_u64_present_invalid_zero () -> bool + (= (arg_u64_or_zero (first_index)) 0u64)) + +(fn imported_arg_f64_missing_err_one () -> bool + (match (arg_f64_result (impossible_index)) + ((ok value) + false) + ((err code) + (= code 1)))) + +(fn imported_arg_f64_present_invalid_zero () -> bool + (= (arg_f64_or_zero (first_index)) 0.0)) + +(fn imported_arg_bool_missing_err_one () -> bool + (match (arg_bool_result (impossible_index)) + ((ok value) + false) + ((err code) + (= code 1)))) + +(fn imported_arg_bool_present_invalid_false () -> bool + (if (arg_bool_or_false (first_index)) + false + true)) + +(fn imported_arg_typed_options_ok () -> bool + (if (match (arg_i32_option (impossible_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_i32_option (first_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_u32_option (impossible_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_u32_option (first_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_i64_option (impossible_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_i64_option (first_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_u64_option (impossible_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_u64_option (first_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_f64_option (impossible_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_f64_option (first_index)) + ((some value) + false) + ((none) + true)) + (if (match (arg_bool_option (impossible_index)) + ((some value) + false) + ((none) + true)) + (match (arg_bool_option (first_index)) + ((some value) + false) + ((none) + true)) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_arg_typed_custom_fallbacks_ok () -> bool + (if (= (arg_i32_or (impossible_index) 7) 7) + (if (= (arg_i32_or (first_index) 7) 7) + (if (= (arg_u32_or (impossible_index) 7u32) 7u32) + (if (= (arg_u32_or (first_index) 7u32) 7u32) + (if (= (arg_i64_or (impossible_index) 9i64) 9i64) + (if (= (arg_i64_or (first_index) 9i64) 9i64) + (if (= (arg_u64_or (impossible_index) 9u64) 9u64) + (if (= (arg_u64_or (first_index) 9u64) 9u64) + (if (= (arg_f64_or (impossible_index) 1.5) 1.5) + (if (= (arg_f64_or (first_index) 1.5) 1.5) + (if (arg_bool_or (impossible_index) true) + (arg_bool_or (first_index) true) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_process_facade_ok () -> bool + (if (imported_arg_count_positive) + (if (imported_arg_result_oob_err_one) + (if (imported_arg_option_flows_ok) + (if (imported_has_arg_zero) + (if (imported_arg_fallback_missing_ok) + (if (imported_arg_fallback_present_ok) + (if (imported_arg_i32_missing_err_one) + (if (imported_arg_i32_present_invalid_zero) + (if (imported_arg_u32_missing_err_one) + (if (imported_arg_u32_present_invalid_zero) + (if (imported_arg_i64_missing_err_one) + (if (imported_arg_i64_present_invalid_zero) + (if (imported_arg_u64_missing_err_one) + (if (imported_arg_u64_present_invalid_zero) + (if (imported_arg_f64_missing_err_one) + (if (imported_arg_f64_present_invalid_zero) + (if (imported_arg_bool_missing_err_one) + (if (imported_arg_bool_present_invalid_false) + (if (imported_arg_typed_options_ok) + (imported_arg_typed_custom_fallbacks_ok) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn main () -> i32 + (if (imported_process_facade_ok) + 42 + 1)) + +(test "explicit local process argc facade" + (imported_arg_count_positive)) + +(test "explicit local process arg result oob facade" + (imported_arg_result_oob_err_one)) + +(test "explicit local process arg option facade" + (imported_arg_option_flows_ok)) + +(test "explicit local process has arg facade" + (imported_has_arg_zero)) + +(test "explicit local process arg fallback missing facade" + (imported_arg_fallback_missing_ok)) + +(test "explicit local process arg fallback present facade" + (imported_arg_fallback_present_ok)) + +(test "explicit local process arg i32 missing facade" + (imported_arg_i32_missing_err_one)) + +(test "explicit local process arg i32 invalid fallback facade" + (imported_arg_i32_present_invalid_zero)) + +(test "explicit local process arg u32 missing facade" + (imported_arg_u32_missing_err_one)) + +(test "explicit local process arg u32 invalid fallback facade" + (imported_arg_u32_present_invalid_zero)) + +(test "explicit local process arg i64 missing facade" + (imported_arg_i64_missing_err_one)) + +(test "explicit local process arg i64 invalid fallback facade" + (imported_arg_i64_present_invalid_zero)) + +(test "explicit local process arg u64 missing facade" + (imported_arg_u64_missing_err_one)) + +(test "explicit local process arg u64 invalid fallback facade" + (imported_arg_u64_present_invalid_zero)) + +(test "explicit local process arg f64 missing facade" + (imported_arg_f64_missing_err_one)) + +(test "explicit local process arg f64 invalid fallback facade" + (imported_arg_f64_present_invalid_zero)) + +(test "explicit local process arg bool missing facade" + (imported_arg_bool_missing_err_one)) + +(test "explicit local process arg bool invalid fallback facade" + (imported_arg_bool_present_invalid_false)) + +(test "explicit local process typed option facade" + (imported_arg_typed_options_ok)) + +(test "explicit local process typed custom fallback facade" + (imported_arg_typed_custom_fallbacks_ok)) + +(test "explicit local process facade all" + (= (main) 42)) diff --git a/examples/projects/std-layout-local-process/src/process.slo b/examples/projects/std-layout-local-process/src/process.slo new file mode 100644 index 0000000..9830f37 --- /dev/null +++ b/examples/projects/std-layout-local-process/src/process.slo @@ -0,0 +1,176 @@ +(module process (export argc arg arg_result arg_option has_arg arg_or arg_or_empty arg_i32_result arg_i32_option arg_i32_or_zero arg_i32_or arg_u32_result arg_u32_option arg_u32_or_zero arg_u32_or arg_i64_result arg_i64_option arg_i64_or_zero arg_i64_or arg_u64_result arg_u64_option arg_u64_or_zero arg_u64_or arg_f64_result arg_f64_option arg_f64_or_zero arg_f64_or arg_bool_result arg_bool_option arg_bool_or_false arg_bool_or)) + +(import result (ok_or_none_string ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool)) + +(import string (parse_i32_result parse_u32_result parse_i64_result parse_u64_result parse_f64_result parse_bool_result)) + +(fn argc () -> i32 + (std.process.argc)) + +(fn arg ((index i32)) -> string + (std.process.arg index)) + +(fn arg_result ((index i32)) -> (result string i32) + (std.process.arg_result index)) + +(fn arg_option ((index i32)) -> (option string) + (ok_or_none_string (arg_result index))) + +(fn has_arg ((index i32)) -> bool + (if (< index 0) + false + (< index (std.process.argc)))) + +(fn arg_or ((index i32) (fallback string)) -> string + (match (arg_result index) + ((ok payload) + payload) + ((err code) + fallback))) + +(fn arg_or_empty ((index i32)) -> string + (arg_or index "")) + +(fn arg_i32_result ((index i32)) -> (result i32 i32) + (match (arg_result index) + ((ok text) + (parse_i32_result text)) + ((err code) + (err i32 i32 code)))) + +(fn arg_i32_option ((index i32)) -> (option i32) + (ok_or_none_i32 (arg_i32_result index))) + +(fn arg_i32_or_zero ((index i32)) -> i32 + (match (arg_i32_result index) + ((ok value) + value) + ((err code) + 0))) + +(fn arg_i32_or ((index i32) (fallback i32)) -> i32 + (match (arg_i32_result index) + ((ok value) + value) + ((err code) + fallback))) + +(fn arg_u32_result ((index i32)) -> (result u32 i32) + (match (arg_result index) + ((ok text) + (parse_u32_result text)) + ((err code) + (err u32 i32 code)))) + +(fn arg_u32_option ((index i32)) -> (option u32) + (ok_or_none_u32 (arg_u32_result index))) + +(fn arg_u32_or_zero ((index i32)) -> u32 + (match (arg_u32_result index) + ((ok value) + value) + ((err code) + 0u32))) + +(fn arg_u32_or ((index i32) (fallback u32)) -> u32 + (match (arg_u32_result index) + ((ok value) + value) + ((err code) + fallback))) + +(fn arg_i64_result ((index i32)) -> (result i64 i32) + (match (arg_result index) + ((ok text) + (parse_i64_result text)) + ((err code) + (err i64 i32 code)))) + +(fn arg_i64_option ((index i32)) -> (option i64) + (ok_or_none_i64 (arg_i64_result index))) + +(fn arg_i64_or_zero ((index i32)) -> i64 + (match (arg_i64_result index) + ((ok value) + value) + ((err code) + 0i64))) + +(fn arg_i64_or ((index i32) (fallback i64)) -> i64 + (match (arg_i64_result index) + ((ok value) + value) + ((err code) + fallback))) + +(fn arg_u64_result ((index i32)) -> (result u64 i32) + (match (arg_result index) + ((ok text) + (parse_u64_result text)) + ((err code) + (err u64 i32 code)))) + +(fn arg_u64_option ((index i32)) -> (option u64) + (ok_or_none_u64 (arg_u64_result index))) + +(fn arg_u64_or_zero ((index i32)) -> u64 + (match (arg_u64_result index) + ((ok value) + value) + ((err code) + 0u64))) + +(fn arg_u64_or ((index i32) (fallback u64)) -> u64 + (match (arg_u64_result index) + ((ok value) + value) + ((err code) + fallback))) + +(fn arg_f64_result ((index i32)) -> (result f64 i32) + (match (arg_result index) + ((ok text) + (parse_f64_result text)) + ((err code) + (err f64 i32 code)))) + +(fn arg_f64_option ((index i32)) -> (option f64) + (ok_or_none_f64 (arg_f64_result index))) + +(fn arg_f64_or_zero ((index i32)) -> f64 + (match (arg_f64_result index) + ((ok value) + value) + ((err code) + 0.0))) + +(fn arg_f64_or ((index i32) (fallback f64)) -> f64 + (match (arg_f64_result index) + ((ok value) + value) + ((err code) + fallback))) + +(fn arg_bool_result ((index i32)) -> (result bool i32) + (match (arg_result index) + ((ok text) + (parse_bool_result text)) + ((err code) + (err bool i32 code)))) + +(fn arg_bool_option ((index i32)) -> (option bool) + (ok_or_none_bool (arg_bool_result index))) + +(fn arg_bool_or_false ((index i32)) -> bool + (match (arg_bool_result index) + ((ok value) + value) + ((err code) + false))) + +(fn arg_bool_or ((index i32) (fallback bool)) -> bool + (match (arg_bool_result index) + ((ok value) + value) + ((err code) + fallback))) diff --git a/examples/projects/std-layout-local-process/src/result.slo b/examples/projects/std-layout-local-process/src/result.slo new file mode 100644 index 0000000..4a7b77b --- /dev/null +++ b/examples/projects/std-layout-local-process/src/result.slo @@ -0,0 +1,199 @@ +(module result (export ok_i32 err_i32 is_ok_i32 is_err_i32 unwrap_ok_i32 unwrap_err_i32 unwrap_or_i32 ok_or_none_i32 ok_u32 err_u32 is_ok_u32 is_err_u32 unwrap_ok_u32 unwrap_err_u32 unwrap_or_u32 ok_or_none_u32 ok_i64 err_i64 is_ok_i64 is_err_i64 unwrap_ok_i64 unwrap_err_i64 unwrap_or_i64 ok_or_none_i64 ok_u64 err_u64 is_ok_u64 is_err_u64 unwrap_ok_u64 unwrap_err_u64 unwrap_or_u64 ok_or_none_u64 ok_string err_string is_ok_string is_err_string unwrap_ok_string unwrap_err_string unwrap_or_string ok_or_none_string ok_f64 err_f64 is_ok_f64 is_err_f64 unwrap_ok_f64 unwrap_err_f64 unwrap_or_f64 ok_or_none_f64 ok_bool err_bool is_ok_bool is_err_bool unwrap_ok_bool unwrap_err_bool unwrap_or_bool ok_or_none_bool)) + +(fn ok_i32 ((value i32)) -> (result i32 i32) + (ok i32 i32 value)) + +(fn err_i32 ((code i32)) -> (result i32 i32) + (err i32 i32 code)) + +(fn is_ok_i32 ((value (result i32 i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_i32 ((value (result i32 i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_i32 ((value (result i32 i32))) -> i32 + (std.result.unwrap_ok value)) + +(fn unwrap_err_i32 ((value (result i32 i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_i32 ((value (result i32 i32)) (fallback i32)) -> i32 + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + fallback)) + +(fn ok_or_none_i32 ((value (result i32 i32))) -> (option i32) + (if (std.result.is_ok value) + (some i32 (std.result.unwrap_ok value)) + (none i32))) + +(fn ok_u32 ((value u32)) -> (result u32 i32) + (ok u32 i32 value)) + +(fn err_u32 ((code i32)) -> (result u32 i32) + (err u32 i32 code)) + +(fn is_ok_u32 ((value (result u32 i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_u32 ((value (result u32 i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_u32 ((value (result u32 i32))) -> u32 + (std.result.unwrap_ok value)) + +(fn unwrap_err_u32 ((value (result u32 i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_u32 ((value (result u32 i32)) (fallback u32)) -> u32 + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + fallback)) + +(fn ok_or_none_u32 ((value (result u32 i32))) -> (option u32) + (if (std.result.is_ok value) + (some u32 (std.result.unwrap_ok value)) + (none u32))) + +(fn ok_i64 ((value i64)) -> (result i64 i32) + (ok i64 i32 value)) + +(fn err_i64 ((code i32)) -> (result i64 i32) + (err i64 i32 code)) + +(fn is_ok_i64 ((value (result i64 i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_i64 ((value (result i64 i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_i64 ((value (result i64 i32))) -> i64 + (std.result.unwrap_ok value)) + +(fn unwrap_err_i64 ((value (result i64 i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_i64 ((value (result i64 i32)) (fallback i64)) -> i64 + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + fallback)) + +(fn ok_or_none_i64 ((value (result i64 i32))) -> (option i64) + (if (std.result.is_ok value) + (some i64 (std.result.unwrap_ok value)) + (none i64))) + +(fn ok_u64 ((value u64)) -> (result u64 i32) + (ok u64 i32 value)) + +(fn err_u64 ((code i32)) -> (result u64 i32) + (err u64 i32 code)) + +(fn is_ok_u64 ((value (result u64 i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_u64 ((value (result u64 i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_u64 ((value (result u64 i32))) -> u64 + (std.result.unwrap_ok value)) + +(fn unwrap_err_u64 ((value (result u64 i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_u64 ((value (result u64 i32)) (fallback u64)) -> u64 + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + fallback)) + +(fn ok_or_none_u64 ((value (result u64 i32))) -> (option u64) + (if (std.result.is_ok value) + (some u64 (std.result.unwrap_ok value)) + (none u64))) + +(fn ok_string ((value string)) -> (result string i32) + (ok string i32 value)) + +(fn err_string ((code i32)) -> (result string i32) + (err string i32 code)) + +(fn is_ok_string ((value (result string i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_string ((value (result string i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_string ((value (result string i32))) -> string + (std.result.unwrap_ok value)) + +(fn unwrap_err_string ((value (result string i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_string ((value (result string i32)) (fallback string)) -> string + (match value + ((ok payload) + payload) + ((err code) + fallback))) + +(fn ok_or_none_string ((value (result string i32))) -> (option string) + (if (std.result.is_ok value) + (some string (std.result.unwrap_ok value)) + (none string))) + +(fn ok_f64 ((value f64)) -> (result f64 i32) + (ok f64 i32 value)) + +(fn err_f64 ((code i32)) -> (result f64 i32) + (err f64 i32 code)) + +(fn is_ok_f64 ((value (result f64 i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_f64 ((value (result f64 i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_f64 ((value (result f64 i32))) -> f64 + (std.result.unwrap_ok value)) + +(fn unwrap_err_f64 ((value (result f64 i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_f64 ((value (result f64 i32)) (fallback f64)) -> f64 + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + fallback)) + +(fn ok_or_none_f64 ((value (result f64 i32))) -> (option f64) + (if (std.result.is_ok value) + (some f64 (std.result.unwrap_ok value)) + (none f64))) + +(fn ok_bool ((value bool)) -> (result bool i32) + (ok bool i32 value)) + +(fn err_bool ((code i32)) -> (result bool i32) + (err bool i32 code)) + +(fn is_ok_bool ((value (result bool i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_bool ((value (result bool i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_bool ((value (result bool i32))) -> bool + (std.result.unwrap_ok value)) + +(fn unwrap_err_bool ((value (result bool i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_bool ((value (result bool i32)) (fallback bool)) -> bool + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + fallback)) + +(fn ok_or_none_bool ((value (result bool i32))) -> (option bool) + (if (std.result.is_ok value) + (some bool (std.result.unwrap_ok value)) + (none bool))) diff --git a/examples/projects/std-layout-local-process/src/string.slo b/examples/projects/std-layout-local-process/src/string.slo new file mode 100644 index 0000000..6191345 --- /dev/null +++ b/examples/projects/std-layout-local-process/src/string.slo @@ -0,0 +1,129 @@ +(module string (export len concat parse_i32_result parse_i32_option parse_u32_result parse_u32_option parse_i64_result parse_i64_option parse_u64_result parse_u64_option parse_f64_result parse_f64_option parse_bool_result parse_bool_option parse_i32_or_zero parse_u32_or_zero parse_i64_or_zero parse_u64_or_zero parse_f64_or_zero parse_bool_or_false parse_i32_or parse_u32_or parse_i64_or parse_u64_or parse_f64_or parse_bool_or)) + +(import result (ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool)) + +(fn len ((value string)) -> i32 + (std.string.len value)) + +(fn concat ((left string) (right string)) -> string + (std.string.concat left right)) + +(fn parse_i32_result ((value string)) -> (result i32 i32) + (std.string.parse_i32_result value)) + +(fn parse_i32_option ((value string)) -> (option i32) + (ok_or_none_i32 (parse_i32_result value))) + +(fn parse_u32_result ((value string)) -> (result u32 i32) + (std.string.parse_u32_result value)) + +(fn parse_u32_option ((value string)) -> (option u32) + (ok_or_none_u32 (parse_u32_result value))) + +(fn parse_i64_result ((value string)) -> (result i64 i32) + (std.string.parse_i64_result value)) + +(fn parse_i64_option ((value string)) -> (option i64) + (ok_or_none_i64 (parse_i64_result value))) + +(fn parse_u64_result ((value string)) -> (result u64 i32) + (std.string.parse_u64_result value)) + +(fn parse_u64_option ((value string)) -> (option u64) + (ok_or_none_u64 (parse_u64_result value))) + +(fn parse_f64_result ((value string)) -> (result f64 i32) + (std.string.parse_f64_result value)) + +(fn parse_f64_option ((value string)) -> (option f64) + (ok_or_none_f64 (parse_f64_result value))) + +(fn parse_bool_result ((value string)) -> (result bool i32) + (std.string.parse_bool_result value)) + +(fn parse_bool_option ((value string)) -> (option bool) + (ok_or_none_bool (parse_bool_result value))) + +(fn parse_i32_or_zero ((value string)) -> i32 + (match (parse_i32_result value) + ((ok payload) + payload) + ((err code) + 0))) + +(fn parse_u32_or_zero ((value string)) -> u32 + (match (parse_u32_result value) + ((ok payload) + payload) + ((err code) + 0u32))) + +(fn parse_i64_or_zero ((value string)) -> i64 + (match (parse_i64_result value) + ((ok payload) + payload) + ((err code) + 0i64))) + +(fn parse_u64_or_zero ((value string)) -> u64 + (match (parse_u64_result value) + ((ok payload) + payload) + ((err code) + 0u64))) + +(fn parse_f64_or_zero ((value string)) -> f64 + (match (parse_f64_result value) + ((ok payload) + payload) + ((err code) + 0.0))) + +(fn parse_bool_or_false ((value string)) -> bool + (match (parse_bool_result value) + ((ok payload) + payload) + ((err code) + false))) + +(fn parse_i32_or ((value string) (fallback i32)) -> i32 + (match (parse_i32_result value) + ((ok payload) + payload) + ((err code) + fallback))) + +(fn parse_u32_or ((value string) (fallback u32)) -> u32 + (match (parse_u32_result value) + ((ok payload) + payload) + ((err code) + fallback))) + +(fn parse_i64_or ((value string) (fallback i64)) -> i64 + (match (parse_i64_result value) + ((ok payload) + payload) + ((err code) + fallback))) + +(fn parse_u64_or ((value string) (fallback u64)) -> u64 + (match (parse_u64_result value) + ((ok payload) + payload) + ((err code) + fallback))) + +(fn parse_f64_or ((value string) (fallback f64)) -> f64 + (match (parse_f64_result value) + ((ok payload) + payload) + ((err code) + fallback))) + +(fn parse_bool_or ((value string) (fallback bool)) -> bool + (match (parse_bool_result value) + ((ok payload) + payload) + ((err code) + fallback))) diff --git a/examples/projects/std-layout-local-random/slovo.toml b/examples/projects/std-layout-local-random/slovo.toml new file mode 100644 index 0000000..79f21bf --- /dev/null +++ b/examples/projects/std-layout-local-random/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-layout-local-random" +source_root = "src" +entry = "main" diff --git a/examples/projects/std-layout-local-random/src/main.slo b/examples/projects/std-layout-local-random/src/main.slo new file mode 100644 index 0000000..7757dcd --- /dev/null +++ b/examples/projects/std-layout-local-random/src/main.slo @@ -0,0 +1,22 @@ +(module main) + +(import random (random_i32 random_i32_non_negative)) + +(fn imported_random_i32_non_negative () -> bool + (>= (random_i32) 0)) + +(fn imported_random_facade_ok () -> bool + (if (imported_random_i32_non_negative) + (random_i32_non_negative) + false)) + +(fn main () -> i32 + (if (imported_random_facade_ok) + 42 + 1)) + +(test "explicit local random i32 non-negative facade" + (imported_random_i32_non_negative)) + +(test "explicit local random facade all" + (= (main) 42)) diff --git a/examples/projects/std-layout-local-random/src/random.slo b/examples/projects/std-layout-local-random/src/random.slo new file mode 100644 index 0000000..e5a3d4c --- /dev/null +++ b/examples/projects/std-layout-local-random/src/random.slo @@ -0,0 +1,10 @@ +(module random (export random_i32 random_i32_non_negative)) + +(fn random_i32 () -> i32 + (std.random.i32)) + +(fn random_i32_non_negative () -> bool + (>= (random_i32) 0)) + +(test "local random i32 non-negative facade" + (random_i32_non_negative)) diff --git a/examples/projects/std-layout-local-result/slovo.toml b/examples/projects/std-layout-local-result/slovo.toml new file mode 100644 index 0000000..2b64c0e --- /dev/null +++ b/examples/projects/std-layout-local-result/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-layout-local-result" +source_root = "src" +entry = "main" diff --git a/examples/projects/std-layout-local-result/src/main.slo b/examples/projects/std-layout-local-result/src/main.slo new file mode 100644 index 0000000..8e06604 --- /dev/null +++ b/examples/projects/std-layout-local-result/src/main.slo @@ -0,0 +1,353 @@ +(module main) + +(import result (ok_i32 err_i32 is_ok_i32 is_err_i32 unwrap_ok_i32 unwrap_err_i32 unwrap_or_i32 ok_or_none_i32 ok_u32 err_u32 is_ok_u32 is_err_u32 unwrap_ok_u32 unwrap_err_u32 unwrap_or_u32 ok_or_none_u32 ok_i64 err_i64 is_err_i64 unwrap_err_i64 unwrap_or_i64 ok_or_none_i64 ok_u64 err_u64 is_ok_u64 is_err_u64 unwrap_ok_u64 unwrap_err_u64 unwrap_or_u64 ok_or_none_u64 ok_string err_string is_err_string unwrap_err_string unwrap_or_string ok_or_none_string ok_f64 err_f64 is_err_f64 unwrap_err_f64 unwrap_or_f64 ok_or_none_f64 ok_bool err_bool is_ok_bool is_err_bool unwrap_ok_bool unwrap_err_bool unwrap_or_bool ok_or_none_bool)) + +(fn i32_ok () -> (result i32 i32) + (ok_i32 42)) + +(fn i32_err () -> (result i32 i32) + (err_i32 5)) + +(fn i64_ok () -> (result i64 i32) + (ok_i64 60i64)) + +(fn i64_err () -> (result i64 i32) + (err_i64 6)) + +(fn u32_ok () -> (result u32 i32) + (ok_u32 42u32)) + +(fn u32_err () -> (result u32 i32) + (err_u32 8)) + +(fn u64_ok () -> (result u64 i32) + (ok_u64 4294967296u64)) + +(fn u64_err () -> (result u64 i32) + (err_u64 9)) + +(fn string_ok () -> (result string i32) + (ok_string "value")) + +(fn string_err () -> (result string i32) + (err_string 7)) + +(fn f64_ok () -> (result f64 i32) + (ok_f64 12.5)) + +(fn f64_err () -> (result f64 i32) + (err_f64 1)) + +(fn bool_ok () -> (result bool i32) + (ok_bool true)) + +(fn bool_false () -> (result bool i32) + (ok_bool false)) + +(fn bool_err () -> (result bool i32) + (err_bool 1)) + +(fn imported_i32_result_wrappers_ok () -> bool + (if (is_ok_i32 (i32_ok)) + (if (= (unwrap_ok_i32 (i32_ok)) 42) + (if (is_err_i32 (i32_err)) + (= (unwrap_err_i32 (i32_err)) 5) + false) + false) + false)) + +(fn imported_result_error_wrappers_ok () -> bool + (if (is_err_i64 (i64_err)) + (if (= (unwrap_err_i64 (i64_err)) 6) + (if (is_err_string (string_err)) + (if (= (unwrap_err_string (string_err)) 7) + (if (is_err_f64 (f64_err)) + (= (unwrap_err_f64 (f64_err)) 1) + false) + false) + false) + false) + false)) + +(fn imported_unwrap_or_i32_score () -> i32 + (+ (unwrap_or_i32 (i32_ok) 0) (unwrap_or_i32 (i32_err) 5))) + +(fn imported_ok_or_none_i32_ok () -> bool + (match (ok_or_none_i32 (i32_ok)) + ((some payload) + (= payload 42)) + ((none) + false))) + +(fn imported_ok_or_none_i32_none () -> bool + (match (ok_or_none_i32 (i32_err)) + ((some payload) + false) + ((none) + true))) + +(fn imported_unwrap_or_i64_score () -> i64 + (+ (unwrap_or_i64 (i64_ok) 0i64) (unwrap_or_i64 (i64_err) 5i64))) + +(fn imported_u32_result_wrappers_ok () -> bool + (if (is_ok_u32 (u32_ok)) + (if (= (unwrap_ok_u32 (u32_ok)) 42u32) + (if (is_err_u32 (u32_err)) + (= (unwrap_err_u32 (u32_err)) 8) + false) + false) + false)) + +(fn imported_unwrap_or_u32_score () -> u32 + (+ (unwrap_or_u32 (u32_ok) 0u32) (unwrap_or_u32 (u32_err) 5u32))) + +(fn imported_ok_or_none_u32_ok () -> bool + (match (ok_or_none_u32 (u32_ok)) + ((some payload) + (= payload 42u32)) + ((none) + false))) + +(fn imported_ok_or_none_u32_none () -> bool + (match (ok_or_none_u32 (u32_err)) + ((some payload) + false) + ((none) + true))) + +(fn imported_ok_or_none_i64_ok () -> bool + (match (ok_or_none_i64 (i64_ok)) + ((some payload) + (= payload 60i64)) + ((none) + false))) + +(fn imported_ok_or_none_i64_none () -> bool + (match (ok_or_none_i64 (i64_err)) + ((some payload) + false) + ((none) + true))) + +(fn imported_u64_result_wrappers_ok () -> bool + (if (is_ok_u64 (u64_ok)) + (if (= (unwrap_ok_u64 (u64_ok)) 4294967296u64) + (if (is_err_u64 (u64_err)) + (= (unwrap_err_u64 (u64_err)) 9) + false) + false) + false)) + +(fn imported_unwrap_or_u64_score () -> u64 + (+ (unwrap_or_u64 (u64_ok) 0u64) (unwrap_or_u64 (u64_err) 5u64))) + +(fn imported_ok_or_none_u64_ok () -> bool + (match (ok_or_none_u64 (u64_ok)) + ((some payload) + (= payload 4294967296u64)) + ((none) + false))) + +(fn imported_ok_or_none_u64_none () -> bool + (match (ok_or_none_u64 (u64_err)) + ((some payload) + false) + ((none) + true))) + +(fn imported_unwrap_or_string_ok () -> bool + (if (= (unwrap_or_string (string_ok) "fallback") "value") + (= (unwrap_or_string (string_err) "fallback") "fallback") + false)) + +(fn imported_ok_or_none_string_ok () -> bool + (match (ok_or_none_string (string_ok)) + ((some payload) + (= payload "value")) + ((none) + false))) + +(fn imported_ok_or_none_string_none () -> bool + (match (ok_or_none_string (string_err)) + ((some payload) + false) + ((none) + true))) + +(fn imported_unwrap_or_f64_score () -> f64 + (+ (unwrap_or_f64 (f64_ok) 0.0) (unwrap_or_f64 (f64_err) 2.5))) + +(fn imported_ok_or_none_f64_ok () -> bool + (match (ok_or_none_f64 (f64_ok)) + ((some payload) + (= payload 12.5)) + ((none) + false))) + +(fn imported_ok_or_none_f64_none () -> bool + (match (ok_or_none_f64 (f64_err)) + ((some payload) + false) + ((none) + true))) + +(fn imported_bool_result_helpers_ok () -> bool + (if (is_ok_bool (bool_ok)) + (if (unwrap_ok_bool (bool_ok)) + (if (is_err_bool (bool_err)) + (if (= (unwrap_err_bool (bool_err)) 1) + (if (unwrap_or_bool (bool_false) true) + false + (unwrap_or_bool (bool_err) true)) + false) + false) + false) + false)) + +(fn imported_ok_or_none_bool_ok () -> bool + (match (ok_or_none_bool (bool_ok)) + ((some payload) + payload) + ((none) + false))) + +(fn imported_ok_or_none_bool_none () -> bool + (match (ok_or_none_bool (bool_err)) + ((some payload) + false) + ((none) + true))) + +(fn imported_result_helpers_ok () -> bool + (if (imported_i32_result_wrappers_ok) + (if (imported_u32_result_wrappers_ok) + (if (imported_result_error_wrappers_ok) + (if (= (imported_unwrap_or_i32_score) 47) + (if (imported_ok_or_none_i32_ok) + (if (imported_ok_or_none_i32_none) + (if (= (imported_unwrap_or_u32_score) 47u32) + (if (imported_ok_or_none_u32_ok) + (if (imported_ok_or_none_u32_none) + (if (= (imported_unwrap_or_i64_score) 65i64) + (if (imported_ok_or_none_i64_ok) + (if (imported_ok_or_none_i64_none) + (if (imported_u64_result_wrappers_ok) + (if (= (imported_unwrap_or_u64_score) 4294967301u64) + (if (imported_ok_or_none_u64_ok) + (if (imported_ok_or_none_u64_none) + (if (imported_unwrap_or_string_ok) + (if (imported_ok_or_none_string_ok) + (if (imported_ok_or_none_string_none) + (if (= (imported_unwrap_or_f64_score) 15.0) + (if (imported_ok_or_none_f64_ok) + (if (imported_ok_or_none_f64_none) + (if (imported_bool_result_helpers_ok) + (if (imported_ok_or_none_bool_ok) + (imported_ok_or_none_bool_none) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn main () -> i32 + (if (imported_result_helpers_ok) + 42 + 1)) + +(test "explicit local result i32 wrappers" + (imported_i32_result_wrappers_ok)) + +(test "explicit local result err wrappers" + (imported_result_error_wrappers_ok)) + +(test "explicit local result unwrap_or i32" + (= (imported_unwrap_or_i32_score) 47)) + +(test "explicit local result ok_or_none i32 ok" + (imported_ok_or_none_i32_ok)) + +(test "explicit local result ok_or_none i32 none" + (imported_ok_or_none_i32_none)) + +(test "explicit local result u32 wrappers" + (imported_u32_result_wrappers_ok)) + +(test "explicit local result unwrap_or u32" + (= (imported_unwrap_or_u32_score) 47u32)) + +(test "explicit local result ok_or_none u32 ok" + (imported_ok_or_none_u32_ok)) + +(test "explicit local result ok_or_none u32 none" + (imported_ok_or_none_u32_none)) + +(test "explicit local result unwrap_or i64" + (= (imported_unwrap_or_i64_score) 65i64)) + +(test "explicit local result ok_or_none i64 ok" + (imported_ok_or_none_i64_ok)) + +(test "explicit local result ok_or_none i64 none" + (imported_ok_or_none_i64_none)) + +(test "explicit local result u64 wrappers" + (imported_u64_result_wrappers_ok)) + +(test "explicit local result unwrap_or u64" + (= (imported_unwrap_or_u64_score) 4294967301u64)) + +(test "explicit local result ok_or_none u64 ok" + (imported_ok_or_none_u64_ok)) + +(test "explicit local result ok_or_none u64 none" + (imported_ok_or_none_u64_none)) + +(test "explicit local result unwrap_or string" + (imported_unwrap_or_string_ok)) + +(test "explicit local result ok_or_none string ok" + (imported_ok_or_none_string_ok)) + +(test "explicit local result ok_or_none string none" + (imported_ok_or_none_string_none)) + +(test "explicit local result unwrap_or f64" + (= (imported_unwrap_or_f64_score) 15.0)) + +(test "explicit local result ok_or_none f64 ok" + (imported_ok_or_none_f64_ok)) + +(test "explicit local result ok_or_none f64 none" + (imported_ok_or_none_f64_none)) + +(test "explicit local result bool helpers" + (imported_bool_result_helpers_ok)) + +(test "explicit local result ok_or_none bool ok" + (imported_ok_or_none_bool_ok)) + +(test "explicit local result ok_or_none bool none" + (imported_ok_or_none_bool_none)) + +(test "explicit local result helpers all" + (= (main) 42)) diff --git a/examples/projects/std-layout-local-result/src/result.slo b/examples/projects/std-layout-local-result/src/result.slo new file mode 100644 index 0000000..4a7b77b --- /dev/null +++ b/examples/projects/std-layout-local-result/src/result.slo @@ -0,0 +1,199 @@ +(module result (export ok_i32 err_i32 is_ok_i32 is_err_i32 unwrap_ok_i32 unwrap_err_i32 unwrap_or_i32 ok_or_none_i32 ok_u32 err_u32 is_ok_u32 is_err_u32 unwrap_ok_u32 unwrap_err_u32 unwrap_or_u32 ok_or_none_u32 ok_i64 err_i64 is_ok_i64 is_err_i64 unwrap_ok_i64 unwrap_err_i64 unwrap_or_i64 ok_or_none_i64 ok_u64 err_u64 is_ok_u64 is_err_u64 unwrap_ok_u64 unwrap_err_u64 unwrap_or_u64 ok_or_none_u64 ok_string err_string is_ok_string is_err_string unwrap_ok_string unwrap_err_string unwrap_or_string ok_or_none_string ok_f64 err_f64 is_ok_f64 is_err_f64 unwrap_ok_f64 unwrap_err_f64 unwrap_or_f64 ok_or_none_f64 ok_bool err_bool is_ok_bool is_err_bool unwrap_ok_bool unwrap_err_bool unwrap_or_bool ok_or_none_bool)) + +(fn ok_i32 ((value i32)) -> (result i32 i32) + (ok i32 i32 value)) + +(fn err_i32 ((code i32)) -> (result i32 i32) + (err i32 i32 code)) + +(fn is_ok_i32 ((value (result i32 i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_i32 ((value (result i32 i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_i32 ((value (result i32 i32))) -> i32 + (std.result.unwrap_ok value)) + +(fn unwrap_err_i32 ((value (result i32 i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_i32 ((value (result i32 i32)) (fallback i32)) -> i32 + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + fallback)) + +(fn ok_or_none_i32 ((value (result i32 i32))) -> (option i32) + (if (std.result.is_ok value) + (some i32 (std.result.unwrap_ok value)) + (none i32))) + +(fn ok_u32 ((value u32)) -> (result u32 i32) + (ok u32 i32 value)) + +(fn err_u32 ((code i32)) -> (result u32 i32) + (err u32 i32 code)) + +(fn is_ok_u32 ((value (result u32 i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_u32 ((value (result u32 i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_u32 ((value (result u32 i32))) -> u32 + (std.result.unwrap_ok value)) + +(fn unwrap_err_u32 ((value (result u32 i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_u32 ((value (result u32 i32)) (fallback u32)) -> u32 + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + fallback)) + +(fn ok_or_none_u32 ((value (result u32 i32))) -> (option u32) + (if (std.result.is_ok value) + (some u32 (std.result.unwrap_ok value)) + (none u32))) + +(fn ok_i64 ((value i64)) -> (result i64 i32) + (ok i64 i32 value)) + +(fn err_i64 ((code i32)) -> (result i64 i32) + (err i64 i32 code)) + +(fn is_ok_i64 ((value (result i64 i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_i64 ((value (result i64 i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_i64 ((value (result i64 i32))) -> i64 + (std.result.unwrap_ok value)) + +(fn unwrap_err_i64 ((value (result i64 i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_i64 ((value (result i64 i32)) (fallback i64)) -> i64 + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + fallback)) + +(fn ok_or_none_i64 ((value (result i64 i32))) -> (option i64) + (if (std.result.is_ok value) + (some i64 (std.result.unwrap_ok value)) + (none i64))) + +(fn ok_u64 ((value u64)) -> (result u64 i32) + (ok u64 i32 value)) + +(fn err_u64 ((code i32)) -> (result u64 i32) + (err u64 i32 code)) + +(fn is_ok_u64 ((value (result u64 i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_u64 ((value (result u64 i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_u64 ((value (result u64 i32))) -> u64 + (std.result.unwrap_ok value)) + +(fn unwrap_err_u64 ((value (result u64 i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_u64 ((value (result u64 i32)) (fallback u64)) -> u64 + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + fallback)) + +(fn ok_or_none_u64 ((value (result u64 i32))) -> (option u64) + (if (std.result.is_ok value) + (some u64 (std.result.unwrap_ok value)) + (none u64))) + +(fn ok_string ((value string)) -> (result string i32) + (ok string i32 value)) + +(fn err_string ((code i32)) -> (result string i32) + (err string i32 code)) + +(fn is_ok_string ((value (result string i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_string ((value (result string i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_string ((value (result string i32))) -> string + (std.result.unwrap_ok value)) + +(fn unwrap_err_string ((value (result string i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_string ((value (result string i32)) (fallback string)) -> string + (match value + ((ok payload) + payload) + ((err code) + fallback))) + +(fn ok_or_none_string ((value (result string i32))) -> (option string) + (if (std.result.is_ok value) + (some string (std.result.unwrap_ok value)) + (none string))) + +(fn ok_f64 ((value f64)) -> (result f64 i32) + (ok f64 i32 value)) + +(fn err_f64 ((code i32)) -> (result f64 i32) + (err f64 i32 code)) + +(fn is_ok_f64 ((value (result f64 i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_f64 ((value (result f64 i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_f64 ((value (result f64 i32))) -> f64 + (std.result.unwrap_ok value)) + +(fn unwrap_err_f64 ((value (result f64 i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_f64 ((value (result f64 i32)) (fallback f64)) -> f64 + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + fallback)) + +(fn ok_or_none_f64 ((value (result f64 i32))) -> (option f64) + (if (std.result.is_ok value) + (some f64 (std.result.unwrap_ok value)) + (none f64))) + +(fn ok_bool ((value bool)) -> (result bool i32) + (ok bool i32 value)) + +(fn err_bool ((code i32)) -> (result bool i32) + (err bool i32 code)) + +(fn is_ok_bool ((value (result bool i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_bool ((value (result bool i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_bool ((value (result bool i32))) -> bool + (std.result.unwrap_ok value)) + +(fn unwrap_err_bool ((value (result bool i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_bool ((value (result bool i32)) (fallback bool)) -> bool + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + fallback)) + +(fn ok_or_none_bool ((value (result bool i32))) -> (option bool) + (if (std.result.is_ok value) + (some bool (std.result.unwrap_ok value)) + (none bool))) diff --git a/examples/projects/std-layout-local-string/slovo.toml b/examples/projects/std-layout-local-string/slovo.toml new file mode 100644 index 0000000..2fcdf3d --- /dev/null +++ b/examples/projects/std-layout-local-string/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-layout-local-string" +source_root = "src" +entry = "main" diff --git a/examples/projects/std-layout-local-string/src/main.slo b/examples/projects/std-layout-local-string/src/main.slo new file mode 100644 index 0000000..a4bdf77 --- /dev/null +++ b/examples/projects/std-layout-local-string/src/main.slo @@ -0,0 +1,196 @@ +(module main) + +(import string (len concat parse_i32_result parse_i32_option parse_u32_result parse_u32_option parse_i64_result parse_i64_option parse_u64_result parse_u64_option parse_f64_result parse_f64_option parse_bool_result parse_bool_option parse_i32_or_zero parse_u32_or_zero parse_i64_or_zero parse_u64_or_zero parse_f64_or_zero parse_bool_or_false parse_i32_or parse_u32_or parse_i64_or parse_u64_or parse_f64_or parse_bool_or)) + +(fn imported_string_concat () -> string + (concat "slo" "vo")) + +(fn imported_string_len_concat_score () -> i32 + (+ (len (imported_string_concat)) 37)) + +(fn imported_string_parse_result_ok () -> bool + (if (= (unwrap_ok (parse_i32_result "40")) 40) + (if (= (unwrap_ok (parse_u32_result "40")) 40u32) + (if (= (unwrap_ok (parse_i64_result "40")) 40i64) + (if (= (unwrap_ok (parse_u64_result "40")) 40u64) + (if (= (unwrap_ok (parse_f64_result "40.0")) 40.0) + (imported_string_parse_bool_ok) + false) + false) + false) + false) + false)) + +(fn imported_string_parse_bool_ok () -> bool + (if (unwrap_ok (parse_bool_result "true")) + (= (unwrap_err (parse_bool_result "TRUE")) 1) + false)) + +(fn imported_string_parse_options_ok () -> bool + (if (match (parse_i32_option "40") + ((some payload) + (= payload 40)) + ((none) + false)) + (if (match (parse_i32_option "bad") + ((some payload) + false) + ((none) + true)) + (if (match (parse_u32_option "40") + ((some payload) + (= payload 40u32)) + ((none) + false)) + (if (match (parse_u32_option "bad") + ((some payload) + false) + ((none) + true)) + (if (match (parse_i64_option "40") + ((some payload) + (= payload 40i64)) + ((none) + false)) + (if (match (parse_i64_option "bad") + ((some payload) + false) + ((none) + true)) + (if (match (parse_u64_option "40") + ((some payload) + (= payload 40u64)) + ((none) + false)) + (if (match (parse_u64_option "bad") + ((some payload) + false) + ((none) + true)) + (if (match (parse_f64_option "40.0") + ((some payload) + (= payload 40.0)) + ((none) + false)) + (if (match (parse_f64_option "bad") + ((some payload) + false) + ((none) + true)) + (if (match (parse_bool_option "true") + ((some payload) + payload) + ((none) + false)) + (match (parse_bool_option "bad") + ((some payload) + false) + ((none) + true)) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_string_parse_integer_fallbacks_ok () -> bool + (if (= (parse_i32_or_zero "40") 40) + (if (= (parse_i32_or_zero "bad") 0) + (if (= (parse_u32_or_zero "40") 40u32) + (if (= (parse_u32_or_zero "bad") 0u32) + (if (= (parse_i64_or_zero "40") 40i64) + (if (= (parse_i64_or_zero "bad") 0i64) + (if (= (parse_u64_or_zero "40") 40u64) + (= (parse_u64_or_zero "bad") 0u64) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_string_parse_float_bool_fallbacks_ok () -> bool + (if (= (parse_f64_or_zero "40.0") 40.0) + (if (= (parse_f64_or_zero "bad") 0.0) + (if (parse_bool_or_false "true") + (if (parse_bool_or_false "bad") + false + (if (parse_bool_or_false "false") + false + true)) + false) + false) + false)) + +(fn imported_string_parse_custom_fallbacks_ok () -> bool + (if (= (parse_i32_or "40" 7) 40) + (if (= (parse_i32_or "bad" 7) 7) + (if (= (parse_u32_or "40" 9u32) 40u32) + (if (= (parse_u32_or "bad" 9u32) 9u32) + (if (= (parse_i64_or "40" 9i64) 40i64) + (if (= (parse_i64_or "bad" 9i64) 9i64) + (if (= (parse_u64_or "40" 11u64) 40u64) + (if (= (parse_u64_or "bad" 11u64) 11u64) + (if (= (parse_f64_or "40.0" 1.5) 40.0) + (if (= (parse_f64_or "bad" 1.5) 1.5) + (if (parse_bool_or "true" false) + (if (parse_bool_or "bad" true) + true + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_string_helpers_ok () -> bool + (if (= (imported_string_len_concat_score) 42) + (if (imported_string_parse_result_ok) + (if (imported_string_parse_options_ok) + (if (imported_string_parse_integer_fallbacks_ok) + (if (imported_string_parse_float_bool_fallbacks_ok) + (imported_string_parse_custom_fallbacks_ok) + false) + false) + false) + false) + false)) + +(fn main () -> i32 + (if (imported_string_helpers_ok) + 42 + 1)) + +(test "explicit local string len concat" + (= (imported_string_len_concat_score) 42)) + +(test "explicit local string parse result wrappers" + (imported_string_parse_result_ok)) + +(test "explicit local string parse option wrappers" + (imported_string_parse_options_ok)) + +(test "explicit local string parse integer fallbacks" + (imported_string_parse_integer_fallbacks_ok)) + +(test "explicit local string parse float bool fallbacks" + (imported_string_parse_float_bool_fallbacks_ok)) + +(test "explicit local string parse custom fallbacks" + (imported_string_parse_custom_fallbacks_ok)) + +(test "explicit local string helpers all" + (= (main) 42)) diff --git a/examples/projects/std-layout-local-string/src/result.slo b/examples/projects/std-layout-local-string/src/result.slo new file mode 100644 index 0000000..4a7b77b --- /dev/null +++ b/examples/projects/std-layout-local-string/src/result.slo @@ -0,0 +1,199 @@ +(module result (export ok_i32 err_i32 is_ok_i32 is_err_i32 unwrap_ok_i32 unwrap_err_i32 unwrap_or_i32 ok_or_none_i32 ok_u32 err_u32 is_ok_u32 is_err_u32 unwrap_ok_u32 unwrap_err_u32 unwrap_or_u32 ok_or_none_u32 ok_i64 err_i64 is_ok_i64 is_err_i64 unwrap_ok_i64 unwrap_err_i64 unwrap_or_i64 ok_or_none_i64 ok_u64 err_u64 is_ok_u64 is_err_u64 unwrap_ok_u64 unwrap_err_u64 unwrap_or_u64 ok_or_none_u64 ok_string err_string is_ok_string is_err_string unwrap_ok_string unwrap_err_string unwrap_or_string ok_or_none_string ok_f64 err_f64 is_ok_f64 is_err_f64 unwrap_ok_f64 unwrap_err_f64 unwrap_or_f64 ok_or_none_f64 ok_bool err_bool is_ok_bool is_err_bool unwrap_ok_bool unwrap_err_bool unwrap_or_bool ok_or_none_bool)) + +(fn ok_i32 ((value i32)) -> (result i32 i32) + (ok i32 i32 value)) + +(fn err_i32 ((code i32)) -> (result i32 i32) + (err i32 i32 code)) + +(fn is_ok_i32 ((value (result i32 i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_i32 ((value (result i32 i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_i32 ((value (result i32 i32))) -> i32 + (std.result.unwrap_ok value)) + +(fn unwrap_err_i32 ((value (result i32 i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_i32 ((value (result i32 i32)) (fallback i32)) -> i32 + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + fallback)) + +(fn ok_or_none_i32 ((value (result i32 i32))) -> (option i32) + (if (std.result.is_ok value) + (some i32 (std.result.unwrap_ok value)) + (none i32))) + +(fn ok_u32 ((value u32)) -> (result u32 i32) + (ok u32 i32 value)) + +(fn err_u32 ((code i32)) -> (result u32 i32) + (err u32 i32 code)) + +(fn is_ok_u32 ((value (result u32 i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_u32 ((value (result u32 i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_u32 ((value (result u32 i32))) -> u32 + (std.result.unwrap_ok value)) + +(fn unwrap_err_u32 ((value (result u32 i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_u32 ((value (result u32 i32)) (fallback u32)) -> u32 + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + fallback)) + +(fn ok_or_none_u32 ((value (result u32 i32))) -> (option u32) + (if (std.result.is_ok value) + (some u32 (std.result.unwrap_ok value)) + (none u32))) + +(fn ok_i64 ((value i64)) -> (result i64 i32) + (ok i64 i32 value)) + +(fn err_i64 ((code i32)) -> (result i64 i32) + (err i64 i32 code)) + +(fn is_ok_i64 ((value (result i64 i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_i64 ((value (result i64 i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_i64 ((value (result i64 i32))) -> i64 + (std.result.unwrap_ok value)) + +(fn unwrap_err_i64 ((value (result i64 i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_i64 ((value (result i64 i32)) (fallback i64)) -> i64 + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + fallback)) + +(fn ok_or_none_i64 ((value (result i64 i32))) -> (option i64) + (if (std.result.is_ok value) + (some i64 (std.result.unwrap_ok value)) + (none i64))) + +(fn ok_u64 ((value u64)) -> (result u64 i32) + (ok u64 i32 value)) + +(fn err_u64 ((code i32)) -> (result u64 i32) + (err u64 i32 code)) + +(fn is_ok_u64 ((value (result u64 i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_u64 ((value (result u64 i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_u64 ((value (result u64 i32))) -> u64 + (std.result.unwrap_ok value)) + +(fn unwrap_err_u64 ((value (result u64 i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_u64 ((value (result u64 i32)) (fallback u64)) -> u64 + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + fallback)) + +(fn ok_or_none_u64 ((value (result u64 i32))) -> (option u64) + (if (std.result.is_ok value) + (some u64 (std.result.unwrap_ok value)) + (none u64))) + +(fn ok_string ((value string)) -> (result string i32) + (ok string i32 value)) + +(fn err_string ((code i32)) -> (result string i32) + (err string i32 code)) + +(fn is_ok_string ((value (result string i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_string ((value (result string i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_string ((value (result string i32))) -> string + (std.result.unwrap_ok value)) + +(fn unwrap_err_string ((value (result string i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_string ((value (result string i32)) (fallback string)) -> string + (match value + ((ok payload) + payload) + ((err code) + fallback))) + +(fn ok_or_none_string ((value (result string i32))) -> (option string) + (if (std.result.is_ok value) + (some string (std.result.unwrap_ok value)) + (none string))) + +(fn ok_f64 ((value f64)) -> (result f64 i32) + (ok f64 i32 value)) + +(fn err_f64 ((code i32)) -> (result f64 i32) + (err f64 i32 code)) + +(fn is_ok_f64 ((value (result f64 i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_f64 ((value (result f64 i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_f64 ((value (result f64 i32))) -> f64 + (std.result.unwrap_ok value)) + +(fn unwrap_err_f64 ((value (result f64 i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_f64 ((value (result f64 i32)) (fallback f64)) -> f64 + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + fallback)) + +(fn ok_or_none_f64 ((value (result f64 i32))) -> (option f64) + (if (std.result.is_ok value) + (some f64 (std.result.unwrap_ok value)) + (none f64))) + +(fn ok_bool ((value bool)) -> (result bool i32) + (ok bool i32 value)) + +(fn err_bool ((code i32)) -> (result bool i32) + (err bool i32 code)) + +(fn is_ok_bool ((value (result bool i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_bool ((value (result bool i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_bool ((value (result bool i32))) -> bool + (std.result.unwrap_ok value)) + +(fn unwrap_err_bool ((value (result bool i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_bool ((value (result bool i32)) (fallback bool)) -> bool + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + fallback)) + +(fn ok_or_none_bool ((value (result bool i32))) -> (option bool) + (if (std.result.is_ok value) + (some bool (std.result.unwrap_ok value)) + (none bool))) diff --git a/examples/projects/std-layout-local-string/src/string.slo b/examples/projects/std-layout-local-string/src/string.slo new file mode 100644 index 0000000..6191345 --- /dev/null +++ b/examples/projects/std-layout-local-string/src/string.slo @@ -0,0 +1,129 @@ +(module string (export len concat parse_i32_result parse_i32_option parse_u32_result parse_u32_option parse_i64_result parse_i64_option parse_u64_result parse_u64_option parse_f64_result parse_f64_option parse_bool_result parse_bool_option parse_i32_or_zero parse_u32_or_zero parse_i64_or_zero parse_u64_or_zero parse_f64_or_zero parse_bool_or_false parse_i32_or parse_u32_or parse_i64_or parse_u64_or parse_f64_or parse_bool_or)) + +(import result (ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool)) + +(fn len ((value string)) -> i32 + (std.string.len value)) + +(fn concat ((left string) (right string)) -> string + (std.string.concat left right)) + +(fn parse_i32_result ((value string)) -> (result i32 i32) + (std.string.parse_i32_result value)) + +(fn parse_i32_option ((value string)) -> (option i32) + (ok_or_none_i32 (parse_i32_result value))) + +(fn parse_u32_result ((value string)) -> (result u32 i32) + (std.string.parse_u32_result value)) + +(fn parse_u32_option ((value string)) -> (option u32) + (ok_or_none_u32 (parse_u32_result value))) + +(fn parse_i64_result ((value string)) -> (result i64 i32) + (std.string.parse_i64_result value)) + +(fn parse_i64_option ((value string)) -> (option i64) + (ok_or_none_i64 (parse_i64_result value))) + +(fn parse_u64_result ((value string)) -> (result u64 i32) + (std.string.parse_u64_result value)) + +(fn parse_u64_option ((value string)) -> (option u64) + (ok_or_none_u64 (parse_u64_result value))) + +(fn parse_f64_result ((value string)) -> (result f64 i32) + (std.string.parse_f64_result value)) + +(fn parse_f64_option ((value string)) -> (option f64) + (ok_or_none_f64 (parse_f64_result value))) + +(fn parse_bool_result ((value string)) -> (result bool i32) + (std.string.parse_bool_result value)) + +(fn parse_bool_option ((value string)) -> (option bool) + (ok_or_none_bool (parse_bool_result value))) + +(fn parse_i32_or_zero ((value string)) -> i32 + (match (parse_i32_result value) + ((ok payload) + payload) + ((err code) + 0))) + +(fn parse_u32_or_zero ((value string)) -> u32 + (match (parse_u32_result value) + ((ok payload) + payload) + ((err code) + 0u32))) + +(fn parse_i64_or_zero ((value string)) -> i64 + (match (parse_i64_result value) + ((ok payload) + payload) + ((err code) + 0i64))) + +(fn parse_u64_or_zero ((value string)) -> u64 + (match (parse_u64_result value) + ((ok payload) + payload) + ((err code) + 0u64))) + +(fn parse_f64_or_zero ((value string)) -> f64 + (match (parse_f64_result value) + ((ok payload) + payload) + ((err code) + 0.0))) + +(fn parse_bool_or_false ((value string)) -> bool + (match (parse_bool_result value) + ((ok payload) + payload) + ((err code) + false))) + +(fn parse_i32_or ((value string) (fallback i32)) -> i32 + (match (parse_i32_result value) + ((ok payload) + payload) + ((err code) + fallback))) + +(fn parse_u32_or ((value string) (fallback u32)) -> u32 + (match (parse_u32_result value) + ((ok payload) + payload) + ((err code) + fallback))) + +(fn parse_i64_or ((value string) (fallback i64)) -> i64 + (match (parse_i64_result value) + ((ok payload) + payload) + ((err code) + fallback))) + +(fn parse_u64_or ((value string) (fallback u64)) -> u64 + (match (parse_u64_result value) + ((ok payload) + payload) + ((err code) + fallback))) + +(fn parse_f64_or ((value string) (fallback f64)) -> f64 + (match (parse_f64_result value) + ((ok payload) + payload) + ((err code) + fallback))) + +(fn parse_bool_or ((value string) (fallback bool)) -> bool + (match (parse_bool_result value) + ((ok payload) + payload) + ((err code) + fallback))) diff --git a/examples/projects/std-layout-local-time/slovo.toml b/examples/projects/std-layout-local-time/slovo.toml new file mode 100644 index 0000000..cc71570 --- /dev/null +++ b/examples/projects/std-layout-local-time/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-layout-local-time" +source_root = "src" +entry = "main" diff --git a/examples/projects/std-layout-local-time/src/main.slo b/examples/projects/std-layout-local-time/src/main.slo new file mode 100644 index 0000000..96a8785 --- /dev/null +++ b/examples/projects/std-layout-local-time/src/main.slo @@ -0,0 +1,28 @@ +(module main) + +(import time (monotonic_ms sleep_ms_zero)) + +(fn imported_time_monotonic_non_negative () -> bool + (>= (monotonic_ms) 0)) + +(fn imported_time_sleep_zero_score () -> i32 + (+ (sleep_ms_zero) 42)) + +(fn imported_time_facade_ok () -> bool + (if (imported_time_monotonic_non_negative) + (= (imported_time_sleep_zero_score) 42) + false)) + +(fn main () -> i32 + (if (imported_time_facade_ok) + 42 + 1)) + +(test "explicit local time monotonic facade" + (imported_time_monotonic_non_negative)) + +(test "explicit local time sleep zero facade" + (= (imported_time_sleep_zero_score) 42)) + +(test "explicit local time facade all" + (= (main) 42)) diff --git a/examples/projects/std-layout-local-time/src/time.slo b/examples/projects/std-layout-local-time/src/time.slo new file mode 100644 index 0000000..e261544 --- /dev/null +++ b/examples/projects/std-layout-local-time/src/time.slo @@ -0,0 +1,8 @@ +(module time (export monotonic_ms sleep_ms_zero)) + +(fn monotonic_ms () -> i32 + (std.time.monotonic_ms)) + +(fn sleep_ms_zero () -> i32 + (std.time.sleep_ms 0) + 0) diff --git a/examples/projects/std-layout-local-vec_bool/slovo.toml b/examples/projects/std-layout-local-vec_bool/slovo.toml new file mode 100644 index 0000000..5ec6943 --- /dev/null +++ b/examples/projects/std-layout-local-vec_bool/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-layout-local-vec-bool" +source_root = "src" +entry = "main" diff --git a/examples/projects/std-layout-local-vec_bool/src/main.slo b/examples/projects/std-layout-local-vec_bool/src/main.slo new file mode 100644 index 0000000..d779495 --- /dev/null +++ b/examples/projects/std-layout-local-vec_bool/src/main.slo @@ -0,0 +1,382 @@ +(module main) + +(import vec_bool (empty append len at singleton append2 append3 pair triple is_empty index_or first_or last_or index_option first_option last_option index_of_option last_index_of_option contains count_of concat take starts_with without_prefix ends_with without_suffix drop reverse subvec insert_at insert_range replace_at replace_range remove_at remove_range)) + +(fn imported_empty_length () -> i32 + (len (empty))) + +(fn imported_pair_index () -> bool + (at (pair true false) 1)) + +(fn original_len_after_append () -> i32 + (let values (vec bool) (empty)) + (let more (vec bool) (append values true)) + (len values)) + +(fn option_bool_is_some_value ((value (option bool)) (expected bool)) -> bool + (if (is_some value) + (= (unwrap_some value) expected) + false)) + +(fn option_i32_is_some_value ((value (option i32)) (expected i32)) -> bool + (if (is_some value) + (= (unwrap_some value) expected) + false)) + +(fn imported_builder_helpers_ok () -> bool + (if (= (len (singleton true)) 1) + (if (= (len (append2 (empty) true false)) 2) + (if (= (len (append3 (singleton true) false true false)) 4) + (if (= (pair true false) (append2 (empty) true false)) + (= (triple true false true) (append3 (empty) true false true)) + false) + false) + false) + false)) + +(fn imported_query_helpers_ok () -> bool + (if (is_empty (empty)) + (if (= (first_or (empty) true) true) + (if (= (first_or (pair false true) true) false) + (if (= (last_or (triple true false false) true) false) + (if (= (index_or (pair true false) -1 true) true) + (if (= (index_or (pair true false) 9 true) true) + (= (index_or (pair true false) 1 true) false) + false) + false) + false) + false) + false) + false)) + +(fn imported_option_query_helpers_ok () -> bool + (let repeated (vec bool) (append3 (pair true false) true false true)) + (if (is_none (first_option (empty))) + (if (option_bool_is_some_value (first_option (pair true false)) true) + (if (is_none (last_option (empty))) + (if (option_bool_is_some_value (last_option (triple true false false)) false) + (if (is_none (index_option (pair true false) -1)) + (if (is_none (index_option (pair true false) 9)) + (if (option_bool_is_some_value (index_option (pair true false) 1) false) + (if (is_none (index_of_option (empty) true)) + (if (option_i32_is_some_value (index_of_option repeated false) 1) + (if (is_none (last_index_of_option (empty) false)) + (option_i32_is_some_value (last_index_of_option repeated false) 3) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_starts_with_helper_ok () -> bool + (let values (vec bool) (append2 (triple true false true) false true)) + (let prefix (vec bool) (pair true false)) + (let longer_prefix (vec bool) (append2 values true false)) + (let mismatched_prefix (vec bool) (pair false true)) + (if (starts_with values (empty)) + (if (starts_with values prefix) + (if (starts_with values values) + (if (starts_with values longer_prefix) + false + (if (starts_with values mismatched_prefix) + false + (if (= values (append2 (triple true false true) false true)) + (= prefix (pair true false)) + false))) + false) + false) + false)) + +(fn imported_ends_with_helper_ok () -> bool + (let values (vec bool) (append2 (triple true false true) false true)) + (let suffix (vec bool) (pair false true)) + (let longer_suffix (vec bool) (append2 values true false)) + (let mismatched_suffix (vec bool) (pair false false)) + (if (ends_with values (empty)) + (if (ends_with values suffix) + (if (ends_with values values) + (if (ends_with values longer_suffix) + false + (if (ends_with values mismatched_suffix) + false + (if (= values (append2 (triple true false true) false true)) + (= suffix (pair false true)) + false))) + false) + false) + false)) + +(fn imported_without_suffix_helper_ok () -> bool + (let values (vec bool) (append2 (triple true false true) false true)) + (let suffix (vec bool) (pair false true)) + (let longer_suffix (vec bool) (append2 values true false)) + (let mismatched_suffix (vec bool) (pair false false)) + (if (= (without_suffix values (empty)) values) + (if (= (without_suffix values values) (empty)) + (if (= (without_suffix values suffix) (triple true false true)) + (if (= (without_suffix values longer_suffix) values) + (if (= (without_suffix values mismatched_suffix) values) + (if (= values (append2 (triple true false true) false true)) + (= suffix (pair false true)) + false) + false) + false) + false) + false) + false)) + +(fn imported_without_prefix_helper_ok () -> bool + (let values (vec bool) (append2 (triple true false true) false true)) + (let prefix (vec bool) (pair true false)) + (let longer_prefix (vec bool) (append2 values true false)) + (let mismatched_prefix (vec bool) (pair true true)) + (if (= (without_prefix values (empty)) values) + (if (= (without_prefix values values) (empty)) + (if (= (without_prefix values prefix) (triple true false true)) + (if (= (without_prefix values longer_prefix) values) + (if (= (without_prefix values mismatched_prefix) values) + (if (= values (append2 (triple true false true) false true)) + (= prefix (pair true false)) + false) + false) + false) + false) + false) + false)) + +(fn imported_transform_helpers_ok () -> bool + (if (= (concat (empty) (pair true false)) (pair true false)) + (if (= (concat (pair true false) (triple false true false)) (append3 (pair true false) false true false)) + (if (= (take (triple true false true) -4) (empty)) + (if (= (take (triple true false true) 2) (pair true false)) + (if (= (take (pair true false) 9) (pair true false)) + (if (= (drop (triple true false true) -4) (triple true false true)) + (if (= (drop (triple true false true) 2) (singleton true)) + (if (= (drop (pair true false) 9) (empty)) + (if (= (reverse (empty)) (empty)) + (= (reverse (triple true false false)) (triple false false true)) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_subvec_helper_ok () -> bool + (let original (vec bool) (append2 (triple true false true) false true)) + (if (= (subvec original -1 2) (empty)) + (if (= (subvec original 3 3) (empty)) + (if (= (subvec original 9 10) (empty)) + (if (= (subvec original 2 99) (triple true false true)) + (if (= (subvec original 1 4) (triple false true false)) + (= original (append2 (triple true false true) false true)) + false) + false) + false) + false) + false)) + +(fn imported_insert_helper_ok () -> bool + (let values (vec bool) (triple true false true)) + (if (= (insert_at values 1 false) (append2 (pair true false) false true)) + (if (= (insert_at values 3 false) (append values false)) + (if (= values (triple true false true)) + (if (= (insert_at values -1 false) values) + (= (insert_at values 4 false) values) + false) + false) + false) + false)) + +(fn imported_insert_range_helper_ok () -> bool + (let values (vec bool) (append2 (triple true false true) false true)) + (let inserted (vec bool) (pair false false)) + (if (= (insert_range values -1 inserted) values) + (if (= (insert_range values 2 inserted) (append3 (append2 (pair true false) false false) true false true)) + (if (= (insert_range values 5 inserted) (append2 (append2 (triple true false true) false true) false false)) + (if (= values (append2 (triple true false true) false true)) + (if (= inserted (pair false false)) + (= (insert_range values 6 inserted) values) + false) + false) + false) + false) + false)) + +(fn imported_replace_helper_ok () -> bool + (if (= (replace_at (triple true false true) 1 true) (triple true true true)) + (if (= (replace_at (triple true false true) -1 true) (triple true false true)) + (= (replace_at (pair true false) 2 true) (pair true false)) + false) + false)) + +(fn imported_replace_range_helper_ok () -> bool + (let original (vec bool) (append2 (triple true false true) false true)) + (let replacement (vec bool) (pair false false)) + (if (= (replace_range original -1 2 replacement) original) + (if (= (replace_range original 3 3 replacement) original) + (if (= (replace_range original 4 2 replacement) original) + (if (= (replace_range original 5 9 replacement) original) + (if (= (replace_range original 2 99 replacement) (append2 (pair true false) false false)) + (if (= (replace_range original 1 4 replacement) (append2 (pair true false) false true)) + (if (= original (append2 (triple true false true) false true)) + (= replacement (pair false false)) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_remove_helper_ok () -> bool + (let original (vec bool) (triple true false true)) + (let removed_middle (vec bool) (remove_at original 1)) + (if (= removed_middle (pair true true)) + (if (= (len removed_middle) 2) + (if (= (remove_at original 0) (pair false true)) + (if (= (remove_at original 2) (pair true false)) + (if (= original (triple true false true)) + (if (= (remove_at original -1) original) + (= (remove_at original 3) original) + false) + false) + false) + false) + false) + false)) + +(fn imported_remove_range_helper_ok () -> bool + (let original (vec bool) (append2 (triple true false true) false true)) + (if (= (remove_range original -1 2) original) + (if (= (remove_range original 3 3) original) + (if (= (remove_range original 4 2) original) + (if (= (remove_range original 5 9) original) + (if (= (remove_range original 2 99) (pair true false)) + (if (= (remove_range original 1 4) (pair true true)) + (= original (append2 (triple true false true) false true)) + false) + false) + false) + false) + false) + false)) + +(fn imported_real_program_helpers_ok () -> bool + (let values (vec bool) (append2 (triple true false true) true true)) + (if (= (original_len_after_append) 0) + (if (contains values true) + (if (contains values false) + (if (= (count_of values true) 4) + (= (count_of values false) 1) + false) + false) + false) + false)) + +(fn helpers_all_ok () -> bool + (if (= (imported_empty_length) 0) + (if (= (imported_pair_index) false) + (if (imported_builder_helpers_ok) + (if (imported_query_helpers_ok) + (if (imported_option_query_helpers_ok) + (if (imported_starts_with_helper_ok) + (if (imported_ends_with_helper_ok) + (if (imported_without_suffix_helper_ok) + (if (imported_without_prefix_helper_ok) + (if (imported_transform_helpers_ok) + (if (imported_subvec_helper_ok) + (if (imported_insert_helper_ok) + (if (imported_insert_range_helper_ok) + (if (imported_replace_helper_ok) + (if (imported_replace_range_helper_ok) + (if (imported_remove_helper_ok) + (if (imported_remove_range_helper_ok) + (imported_real_program_helpers_ok) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(test "explicit local vec_bool empty len facade" + (= (imported_empty_length) 0)) + +(test "explicit local vec_bool direct at facade" + (= (imported_pair_index) false)) + +(test "explicit local vec_bool builder helpers" + (imported_builder_helpers_ok)) + +(test "explicit local vec_bool query helpers" + (imported_query_helpers_ok)) + +(test "explicit local vec_bool option query helpers" + (imported_option_query_helpers_ok)) + +(test "explicit local vec_bool starts_with helper" + (imported_starts_with_helper_ok)) + +(test "explicit local vec_bool ends_with helper" + (imported_ends_with_helper_ok)) + +(test "explicit local vec_bool without_suffix helper" + (imported_without_suffix_helper_ok)) + +(test "explicit local vec_bool without_prefix helper" + (imported_without_prefix_helper_ok)) + +(test "explicit local vec_bool transform helpers" + (imported_transform_helpers_ok)) + +(test "explicit local vec_bool subvec helper" + (imported_subvec_helper_ok)) + +(test "explicit local vec_bool insert helper" + (imported_insert_helper_ok)) + +(test "explicit local vec_bool insert range helper" + (imported_insert_range_helper_ok)) + +(test "explicit local vec_bool replace helper" + (imported_replace_helper_ok)) + +(test "explicit local vec_bool replace range helper" + (imported_replace_range_helper_ok)) + +(test "explicit local vec_bool remove helper" + (imported_remove_helper_ok)) + +(test "explicit local vec_bool remove range helper" + (imported_remove_range_helper_ok)) + +(test "explicit local vec_bool real program helpers" + (imported_real_program_helpers_ok)) + +(test "explicit local vec_bool helpers all" + (helpers_all_ok)) + +(fn main () -> i32 + (if (helpers_all_ok) + 42 + 1)) diff --git a/examples/projects/std-layout-local-vec_bool/src/vec_bool.slo b/examples/projects/std-layout-local-vec_bool/src/vec_bool.slo new file mode 100644 index 0000000..bc08933 --- /dev/null +++ b/examples/projects/std-layout-local-vec_bool/src/vec_bool.slo @@ -0,0 +1,228 @@ +(module vec_bool (export empty append len at singleton append2 append3 pair triple is_empty index_or first_or last_or index_option first_option last_option index_of_option last_index_of_option contains count_of concat take starts_with without_prefix ends_with without_suffix drop reverse subvec insert_at insert_range replace_at replace_range remove_at remove_range)) + +(fn empty () -> (vec bool) + (std.vec.bool.empty)) + +(fn append ((values (vec bool)) (value bool)) -> (vec bool) + (std.vec.bool.append values value)) + +(fn len ((values (vec bool))) -> i32 + (std.vec.bool.len values)) + +(fn at ((values (vec bool)) (position i32)) -> bool + (std.vec.bool.index values position)) + +(fn singleton ((value bool)) -> (vec bool) + (append (empty) value)) + +(fn append2 ((values (vec bool)) (first bool) (second bool)) -> (vec bool) + (append (append values first) second)) + +(fn append3 ((values (vec bool)) (first bool) (second bool) (third bool)) -> (vec bool) + (append (append2 values first second) third)) + +(fn pair ((first bool) (second bool)) -> (vec bool) + (append2 (empty) first second)) + +(fn triple ((first bool) (second bool) (third bool)) -> (vec bool) + (append3 (empty) first second third)) + +(fn is_empty ((values (vec bool))) -> bool + (= (len values) 0)) + +(fn index_or ((values (vec bool)) (position i32) (fallback bool)) -> bool + (if (< position 0) + fallback + (if (< position (len values)) + (at values position) + fallback))) + +(fn first_or ((values (vec bool)) (fallback bool)) -> bool + (index_or values 0 fallback)) + +(fn last_or ((values (vec bool)) (fallback bool)) -> bool + (if (is_empty values) + fallback + (at values (- (len values) 1)))) + +(fn index_option ((values (vec bool)) (position i32)) -> (option bool) + (if (< position 0) + (none bool) + (if (< position (len values)) + (some bool (at values position)) + (none bool)))) + +(fn first_option ((values (vec bool))) -> (option bool) + (index_option values 0)) + +(fn last_option ((values (vec bool))) -> (option bool) + (if (is_empty values) + (none bool) + (some bool (at values (- (len values) 1))))) + +(fn index_of_option_loop ((values (vec bool)) (target bool) (position i32) (values_len i32)) -> (option i32) + (if (< position values_len) + (if (= (at values position) target) + (some i32 position) + (index_of_option_loop values target (+ position 1) values_len)) + (none i32))) + +(fn index_of_option ((values (vec bool)) (target bool)) -> (option i32) + (index_of_option_loop values target 0 (len values))) + +(fn last_index_of_option_loop ((values (vec bool)) (target bool) (position i32) (values_len i32) (found_position (option i32))) -> (option i32) + (if (< position values_len) + (last_index_of_option_loop values target (+ position 1) values_len (if (= (at values position) target) + (some i32 position) + found_position)) + found_position)) + +(fn last_index_of_option ((values (vec bool)) (target bool)) -> (option i32) + (last_index_of_option_loop values target 0 (len values) (none i32))) + +(fn contains_loop ((values (vec bool)) (target bool) (position i32) (values_len i32)) -> bool + (if (< position values_len) + (if (= (at values position) target) + true + (contains_loop values target (+ position 1) values_len)) + false)) + +(fn contains ((values (vec bool)) (target bool)) -> bool + (contains_loop values target 0 (len values))) + +(fn count_of_loop ((values (vec bool)) (target bool) (position i32) (values_len i32) (hits i32)) -> i32 + (if (< position values_len) + (count_of_loop values target (+ position 1) values_len (+ hits (if (= (at values position) target) + 1 + 0))) + hits)) + +(fn count_of ((values (vec bool)) (target bool)) -> i32 + (count_of_loop values target 0 (len values) 0)) + +(fn concat_loop ((result (vec bool)) (right (vec bool)) (position i32) (right_len i32)) -> (vec bool) + (if (< position right_len) + (concat_loop (append result (at right position)) right (+ position 1) right_len) + result)) + +(fn concat ((left (vec bool)) (right (vec bool))) -> (vec bool) + (let right_len i32 (len right)) + (concat_loop left right 0 right_len)) + +(fn take_loop ((values (vec bool)) (position i32) (limit i32) (result (vec bool))) -> (vec bool) + (if (< position limit) + (take_loop values (+ position 1) limit (append result (at values position))) + result)) + +(fn take ((values (vec bool)) (count i32)) -> (vec bool) + (let values_len i32 (len values)) + (if (< count 0) + (empty) + (if (< count values_len) + (take_loop values 0 count (empty)) + values))) + +(fn starts_with ((values (vec bool)) (prefix (vec bool))) -> bool + (let prefix_len i32 (len prefix)) + (if (< (len values) prefix_len) + false + (= (take values prefix_len) prefix))) + +(fn without_prefix ((values (vec bool)) (prefix (vec bool))) -> (vec bool) + (if (starts_with values prefix) + (drop values (len prefix)) + values)) + +(fn ends_with ((values (vec bool)) (suffix (vec bool))) -> bool + (let values_len i32 (len values)) + (let suffix_len i32 (len suffix)) + (if (< values_len suffix_len) + false + (= (drop values (- values_len suffix_len)) suffix))) + +(fn without_suffix ((values (vec bool)) (suffix (vec bool))) -> (vec bool) + (if (ends_with values suffix) + (take values (- (len values) (len suffix))) + values)) + +(fn drop_loop ((values (vec bool)) (position i32) (values_len i32) (result (vec bool))) -> (vec bool) + (if (< position values_len) + (drop_loop values (+ position 1) values_len (append result (at values position))) + result)) + +(fn drop ((values (vec bool)) (count i32)) -> (vec bool) + (let values_len i32 (len values)) + (if (< count 0) + values + (if (< count values_len) + (drop_loop values count values_len (empty)) + (empty)))) + +(fn reverse_loop ((values (vec bool)) (position i32) (result (vec bool))) -> (vec bool) + (if (< position 0) + result + (reverse_loop values (- position 1) (append result (at values position))))) + +(fn reverse ((values (vec bool))) -> (vec bool) + (reverse_loop values (- (len values) 1) (empty))) + +(fn subvec ((values (vec bool)) (start i32) (end_exclusive i32)) -> (vec bool) + (if (< start 0) + (empty) + (if (< start end_exclusive) + (take (drop values start) (- end_exclusive start)) + (empty)))) + +(fn insert_at ((values (vec bool)) (position i32) (value bool)) -> (vec bool) + (let values_len i32 (len values)) + (if (< position 0) + values + (if (< position values_len) + (concat (append (take values position) value) (drop values position)) + (if (= position values_len) + (append values value) + values)))) + +(fn insert_range ((values (vec bool)) (position i32) (inserted (vec bool))) -> (vec bool) + (let values_len i32 (len values)) + (if (< position 0) + values + (if (< position values_len) + (concat (concat (take values position) inserted) (drop values position)) + (if (= position values_len) + (concat values inserted) + values)))) + +(fn replace_at ((values (vec bool)) (position i32) (replacement bool)) -> (vec bool) + (if (< position 0) + values + (if (< position (len values)) + (concat (append (take values position) replacement) (drop values (+ position 1))) + values))) + +(fn replace_range ((values (vec bool)) (start i32) (end_exclusive i32) (replacement (vec bool))) -> (vec bool) + (let values_len i32 (len values)) + (if (< start 0) + values + (if (< start values_len) + (if (< start end_exclusive) + (concat (concat (take values start) replacement) (drop values end_exclusive)) + values) + values))) + +(fn remove_at ((values (vec bool)) (position i32)) -> (vec bool) + (if (< position 0) + values + (if (< position (len values)) + (concat (take values position) (drop values (+ position 1))) + values))) + +(fn remove_range ((values (vec bool)) (start i32) (end_exclusive i32)) -> (vec bool) + (let values_len i32 (len values)) + (if (< start 0) + values + (if (< start values_len) + (if (< start end_exclusive) + (concat (take values start) (drop values end_exclusive)) + values) + values))) diff --git a/examples/projects/std-layout-local-vec_f64/slovo.toml b/examples/projects/std-layout-local-vec_f64/slovo.toml new file mode 100644 index 0000000..6a5c7e8 --- /dev/null +++ b/examples/projects/std-layout-local-vec_f64/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-layout-local-vec-f64" +source_root = "src" +entry = "main" diff --git a/examples/projects/std-layout-local-vec_f64/src/main.slo b/examples/projects/std-layout-local-vec_f64/src/main.slo new file mode 100644 index 0000000..ecbde14 --- /dev/null +++ b/examples/projects/std-layout-local-vec_f64/src/main.slo @@ -0,0 +1,379 @@ +(module main) + +(import vec_f64 (empty append len at singleton append2 append3 pair triple is_empty index_or first_or last_or index_option first_option last_option index_of_option last_index_of_option contains sum concat take starts_with without_prefix ends_with without_suffix drop reverse subvec insert_at insert_range replace_at replace_range remove_at remove_range)) + +(fn imported_empty_length () -> i32 + (len (empty))) + +(fn imported_pair_index () -> f64 + (at (pair 40.0 41.0) 1)) + +(fn original_len_after_append () -> i32 + (let values (vec f64) (empty)) + (let more (vec f64) (append values 9.0)) + (len values)) + +(fn option_f64_is_some_value ((value (option f64)) (expected f64)) -> bool + (if (is_some value) + (= (unwrap_some value) expected) + false)) + +(fn option_i32_is_some_value ((value (option i32)) (expected i32)) -> bool + (if (is_some value) + (= (unwrap_some value) expected) + false)) + +(fn imported_builder_helpers_ok () -> bool + (if (= (len (singleton 42.0)) 1) + (if (= (len (append2 (empty) 10.0 20.0)) 2) + (if (= (len (append3 (singleton 10.0) 20.0 30.0 40.0)) 4) + (if (= (pair 5.0 6.0) (append2 (empty) 5.0 6.0)) + (= (triple 7.0 8.0 9.0) (append3 (empty) 7.0 8.0 9.0)) + false) + false) + false) + false)) + +(fn imported_query_helpers_ok () -> bool + (if (is_empty (empty)) + (if (= (first_or (empty) 9.0) 9.0) + (if (= (first_or (pair 40.0 41.0) 9.0) 40.0) + (if (= (last_or (triple 10.0 20.0 30.0) 9.0) 30.0) + (if (= (index_or (pair 40.0 41.0) -1 7.0) 7.0) + (if (= (index_or (pair 40.0 41.0) 9 7.0) 7.0) + (= (index_or (pair 40.0 41.0) 1 7.0) 41.0) + false) + false) + false) + false) + false) + false)) + +(fn imported_option_query_helpers_ok () -> bool + (if (is_none (first_option (empty))) + (if (option_f64_is_some_value (first_option (pair 40.0 41.0)) 40.0) + (if (is_none (last_option (empty))) + (if (option_f64_is_some_value (last_option (triple 10.0 20.0 30.0)) 30.0) + (if (is_none (index_option (pair 40.0 41.0) -1)) + (if (is_none (index_option (pair 40.0 41.0) 9)) + (if (option_f64_is_some_value (index_option (pair 40.0 41.0) 1) 41.0) + (if (is_none (index_of_option (append3 (pair 10.0 20.0) 30.0 20.0 10.0) 99.0)) + (if (option_i32_is_some_value (index_of_option (append3 (pair 10.0 20.0) 30.0 20.0 10.0) 20.0) 1) + (if (is_none (last_index_of_option (append3 (pair 10.0 20.0) 30.0 20.0 10.0) 99.0)) + (option_i32_is_some_value (last_index_of_option (append3 (pair 10.0 20.0) 30.0 20.0 10.0) 20.0) 3) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_starts_with_helper_ok () -> bool + (let values (vec f64) (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + (let prefix (vec f64) (pair 10.0 20.0)) + (let longer_prefix (vec f64) (append2 values 60.0 70.0)) + (let mismatched_prefix (vec f64) (pair 20.0 30.0)) + (if (starts_with values (empty)) + (if (starts_with values prefix) + (if (starts_with values values) + (if (starts_with values longer_prefix) + false + (if (starts_with values mismatched_prefix) + false + (if (= values (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + (= prefix (pair 10.0 20.0)) + false))) + false) + false) + false)) + +(fn imported_ends_with_helper_ok () -> bool + (let values (vec f64) (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + (let suffix (vec f64) (pair 40.0 50.0)) + (let longer_suffix (vec f64) (append2 values 60.0 70.0)) + (let mismatched_suffix (vec f64) (pair 40.0 51.0)) + (if (ends_with values (empty)) + (if (ends_with values suffix) + (if (ends_with values values) + (if (ends_with values longer_suffix) + false + (if (ends_with values mismatched_suffix) + false + (if (= values (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + (= suffix (pair 40.0 50.0)) + false))) + false) + false) + false)) + +(fn imported_without_suffix_helper_ok () -> bool + (let values (vec f64) (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + (let suffix (vec f64) (pair 40.0 50.0)) + (let longer_suffix (vec f64) (append2 values 60.0 70.0)) + (let mismatched_suffix (vec f64) (pair 40.0 51.0)) + (if (= (without_suffix values (empty)) values) + (if (= (without_suffix values values) (empty)) + (if (= (without_suffix values suffix) (triple 10.0 20.0 30.0)) + (if (= (without_suffix values longer_suffix) values) + (if (= (without_suffix values mismatched_suffix) values) + (if (= values (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + (= suffix (pair 40.0 50.0)) + false) + false) + false) + false) + false) + false)) + +(fn imported_without_prefix_helper_ok () -> bool + (let values (vec f64) (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + (let prefix (vec f64) (pair 10.0 20.0)) + (let longer_prefix (vec f64) (append2 values 60.0 70.0)) + (let mismatched_prefix (vec f64) (pair 10.0 21.0)) + (if (= (without_prefix values (empty)) values) + (if (= (without_prefix values values) (empty)) + (if (= (without_prefix values prefix) (triple 30.0 40.0 50.0)) + (if (= (without_prefix values longer_prefix) values) + (if (= (without_prefix values mismatched_prefix) values) + (if (= values (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + (= prefix (pair 10.0 20.0)) + false) + false) + false) + false) + false) + false)) + +(fn imported_transform_helpers_ok () -> bool + (if (= (concat (empty) (pair 7.0 8.0)) (pair 7.0 8.0)) + (if (= (concat (pair 1.0 2.0) (triple 3.0 4.0 5.0)) (append3 (pair 1.0 2.0) 3.0 4.0 5.0)) + (if (= (take (triple 10.0 20.0 30.0) -4) (empty)) + (if (= (take (triple 10.0 20.0 30.0) 2) (pair 10.0 20.0)) + (if (= (take (pair 10.0 20.0) 9) (pair 10.0 20.0)) + (if (= (drop (triple 10.0 20.0 30.0) -4) (triple 10.0 20.0 30.0)) + (if (= (drop (triple 10.0 20.0 30.0) 2) (singleton 30.0)) + (if (= (drop (pair 10.0 20.0) 9) (empty)) + (if (= (reverse (empty)) (empty)) + (= (reverse (append3 (pair 10.0 20.0) 30.0 40.0 50.0)) (append3 (pair 50.0 40.0) 30.0 20.0 10.0)) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_subvec_helper_ok () -> bool + (let original (vec f64) (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + (if (= (subvec original -1 2) (empty)) + (if (= (subvec original 3 3) (empty)) + (if (= (subvec original 9 10) (empty)) + (if (= (subvec original 2 99) (triple 30.0 40.0 50.0)) + (if (= (subvec original 1 4) (triple 20.0 30.0 40.0)) + (= original (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + false) + false) + false) + false) + false)) + +(fn imported_insert_helper_ok () -> bool + (let values (vec f64) (triple 10.0 20.0 30.0)) + (if (= (insert_at values 1 99.0) (append2 (pair 10.0 99.0) 20.0 30.0)) + (if (= (insert_at values 3 99.0) (append values 99.0)) + (if (= values (triple 10.0 20.0 30.0)) + (if (= (insert_at values -1 99.0) values) + (= (insert_at values 4 99.0) values) + false) + false) + false) + false)) + +(fn imported_insert_range_helper_ok () -> bool + (let values (vec f64) (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + (let inserted (vec f64) (pair 77.0 88.0)) + (if (= (insert_range values -1 inserted) values) + (if (= (insert_range values 2 inserted) (append3 (append2 (pair 10.0 20.0) 77.0 88.0) 30.0 40.0 50.0)) + (if (= (insert_range values 5 inserted) (append2 (append2 (triple 10.0 20.0 30.0) 40.0 50.0) 77.0 88.0)) + (if (= values (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + (if (= inserted (pair 77.0 88.0)) + (= (insert_range values 6 inserted) values) + false) + false) + false) + false) + false)) + +(fn imported_replace_helper_ok () -> bool + (if (= (replace_at (triple 10.0 20.0 30.0) 1 99.0) (triple 10.0 99.0 30.0)) + (if (= (replace_at (triple 10.0 20.0 30.0) -1 99.0) (triple 10.0 20.0 30.0)) + (= (replace_at (pair 10.0 20.0) 2 99.0) (pair 10.0 20.0)) + false) + false)) + +(fn imported_replace_range_helper_ok () -> bool + (let original (vec f64) (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + (let replacement (vec f64) (pair 77.0 88.0)) + (if (= (replace_range original -1 2 replacement) original) + (if (= (replace_range original 3 3 replacement) original) + (if (= (replace_range original 4 2 replacement) original) + (if (= (replace_range original 5 9 replacement) original) + (if (= (replace_range original 2 99 replacement) (append2 (pair 10.0 20.0) 77.0 88.0)) + (if (= (replace_range original 1 4 replacement) (append2 (pair 10.0 77.0) 88.0 50.0)) + (if (= original (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + (= replacement (pair 77.0 88.0)) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_remove_helper_ok () -> bool + (let original (vec f64) (triple 10.0 20.0 30.0)) + (let removed_middle (vec f64) (remove_at original 1)) + (if (= removed_middle (pair 10.0 30.0)) + (if (= (len removed_middle) 2) + (if (= (remove_at original 0) (pair 20.0 30.0)) + (if (= (remove_at original 2) (pair 10.0 20.0)) + (if (= original (triple 10.0 20.0 30.0)) + (if (= (remove_at original -1) original) + (= (remove_at original 3) original) + false) + false) + false) + false) + false) + false)) + +(fn imported_remove_range_helper_ok () -> bool + (let original (vec f64) (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + (if (= (remove_range original -1 2) original) + (if (= (remove_range original 3 3) original) + (if (= (remove_range original 4 2) original) + (if (= (remove_range original 5 9) original) + (if (= (remove_range original 2 99) (pair 10.0 20.0)) + (if (= (remove_range original 1 4) (pair 10.0 50.0)) + (= original (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + false) + false) + false) + false) + false) + false)) + +(fn imported_real_program_helpers_ok () -> bool + (let values (vec f64) (append2 (triple 10.0 20.0 30.0) 40.0 50.0)) + (if (= (original_len_after_append) 0) + (if (contains values 20.0) + (if (contains values 99.0) + false + (= (sum values) 150.0)) + false) + false)) + +(fn helpers_all_ok () -> bool + (if (= (imported_empty_length) 0) + (if (= (imported_pair_index) 41.0) + (if (imported_builder_helpers_ok) + (if (imported_query_helpers_ok) + (if (imported_option_query_helpers_ok) + (if (imported_starts_with_helper_ok) + (if (imported_ends_with_helper_ok) + (if (imported_without_suffix_helper_ok) + (if (imported_without_prefix_helper_ok) + (if (imported_transform_helpers_ok) + (if (imported_subvec_helper_ok) + (if (imported_insert_helper_ok) + (if (imported_insert_range_helper_ok) + (if (imported_replace_helper_ok) + (if (imported_replace_range_helper_ok) + (if (imported_remove_helper_ok) + (if (imported_remove_range_helper_ok) + (imported_real_program_helpers_ok) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(test "explicit local vec_f64 empty len facade" + (= (imported_empty_length) 0)) + +(test "explicit local vec_f64 direct at facade" + (= (imported_pair_index) 41.0)) + +(test "explicit local vec_f64 builder helpers" + (imported_builder_helpers_ok)) + +(test "explicit local vec_f64 query helpers" + (imported_query_helpers_ok)) + +(test "explicit local vec_f64 option query helpers" + (imported_option_query_helpers_ok)) + +(test "explicit local vec_f64 starts_with helper" + (imported_starts_with_helper_ok)) + +(test "explicit local vec_f64 ends_with helper" + (imported_ends_with_helper_ok)) + +(test "explicit local vec_f64 without_suffix helper" + (imported_without_suffix_helper_ok)) + +(test "explicit local vec_f64 without_prefix helper" + (imported_without_prefix_helper_ok)) + +(test "explicit local vec_f64 transform helpers" + (imported_transform_helpers_ok)) + +(test "explicit local vec_f64 subvec helper" + (imported_subvec_helper_ok)) + +(test "explicit local vec_f64 insert helper" + (imported_insert_helper_ok)) + +(test "explicit local vec_f64 insert range helper" + (imported_insert_range_helper_ok)) + +(test "explicit local vec_f64 replace helper" + (imported_replace_helper_ok)) + +(test "explicit local vec_f64 replace range helper" + (imported_replace_range_helper_ok)) + +(test "explicit local vec_f64 remove helper" + (imported_remove_helper_ok)) + +(test "explicit local vec_f64 remove range helper" + (imported_remove_range_helper_ok)) + +(test "explicit local vec_f64 real program helpers" + (imported_real_program_helpers_ok)) + +(test "explicit local vec_f64 helpers all" + (helpers_all_ok)) + +(fn main () -> i32 + (if (helpers_all_ok) + 42 + 1)) diff --git a/examples/projects/std-layout-local-vec_f64/src/vec_f64.slo b/examples/projects/std-layout-local-vec_f64/src/vec_f64.slo new file mode 100644 index 0000000..0b36953 --- /dev/null +++ b/examples/projects/std-layout-local-vec_f64/src/vec_f64.slo @@ -0,0 +1,226 @@ +(module vec_f64 (export empty append len at singleton append2 append3 pair triple is_empty index_or first_or last_or index_option first_option last_option index_of_option last_index_of_option contains sum concat take starts_with without_prefix ends_with without_suffix drop reverse subvec insert_at insert_range replace_at replace_range remove_at remove_range)) + +(fn empty () -> (vec f64) + (std.vec.f64.empty)) + +(fn append ((values (vec f64)) (value f64)) -> (vec f64) + (std.vec.f64.append values value)) + +(fn len ((values (vec f64))) -> i32 + (std.vec.f64.len values)) + +(fn at ((values (vec f64)) (position i32)) -> f64 + (std.vec.f64.index values position)) + +(fn singleton ((value f64)) -> (vec f64) + (append (empty) value)) + +(fn append2 ((values (vec f64)) (first f64) (second f64)) -> (vec f64) + (append (append values first) second)) + +(fn append3 ((values (vec f64)) (first f64) (second f64) (third f64)) -> (vec f64) + (append (append2 values first second) third)) + +(fn pair ((first f64) (second f64)) -> (vec f64) + (append2 (empty) first second)) + +(fn triple ((first f64) (second f64) (third f64)) -> (vec f64) + (append3 (empty) first second third)) + +(fn is_empty ((values (vec f64))) -> bool + (= (len values) 0)) + +(fn index_or ((values (vec f64)) (position i32) (fallback f64)) -> f64 + (if (< position 0) + fallback + (if (< position (len values)) + (at values position) + fallback))) + +(fn first_or ((values (vec f64)) (fallback f64)) -> f64 + (index_or values 0 fallback)) + +(fn last_or ((values (vec f64)) (fallback f64)) -> f64 + (if (is_empty values) + fallback + (at values (- (len values) 1)))) + +(fn index_option ((values (vec f64)) (position i32)) -> (option f64) + (if (< position 0) + (none f64) + (if (< position (len values)) + (some f64 (at values position)) + (none f64)))) + +(fn first_option ((values (vec f64))) -> (option f64) + (index_option values 0)) + +(fn last_option ((values (vec f64))) -> (option f64) + (if (is_empty values) + (none f64) + (some f64 (at values (- (len values) 1))))) + +(fn index_of_option_loop ((values (vec f64)) (target f64) (position i32) (values_len i32)) -> (option i32) + (if (< position values_len) + (if (= (at values position) target) + (some i32 position) + (index_of_option_loop values target (+ position 1) values_len)) + (none i32))) + +(fn index_of_option ((values (vec f64)) (target f64)) -> (option i32) + (index_of_option_loop values target 0 (len values))) + +(fn last_index_of_option_loop ((values (vec f64)) (target f64) (position i32) (values_len i32) (found_position (option i32))) -> (option i32) + (if (< position values_len) + (last_index_of_option_loop values target (+ position 1) values_len (if (= (at values position) target) + (some i32 position) + found_position)) + found_position)) + +(fn last_index_of_option ((values (vec f64)) (target f64)) -> (option i32) + (last_index_of_option_loop values target 0 (len values) (none i32))) + +(fn contains_loop ((values (vec f64)) (target f64) (position i32) (values_len i32)) -> bool + (if (< position values_len) + (if (= (at values position) target) + true + (contains_loop values target (+ position 1) values_len)) + false)) + +(fn contains ((values (vec f64)) (target f64)) -> bool + (contains_loop values target 0 (len values))) + +(fn sum_loop ((values (vec f64)) (position i32) (values_len i32) (total f64)) -> f64 + (if (< position values_len) + (sum_loop values (+ position 1) values_len (+ total (at values position))) + total)) + +(fn sum ((values (vec f64))) -> f64 + (sum_loop values 0 (len values) 0.0)) + +(fn concat_loop ((result (vec f64)) (right (vec f64)) (position i32) (right_len i32)) -> (vec f64) + (if (< position right_len) + (concat_loop (append result (at right position)) right (+ position 1) right_len) + result)) + +(fn concat ((left (vec f64)) (right (vec f64))) -> (vec f64) + (let right_len i32 (len right)) + (concat_loop left right 0 right_len)) + +(fn take_loop ((values (vec f64)) (position i32) (limit i32) (result (vec f64))) -> (vec f64) + (if (< position limit) + (take_loop values (+ position 1) limit (append result (at values position))) + result)) + +(fn take ((values (vec f64)) (count i32)) -> (vec f64) + (let values_len i32 (len values)) + (if (< count 0) + (empty) + (if (< count values_len) + (take_loop values 0 count (empty)) + values))) + +(fn starts_with ((values (vec f64)) (prefix (vec f64))) -> bool + (let prefix_len i32 (len prefix)) + (if (< (len values) prefix_len) + false + (= (take values prefix_len) prefix))) + +(fn without_prefix ((values (vec f64)) (prefix (vec f64))) -> (vec f64) + (if (starts_with values prefix) + (drop values (len prefix)) + values)) + +(fn ends_with ((values (vec f64)) (suffix (vec f64))) -> bool + (let values_len i32 (len values)) + (let suffix_len i32 (len suffix)) + (if (< values_len suffix_len) + false + (= (drop values (- values_len suffix_len)) suffix))) + +(fn without_suffix ((values (vec f64)) (suffix (vec f64))) -> (vec f64) + (if (ends_with values suffix) + (take values (- (len values) (len suffix))) + values)) + +(fn drop_loop ((values (vec f64)) (position i32) (values_len i32) (result (vec f64))) -> (vec f64) + (if (< position values_len) + (drop_loop values (+ position 1) values_len (append result (at values position))) + result)) + +(fn drop ((values (vec f64)) (count i32)) -> (vec f64) + (let values_len i32 (len values)) + (if (< count 0) + values + (if (< count values_len) + (drop_loop values count values_len (empty)) + (empty)))) + +(fn reverse_loop ((values (vec f64)) (position i32) (result (vec f64))) -> (vec f64) + (if (< position 0) + result + (reverse_loop values (- position 1) (append result (at values position))))) + +(fn reverse ((values (vec f64))) -> (vec f64) + (reverse_loop values (- (len values) 1) (empty))) + +(fn subvec ((values (vec f64)) (start i32) (end_exclusive i32)) -> (vec f64) + (if (< start 0) + (empty) + (if (< start end_exclusive) + (take (drop values start) (- end_exclusive start)) + (empty)))) + +(fn insert_at ((values (vec f64)) (position i32) (value f64)) -> (vec f64) + (let values_len i32 (len values)) + (if (< position 0) + values + (if (< position values_len) + (concat (append (take values position) value) (drop values position)) + (if (= position values_len) + (append values value) + values)))) + +(fn insert_range ((values (vec f64)) (position i32) (inserted (vec f64))) -> (vec f64) + (let values_len i32 (len values)) + (if (< position 0) + values + (if (< position values_len) + (concat (concat (take values position) inserted) (drop values position)) + (if (= position values_len) + (concat values inserted) + values)))) + +(fn replace_at ((values (vec f64)) (position i32) (replacement f64)) -> (vec f64) + (if (< position 0) + values + (if (< position (len values)) + (concat (append (take values position) replacement) (drop values (+ position 1))) + values))) + +(fn replace_range ((values (vec f64)) (start i32) (end_exclusive i32) (replacement (vec f64))) -> (vec f64) + (let values_len i32 (len values)) + (if (< start 0) + values + (if (< start values_len) + (if (< start end_exclusive) + (concat (concat (take values start) replacement) (drop values end_exclusive)) + values) + values))) + +(fn remove_at ((values (vec f64)) (position i32)) -> (vec f64) + (if (< position 0) + values + (if (< position (len values)) + (concat (take values position) (drop values (+ position 1))) + values))) + +(fn remove_range ((values (vec f64)) (start i32) (end_exclusive i32)) -> (vec f64) + (let values_len i32 (len values)) + (if (< start 0) + values + (if (< start values_len) + (if (< start end_exclusive) + (concat (take values start) (drop values end_exclusive)) + values) + values))) diff --git a/examples/projects/std-layout-local-vec_i32/slovo.toml b/examples/projects/std-layout-local-vec_i32/slovo.toml new file mode 100644 index 0000000..7d79f5a --- /dev/null +++ b/examples/projects/std-layout-local-vec_i32/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-layout-local-vec-i32" +source_root = "src" +entry = "main" diff --git a/examples/projects/std-layout-local-vec_i32/src/main.slo b/examples/projects/std-layout-local-vec_i32/src/main.slo new file mode 100644 index 0000000..df55bf1 --- /dev/null +++ b/examples/projects/std-layout-local-vec_i32/src/main.slo @@ -0,0 +1,427 @@ +(module main) + +(import vec_i32 (empty append len at singleton append2 append3 pair triple repeat range range_from_zero is_empty index_or first_or last_or index_option first_option last_option index_of_option last_index_of_option count_of contains sum concat take starts_with without_prefix ends_with without_suffix drop reverse subvec insert_at insert_range replace_at replace_range remove_at remove_range)) + +(fn imported_empty_length () -> i32 + (len (empty))) + +(fn imported_pair_index () -> i32 + (at (pair 40 41) 1)) + +(fn imported_builder_helpers_ok () -> bool + (if (= (len (singleton 42)) 1) + (if (= (len (append2 (empty) 10 20)) 2) + (if (= (len (append3 (singleton 10) 20 30 40)) 4) + (= (pair 5 6) (append2 (empty) 5 6)) + false) + false) + false)) + +(fn imported_constructor_helpers_ok () -> bool + (if (= (repeat 7 -4) (empty)) + (if (= (repeat 7 0) (empty)) + (if (= (repeat 7 3) (triple 7 7 7)) + (if (= (range_from_zero -1) (empty)) + (if (= (range_from_zero 0) (empty)) + (if (= (range_from_zero 4) (append (triple 0 1 2) 3)) + (= (range_from_zero 2) (pair 0 1)) + false) + false) + false) + false) + false) + false)) + +(fn imported_range_helpers_ok () -> bool + (if (= (range -3 2) (append3 (pair -3 -2) -1 0 1)) + (if (= (range 3 7) (append2 (pair 3 4) 5 6)) + (if (= (range -2 -2) (empty)) + (= (range 7 3) (empty)) + false) + false) + false)) + +(fn option_is_some_value ((value (option i32)) (expected i32)) -> bool + (if (is_some value) + (= (unwrap_some value) expected) + false)) + +(fn imported_query_helpers_ok () -> bool + (if (is_empty (empty)) + (if (= (first_or (empty) 9) 9) + (if (= (first_or (pair 40 41) 9) 40) + (if (= (last_or (triple 10 20 30) 9) 30) + (if (= (index_or (pair 40 41) -1 7) 7) + (if (= (index_or (pair 40 41) 9 7) 7) + (if (= (index_or (pair 40 41) 1 7) 41) + (if (is_none (first_option (empty))) + (if (option_is_some_value (first_option (pair 40 41)) 40) + (if (option_is_some_value (last_option (triple 10 20 30)) 30) + (if (is_none (index_option (pair 40 41) -1)) + (if (is_none (index_option (pair 40 41) 9)) + (option_is_some_value (index_option (pair 40 41) 1) 41) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_option_query_helpers_ok () -> bool + (if (is_none (first_option (empty))) + (if (option_is_some_value (first_option (pair 40 41)) 40) + (if (is_none (last_option (empty))) + (if (option_is_some_value (last_option (triple 10 20 30)) 30) + (if (is_none (index_option (pair 40 41) -1)) + (if (is_none (index_option (pair 40 41) 9)) + (option_is_some_value (index_option (pair 40 41) 1) 41) + false) + false) + false) + false) + false) + false)) + +(fn imported_starts_with_helper_ok () -> bool + (let values (vec i32) (append2 (triple 10 20 30) 40 50)) + (let prefix (vec i32) (pair 10 20)) + (let longer_prefix (vec i32) (append2 values 60 70)) + (let mismatched_prefix (vec i32) (pair 20 30)) + (if (starts_with values (empty)) + (if (starts_with values prefix) + (if (starts_with values values) + (if (starts_with values longer_prefix) + false + (if (starts_with values mismatched_prefix) + false + (if (= values (append2 (triple 10 20 30) 40 50)) + (= prefix (pair 10 20)) + false))) + false) + false) + false)) + +(fn imported_ends_with_helper_ok () -> bool + (let values (vec i32) (append2 (triple 10 20 30) 40 50)) + (let suffix (vec i32) (pair 40 50)) + (let longer_suffix (vec i32) (append2 values 60 70)) + (let mismatched_suffix (vec i32) (pair 40 51)) + (if (ends_with values (empty)) + (if (ends_with values suffix) + (if (ends_with values values) + (if (ends_with values longer_suffix) + false + (if (ends_with values mismatched_suffix) + false + (if (= values (append2 (triple 10 20 30) 40 50)) + (= suffix (pair 40 50)) + false))) + false) + false) + false)) + +(fn imported_without_suffix_helper_ok () -> bool + (let values (vec i32) (append2 (triple 10 20 30) 40 50)) + (let suffix (vec i32) (pair 40 50)) + (let longer_suffix (vec i32) (append2 values 60 70)) + (let mismatched_suffix (vec i32) (pair 40 51)) + (if (= (without_suffix values (empty)) values) + (if (= (without_suffix values values) (empty)) + (if (= (without_suffix values suffix) (triple 10 20 30)) + (if (= (without_suffix values longer_suffix) values) + (if (= (without_suffix values mismatched_suffix) values) + (if (= values (append2 (triple 10 20 30) 40 50)) + (= suffix (pair 40 50)) + false) + false) + false) + false) + false) + false)) + +(fn imported_without_prefix_helper_ok () -> bool + (let values (vec i32) (append2 (triple 10 20 30) 40 50)) + (let prefix (vec i32) (pair 10 20)) + (let longer_prefix (vec i32) (append2 values 60 70)) + (let mismatched_prefix (vec i32) (pair 10 21)) + (if (= (without_prefix values (empty)) values) + (if (= (without_prefix values values) (empty)) + (if (= (without_prefix values prefix) (triple 30 40 50)) + (if (= (without_prefix values longer_prefix) values) + (if (= (without_prefix values mismatched_prefix) values) + (if (= values (append2 (triple 10 20 30) 40 50)) + (= prefix (pair 10 20)) + false) + false) + false) + false) + false) + false)) + +(fn imported_transform_helpers_ok () -> bool + (if (= (concat (empty) (pair 7 8)) (pair 7 8)) + (if (= (concat (pair 1 2) (triple 3 4 5)) (append3 (pair 1 2) 3 4 5)) + (if (= (take (triple 10 20 30) -4) (empty)) + (if (= (take (triple 10 20 30) 2) (pair 10 20)) + (if (= (take (pair 10 20) 9) (pair 10 20)) + (if (= (drop (triple 10 20 30) -4) (triple 10 20 30)) + (if (= (drop (triple 10 20 30) 2) (singleton 30)) + (if (= (drop (pair 10 20) 9) (empty)) + (if (= (reverse (empty)) (empty)) + (= (reverse (append3 (pair 10 20) 30 40 50)) (append3 (pair 50 40) 30 20 10)) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_subvec_helper_ok () -> bool + (let original (vec i32) (append2 (triple 10 20 30) 40 50)) + (if (= (subvec original -1 2) (empty)) + (if (= (subvec original 3 3) (empty)) + (if (= (subvec original 9 10) (empty)) + (if (= (subvec original 2 99) (triple 30 40 50)) + (if (= (subvec original 1 4) (triple 20 30 40)) + (= original (append2 (triple 10 20 30) 40 50)) + false) + false) + false) + false) + false)) + +(fn imported_insert_helper_ok () -> bool + (let values (vec i32) (triple 10 20 30)) + (if (= (insert_at values 1 99) (append2 (pair 10 99) 20 30)) + (if (= (insert_at values 3 99) (append values 99)) + (if (= values (triple 10 20 30)) + (if (= (insert_at values -1 99) values) + (= (insert_at values 4 99) values) + false) + false) + false) + false)) + +(fn imported_insert_range_helper_ok () -> bool + (let values (vec i32) (append2 (triple 10 20 30) 40 50)) + (let inserted (vec i32) (pair 77 88)) + (if (= (insert_range values -1 inserted) values) + (if (= (insert_range values 2 inserted) (append3 (append2 (pair 10 20) 77 88) 30 40 50)) + (if (= (insert_range values 5 inserted) (append2 (append2 (triple 10 20 30) 40 50) 77 88)) + (if (= values (append2 (triple 10 20 30) 40 50)) + (if (= inserted (pair 77 88)) + (= (insert_range values 6 inserted) values) + false) + false) + false) + false) + false)) + +(fn imported_replace_helper_ok () -> bool + (if (= (replace_at (triple 10 20 30) 1 99) (triple 10 99 30)) + (if (= (replace_at (triple 10 20 30) -1 99) (triple 10 20 30)) + (= (replace_at (pair 10 20) 2 99) (pair 10 20)) + false) + false)) + +(fn imported_replace_range_helper_ok () -> bool + (let original (vec i32) (append2 (triple 10 20 30) 40 50)) + (let replacement (vec i32) (pair 77 88)) + (if (= (replace_range original -1 2 replacement) original) + (if (= (replace_range original 3 3 replacement) original) + (if (= (replace_range original 4 2 replacement) original) + (if (= (replace_range original 5 9 replacement) original) + (if (= (replace_range original 2 99 replacement) (append2 (pair 10 20) 77 88)) + (if (= (replace_range original 1 4 replacement) (append2 (pair 10 77) 88 50)) + (if (= original (append2 (triple 10 20 30) 40 50)) + (= replacement (pair 77 88)) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_remove_helper_ok () -> bool + (let original (vec i32) (triple 10 20 30)) + (let removed_middle (vec i32) (remove_at original 1)) + (if (= removed_middle (pair 10 30)) + (if (= (len removed_middle) 2) + (if (= (remove_at original 0) (pair 20 30)) + (if (= (remove_at original 2) (pair 10 20)) + (if (= original (triple 10 20 30)) + (if (= (remove_at original -1) original) + (= (remove_at original 3) original) + false) + false) + false) + false) + false) + false)) + +(fn imported_remove_range_helper_ok () -> bool + (let original (vec i32) (append2 (triple 10 20 30) 40 50)) + (if (= (remove_range original -1 2) original) + (if (= (remove_range original 3 3) original) + (if (= (remove_range original 4 2) original) + (if (= (remove_range original 5 9) original) + (if (= (remove_range original 2 99) (pair 10 20)) + (if (= (remove_range original 1 4) (pair 10 50)) + (= original (append2 (triple 10 20 30) 40 50)) + false) + false) + false) + false) + false) + false)) + +(fn imported_count_of_helper_ok () -> bool + (let values (vec i32) (append3 (pair 10 20) 10 30 10)) + (if (= (count_of (empty) 10) 0) + (if (= (count_of values 10) 3) + (if (= (count_of values 20) 1) + (if (= (count_of values 99) 0) + (= values (append3 (pair 10 20) 10 30 10)) + false) + false) + false) + false)) + +(fn imported_real_program_helpers_ok () -> bool + (if (contains (triple 10 20 30) 20) + (if (contains (triple 10 20 30) 99) + false + (if (option_is_some_value (index_of_option (append3 (pair 10 20) 30 20 10) 20) 1) + (if (option_is_some_value (last_index_of_option (append3 (pair 10 20) 30 20 10) 20) 3) + (if (is_none (index_of_option (append3 (pair 10 20) 30 20 10) 99)) + (if (is_none (last_index_of_option (append3 (pair 10 20) 30 20 10) 99)) + (= (sum (append3 (empty) 10 20 12)) 42) + false) + false) + false) + false)) + false)) + +(fn imported_vec_i32_helpers_ok () -> bool + (if (= (imported_empty_length) 0) + (if (= (imported_pair_index) 41) + (if (imported_builder_helpers_ok) + (if (imported_constructor_helpers_ok) + (if (imported_range_helpers_ok) + (if (imported_query_helpers_ok) + (if (imported_option_query_helpers_ok) + (if (imported_starts_with_helper_ok) + (if (imported_ends_with_helper_ok) + (if (imported_without_suffix_helper_ok) + (if (imported_without_prefix_helper_ok) + (if (imported_transform_helpers_ok) + (if (imported_subvec_helper_ok) + (if (imported_insert_helper_ok) + (if (imported_insert_range_helper_ok) + (if (imported_replace_helper_ok) + (if (imported_replace_range_helper_ok) + (if (imported_remove_helper_ok) + (if (imported_remove_range_helper_ok) + (if (imported_count_of_helper_ok) + (imported_real_program_helpers_ok) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn main () -> i32 + (if (imported_vec_i32_helpers_ok) + 42 + 1)) + +(test "explicit local vec_i32 empty len facade" + (= (imported_empty_length) 0)) + +(test "explicit local vec_i32 direct at facade" + (= (imported_pair_index) 41)) + +(test "explicit local vec_i32 builder helpers" + (imported_builder_helpers_ok)) + +(test "explicit local vec_i32 constructor helpers" + (imported_constructor_helpers_ok)) + +(test "explicit local vec_i32 range helper" + (imported_range_helpers_ok)) + +(test "explicit local vec_i32 query helpers" + (imported_query_helpers_ok)) + +(test "explicit local vec_i32 option query helpers" + (imported_option_query_helpers_ok)) + +(test "explicit local vec_i32 starts_with helper" + (imported_starts_with_helper_ok)) + +(test "explicit local vec_i32 ends_with helper" + (imported_ends_with_helper_ok)) + +(test "explicit local vec_i32 without_suffix helper" + (imported_without_suffix_helper_ok)) + +(test "explicit local vec_i32 without_prefix helper" + (imported_without_prefix_helper_ok)) + +(test "explicit local vec_i32 transform helpers" + (imported_transform_helpers_ok)) + +(test "explicit local vec_i32 subvec helper" + (imported_subvec_helper_ok)) + +(test "explicit local vec_i32 insert helper" + (imported_insert_helper_ok)) + +(test "explicit local vec_i32 insert range helper" + (imported_insert_range_helper_ok)) + +(test "explicit local vec_i32 replace helper" + (imported_replace_helper_ok)) + +(test "explicit local vec_i32 replace range helper" + (imported_replace_range_helper_ok)) + +(test "explicit local vec_i32 remove helper" + (imported_remove_helper_ok)) + +(test "explicit local vec_i32 remove range helper" + (imported_remove_range_helper_ok)) + +(test "explicit local vec_i32 count_of helper" + (imported_count_of_helper_ok)) + +(test "explicit local vec_i32 real program helpers" + (imported_real_program_helpers_ok)) + +(test "explicit local vec_i32 helpers all" + (= (main) 42)) diff --git a/examples/projects/std-layout-local-vec_i32/src/option.slo b/examples/projects/std-layout-local-vec_i32/src/option.slo new file mode 100644 index 0000000..20baf8f --- /dev/null +++ b/examples/projects/std-layout-local-vec_i32/src/option.slo @@ -0,0 +1,7 @@ +(module option (export some_i32 none_i32)) + +(fn some_i32 ((value i32)) -> (option i32) + (some i32 value)) + +(fn none_i32 () -> (option i32) + (none i32)) diff --git a/examples/projects/std-layout-local-vec_i32/src/vec_i32.slo b/examples/projects/std-layout-local-vec_i32/src/vec_i32.slo new file mode 100644 index 0000000..43a052b --- /dev/null +++ b/examples/projects/std-layout-local-vec_i32/src/vec_i32.slo @@ -0,0 +1,270 @@ +(module vec_i32 (export empty append len at singleton append2 append3 pair triple repeat range range_from_zero is_empty index_or first_or last_or index_option first_option last_option index_of_option last_index_of_option count_of contains sum concat take starts_with without_prefix ends_with without_suffix drop reverse subvec insert_at insert_range replace_at replace_range remove_at remove_range)) + +(import option (some_i32 none_i32)) + +(fn empty () -> (vec i32) + (std.vec.i32.empty)) + +(fn append ((values (vec i32)) (value i32)) -> (vec i32) + (std.vec.i32.append values value)) + +(fn len ((values (vec i32))) -> i32 + (std.vec.i32.len values)) + +(fn at ((values (vec i32)) (position i32)) -> i32 + (std.vec.i32.index values position)) + +(fn singleton ((value i32)) -> (vec i32) + (append (empty) value)) + +(fn append2 ((values (vec i32)) (first i32) (second i32)) -> (vec i32) + (append (append values first) second)) + +(fn append3 ((values (vec i32)) (first i32) (second i32) (third i32)) -> (vec i32) + (append (append2 values first second) third)) + +(fn pair ((first i32) (second i32)) -> (vec i32) + (append2 (empty) first second)) + +(fn triple ((first i32) (second i32) (third i32)) -> (vec i32) + (append3 (empty) first second third)) + +(fn repeat_loop ((value i32) (remaining i32) (result (vec i32))) -> (vec i32) + (if (< remaining 1) + result + (repeat_loop value (- remaining 1) (append result value)))) + +(fn repeat ((value i32) (count i32)) -> (vec i32) + (if (< count 1) + (empty) + (repeat_loop value count (empty)))) + +(fn range_from_zero_loop ((current i32) (count i32) (result (vec i32))) -> (vec i32) + (if (< current count) + (range_from_zero_loop (+ current 1) count (append result current)) + result)) + +(fn range_loop ((current i32) (end_exclusive i32) (result (vec i32))) -> (vec i32) + (if (< current end_exclusive) + (range_loop (+ current 1) end_exclusive (append result current)) + result)) + +(fn range ((start i32) (end_exclusive i32)) -> (vec i32) + (if (< start end_exclusive) + (range_loop start end_exclusive (empty)) + (empty))) + +(fn range_from_zero ((count i32)) -> (vec i32) + (if (< count 1) + (empty) + (range_from_zero_loop 0 count (empty)))) + +(fn is_empty ((values (vec i32))) -> bool + (= (len values) 0)) + +(fn index_or ((values (vec i32)) (position i32) (fallback i32)) -> i32 + (if (< position 0) + fallback + (if (< position (len values)) + (at values position) + fallback))) + +(fn first_or ((values (vec i32)) (fallback i32)) -> i32 + (index_or values 0 fallback)) + +(fn last_or ((values (vec i32)) (fallback i32)) -> i32 + (if (is_empty values) + fallback + (at values (- (len values) 1)))) + +(fn index_option ((values (vec i32)) (position i32)) -> (option i32) + (if (< position 0) + (none_i32) + (if (< position (len values)) + (some_i32 (at values position)) + (none_i32)))) + +(fn first_option ((values (vec i32))) -> (option i32) + (index_option values 0)) + +(fn last_option ((values (vec i32))) -> (option i32) + (if (is_empty values) + (none_i32) + (some_i32 (at values (- (len values) 1))))) + +(fn index_of_option ((values (vec i32)) (target i32)) -> (option i32) + (var position i32 0) + (var found_position i32 -1) + (while (and (< position (len values)) (< found_position 0)) + (set found_position (if (= (at values position) target) + position + found_position)) + (set position (+ position 1))) + (if (< found_position 0) + (none_i32) + (some_i32 found_position))) + +(fn last_index_of_option ((values (vec i32)) (target i32)) -> (option i32) + (var position i32 0) + (var found_position i32 -1) + (while (< position (len values)) + (set found_position (if (= (at values position) target) + position + found_position)) + (set position (+ position 1))) + (if (< found_position 0) + (none_i32) + (some_i32 found_position))) + +(fn count_of ((values (vec i32)) (target i32)) -> i32 + (var position i32 0) + (var hits i32 0) + (while (< position (len values)) + (set hits (+ hits (if (= (at values position) target) + 1 + 0))) + (set position (+ position 1))) + hits) + +(fn contains ((values (vec i32)) (target i32)) -> bool + (var position i32 0) + (var hits i32 0) + (while (< position (len values)) + (set hits (+ hits (if (= (at values position) target) + 1 + 0))) + (set position (+ position 1))) + (> hits 0)) + +(fn sum ((values (vec i32))) -> i32 + (var position i32 0) + (var total i32 0) + (while (< position (len values)) + (set total (+ total (at values position))) + (set position (+ position 1))) + total) + +(fn concat_loop ((result (vec i32)) (right (vec i32)) (position i32) (right_len i32)) -> (vec i32) + (if (< position right_len) + (concat_loop (append result (at right position)) right (+ position 1) right_len) + result)) + +(fn concat ((left (vec i32)) (right (vec i32))) -> (vec i32) + (let right_len i32 (len right)) + (concat_loop left right 0 right_len)) + +(fn take_loop ((values (vec i32)) (position i32) (limit i32) (result (vec i32))) -> (vec i32) + (if (< position limit) + (take_loop values (+ position 1) limit (append result (at values position))) + result)) + +(fn take ((values (vec i32)) (count i32)) -> (vec i32) + (let values_len i32 (len values)) + (if (< count 0) + (empty) + (if (< count values_len) + (take_loop values 0 count (empty)) + values))) + +(fn starts_with ((values (vec i32)) (prefix (vec i32))) -> bool + (let prefix_len i32 (len prefix)) + (if (< (len values) prefix_len) + false + (= (take values prefix_len) prefix))) + +(fn without_prefix ((values (vec i32)) (prefix (vec i32))) -> (vec i32) + (if (starts_with values prefix) + (drop values (len prefix)) + values)) + +(fn ends_with ((values (vec i32)) (suffix (vec i32))) -> bool + (let values_len i32 (len values)) + (let suffix_len i32 (len suffix)) + (if (< values_len suffix_len) + false + (= (drop values (- values_len suffix_len)) suffix))) + +(fn without_suffix ((values (vec i32)) (suffix (vec i32))) -> (vec i32) + (if (ends_with values suffix) + (take values (- (len values) (len suffix))) + values)) + +(fn drop_loop ((values (vec i32)) (position i32) (values_len i32) (result (vec i32))) -> (vec i32) + (if (< position values_len) + (drop_loop values (+ position 1) values_len (append result (at values position))) + result)) + +(fn drop ((values (vec i32)) (count i32)) -> (vec i32) + (let values_len i32 (len values)) + (if (< count 0) + values + (if (< count values_len) + (drop_loop values count values_len (empty)) + (empty)))) + +(fn reverse_loop ((values (vec i32)) (position i32) (result (vec i32))) -> (vec i32) + (if (< position 0) + result + (reverse_loop values (- position 1) (append result (at values position))))) + +(fn reverse ((values (vec i32))) -> (vec i32) + (reverse_loop values (- (len values) 1) (empty))) + +(fn subvec ((values (vec i32)) (start i32) (end_exclusive i32)) -> (vec i32) + (if (< start 0) + (empty) + (if (< start end_exclusive) + (take (drop values start) (- end_exclusive start)) + (empty)))) + +(fn insert_at ((values (vec i32)) (position i32) (value i32)) -> (vec i32) + (if (< position 0) + values + (if (< position (len values)) + (concat (append (take values position) value) (drop values position)) + (if (= position (len values)) + (append values value) + values)))) + +(fn insert_range ((values (vec i32)) (position i32) (inserted (vec i32))) -> (vec i32) + (if (< position 0) + values + (if (< position (len values)) + (concat (concat (take values position) inserted) (drop values position)) + (if (= position (len values)) + (concat values inserted) + values)))) + +(fn replace_at ((values (vec i32)) (position i32) (replacement i32)) -> (vec i32) + (if (< position 0) + values + (if (< position (len values)) + (concat (append (take values position) replacement) (drop values (+ position 1))) + values))) + +(fn replace_range ((values (vec i32)) (start i32) (end_exclusive i32) (replacement (vec i32))) -> (vec i32) + (let values_len i32 (len values)) + (if (< start 0) + values + (if (< start values_len) + (if (< start end_exclusive) + (concat (concat (take values start) replacement) (drop values end_exclusive)) + values) + values))) + +(fn remove_at ((values (vec i32)) (position i32)) -> (vec i32) + (if (< position 0) + values + (if (< position (len values)) + (concat (take values position) (drop values (+ position 1))) + values))) + +(fn remove_range ((values (vec i32)) (start i32) (end_exclusive i32)) -> (vec i32) + (let values_len i32 (len values)) + (if (< start 0) + values + (if (< start values_len) + (if (< start end_exclusive) + (concat (take values start) (drop values end_exclusive)) + values) + values))) diff --git a/examples/projects/std-layout-local-vec_i64/slovo.toml b/examples/projects/std-layout-local-vec_i64/slovo.toml new file mode 100644 index 0000000..e596d7f --- /dev/null +++ b/examples/projects/std-layout-local-vec_i64/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-layout-local-vec-i64" +source_root = "src" +entry = "main" diff --git a/examples/projects/std-layout-local-vec_i64/src/main.slo b/examples/projects/std-layout-local-vec_i64/src/main.slo new file mode 100644 index 0000000..f2b7667 --- /dev/null +++ b/examples/projects/std-layout-local-vec_i64/src/main.slo @@ -0,0 +1,283 @@ +(module main) + +(import vec_i64 (empty append len at singleton append2 append3 pair triple is_empty index_or first_or last_or index_option first_option last_option index_of_option last_index_of_option contains sum concat take drop reverse subvec insert_at insert_range replace_at replace_range remove_at remove_range)) + +(fn imported_empty_length () -> i32 + (len (empty))) + +(fn imported_pair_index () -> i64 + (at (pair 40i64 41i64) 1)) + +(fn original_len_after_append () -> i32 + (let values (vec i64) (empty)) + (let more (vec i64) (append values 9i64)) + (len values)) + +(fn option_i64_is_some_value ((value (option i64)) (expected i64)) -> bool + (if (is_some value) + (= (unwrap_some value) expected) + false)) + +(fn option_i32_is_some_value ((value (option i32)) (expected i32)) -> bool + (if (is_some value) + (= (unwrap_some value) expected) + false)) + +(fn imported_builder_helpers_ok () -> bool + (if (= (len (singleton 42i64)) 1) + (if (= (len (append2 (empty) 10i64 20i64)) 2) + (if (= (len (append3 (singleton 10i64) 20i64 30i64 40i64)) 4) + (if (= (pair 5i64 6i64) (append2 (empty) 5i64 6i64)) + (= (triple 7i64 8i64 9i64) (append3 (empty) 7i64 8i64 9i64)) + false) + false) + false) + false)) + +(fn imported_query_helpers_ok () -> bool + (if (is_empty (empty)) + (if (= (first_or (empty) 9i64) 9i64) + (if (= (first_or (pair 40i64 41i64) 9i64) 40i64) + (if (= (last_or (triple 10i64 20i64 30i64) 9i64) 30i64) + (if (= (index_or (pair 40i64 41i64) -1 7i64) 7i64) + (if (= (index_or (pair 40i64 41i64) 9 7i64) 7i64) + (= (index_or (pair 40i64 41i64) 1 7i64) 41i64) + false) + false) + false) + false) + false) + false)) + +(fn imported_option_query_helpers_ok () -> bool + (if (is_none (first_option (empty))) + (if (option_i64_is_some_value (first_option (pair 40i64 41i64)) 40i64) + (if (is_none (last_option (empty))) + (if (option_i64_is_some_value (last_option (triple 10i64 20i64 30i64)) 30i64) + (if (is_none (index_option (pair 40i64 41i64) -1)) + (if (is_none (index_option (pair 40i64 41i64) 9)) + (if (option_i64_is_some_value (index_option (pair 40i64 41i64) 1) 41i64) + (if (is_none (index_of_option (append3 (pair 10i64 20i64) 30i64 20i64 10i64) 99i64)) + (if (option_i32_is_some_value (index_of_option (append3 (pair 10i64 20i64) 30i64 20i64 10i64) 20i64) 1) + (if (is_none (last_index_of_option (append3 (pair 10i64 20i64) 30i64 20i64 10i64) 99i64)) + (option_i32_is_some_value (last_index_of_option (append3 (pair 10i64 20i64) 30i64 20i64 10i64) 20i64) 3) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_transform_helpers_ok () -> bool + (if (= (concat (empty) (pair 7i64 8i64)) (pair 7i64 8i64)) + (if (= (concat (pair 1i64 2i64) (triple 3i64 4i64 5i64)) (append3 (pair 1i64 2i64) 3i64 4i64 5i64)) + (if (= (take (triple 10i64 20i64 30i64) -4) (empty)) + (if (= (take (triple 10i64 20i64 30i64) 2) (pair 10i64 20i64)) + (if (= (take (pair 10i64 20i64) 9) (pair 10i64 20i64)) + (if (= (drop (triple 10i64 20i64 30i64) -4) (triple 10i64 20i64 30i64)) + (if (= (drop (triple 10i64 20i64 30i64) 2) (singleton 30i64)) + (if (= (drop (pair 10i64 20i64) 9) (empty)) + (if (= (reverse (empty)) (empty)) + (= (reverse (append3 (pair 10i64 20i64) 30i64 40i64 50i64)) (append3 (pair 50i64 40i64) 30i64 20i64 10i64)) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_subvec_helper_ok () -> bool + (let original (vec i64) (append2 (triple 10i64 20i64 30i64) 40i64 50i64)) + (if (= (subvec original -1 2) (empty)) + (if (= (subvec original 3 3) (empty)) + (if (= (subvec original 9 10) (empty)) + (if (= (subvec original 2 99) (triple 30i64 40i64 50i64)) + (if (= (subvec original 1 4) (triple 20i64 30i64 40i64)) + (= original (append2 (triple 10i64 20i64 30i64) 40i64 50i64)) + false) + false) + false) + false) + false)) + +(fn imported_insert_helper_ok () -> bool + (let values (vec i64) (triple 10i64 20i64 30i64)) + (if (= (insert_at values 1 99i64) (append2 (pair 10i64 99i64) 20i64 30i64)) + (if (= (insert_at values 3 99i64) (append values 99i64)) + (if (= values (triple 10i64 20i64 30i64)) + (if (= (insert_at values -1 99i64) values) + (= (insert_at values 4 99i64) values) + false) + false) + false) + false)) + +(fn imported_insert_range_helper_ok () -> bool + (let values (vec i64) (append2 (triple 10i64 20i64 30i64) 40i64 50i64)) + (let inserted (vec i64) (pair 77i64 88i64)) + (if (= (insert_range values -1 inserted) values) + (if (= (insert_range values 2 inserted) (append3 (append2 (pair 10i64 20i64) 77i64 88i64) 30i64 40i64 50i64)) + (if (= (insert_range values 5 inserted) (append2 (append2 (triple 10i64 20i64 30i64) 40i64 50i64) 77i64 88i64)) + (if (= values (append2 (triple 10i64 20i64 30i64) 40i64 50i64)) + (if (= inserted (pair 77i64 88i64)) + (= (insert_range values 6 inserted) values) + false) + false) + false) + false) + false)) + +(fn imported_replace_helper_ok () -> bool + (if (= (replace_at (triple 10i64 20i64 30i64) 1 99i64) (triple 10i64 99i64 30i64)) + (if (= (replace_at (triple 10i64 20i64 30i64) -1 99i64) (triple 10i64 20i64 30i64)) + (= (replace_at (pair 10i64 20i64) 2 99i64) (pair 10i64 20i64)) + false) + false)) + +(fn imported_replace_range_helper_ok () -> bool + (let original (vec i64) (append2 (triple 10i64 20i64 30i64) 40i64 50i64)) + (let replacement (vec i64) (pair 77i64 88i64)) + (if (= (replace_range original -1 2 replacement) original) + (if (= (replace_range original 3 3 replacement) original) + (if (= (replace_range original 4 2 replacement) original) + (if (= (replace_range original 5 9 replacement) original) + (if (= (replace_range original 2 99 replacement) (append2 (pair 10i64 20i64) 77i64 88i64)) + (if (= (replace_range original 1 4 replacement) (append2 (pair 10i64 77i64) 88i64 50i64)) + (if (= original (append2 (triple 10i64 20i64 30i64) 40i64 50i64)) + (= replacement (pair 77i64 88i64)) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_remove_helper_ok () -> bool + (let original (vec i64) (triple 10i64 20i64 30i64)) + (let removed_middle (vec i64) (remove_at original 1)) + (if (= removed_middle (pair 10i64 30i64)) + (if (= (len removed_middle) 2) + (if (= (remove_at original 0) (pair 20i64 30i64)) + (if (= (remove_at original 2) (pair 10i64 20i64)) + (if (= original (triple 10i64 20i64 30i64)) + (if (= (remove_at original -1) original) + (= (remove_at original 3) original) + false) + false) + false) + false) + false) + false)) + +(fn imported_remove_range_helper_ok () -> bool + (let original (vec i64) (append2 (triple 10i64 20i64 30i64) 40i64 50i64)) + (if (= (remove_range original -1 2) original) + (if (= (remove_range original 3 3) original) + (if (= (remove_range original 4 2) original) + (if (= (remove_range original 5 9) original) + (if (= (remove_range original 2 99) (pair 10i64 20i64)) + (if (= (remove_range original 1 4) (pair 10i64 50i64)) + (= original (append2 (triple 10i64 20i64 30i64) 40i64 50i64)) + false) + false) + false) + false) + false) + false)) + +(fn imported_real_program_helpers_ok () -> bool + (let values (vec i64) (append2 (triple 10i64 20i64 30i64) 40i64 50i64)) + (if (= (original_len_after_append) 0) + (if (contains values 20i64) + (if (contains values 99i64) + false + (= (sum values) 150i64)) + false) + false)) + +(fn helpers_all_ok () -> bool + (if (= (imported_empty_length) 0) + (if (= (imported_pair_index) 41i64) + (if (imported_builder_helpers_ok) + (if (imported_query_helpers_ok) + (if (imported_option_query_helpers_ok) + (if (imported_transform_helpers_ok) + (if (imported_subvec_helper_ok) + (if (imported_insert_helper_ok) + (if (imported_insert_range_helper_ok) + (if (imported_replace_helper_ok) + (if (imported_replace_range_helper_ok) + (if (imported_remove_helper_ok) + (if (imported_remove_range_helper_ok) + (imported_real_program_helpers_ok) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(test "explicit local vec_i64 empty len facade" + (= (imported_empty_length) 0)) + +(test "explicit local vec_i64 direct at facade" + (= (imported_pair_index) 41i64)) + +(test "explicit local vec_i64 builder helpers" + (imported_builder_helpers_ok)) + +(test "explicit local vec_i64 query helpers" + (imported_query_helpers_ok)) + +(test "explicit local vec_i64 option query helpers" + (imported_option_query_helpers_ok)) + +(test "explicit local vec_i64 transform helpers" + (imported_transform_helpers_ok)) + +(test "explicit local vec_i64 subvec helper" + (imported_subvec_helper_ok)) + +(test "explicit local vec_i64 insert helper" + (imported_insert_helper_ok)) + +(test "explicit local vec_i64 insert range helper" + (imported_insert_range_helper_ok)) + +(test "explicit local vec_i64 replace helper" + (imported_replace_helper_ok)) + +(test "explicit local vec_i64 replace range helper" + (imported_replace_range_helper_ok)) + +(test "explicit local vec_i64 remove helper" + (imported_remove_helper_ok)) + +(test "explicit local vec_i64 remove range helper" + (imported_remove_range_helper_ok)) + +(test "explicit local vec_i64 real program helpers" + (imported_real_program_helpers_ok)) + +(test "explicit local vec_i64 helpers all" + (helpers_all_ok)) + +(fn main () -> i32 + (if (helpers_all_ok) + 0 + 1)) diff --git a/examples/projects/std-layout-local-vec_i64/src/vec_i64.slo b/examples/projects/std-layout-local-vec_i64/src/vec_i64.slo new file mode 100644 index 0000000..7327e52 --- /dev/null +++ b/examples/projects/std-layout-local-vec_i64/src/vec_i64.slo @@ -0,0 +1,201 @@ +(module vec_i64 (export empty append len at singleton append2 append3 pair triple is_empty index_or first_or last_or index_option first_option last_option index_of_option last_index_of_option contains sum concat take drop reverse subvec insert_at insert_range replace_at replace_range remove_at remove_range)) + +(fn empty () -> (vec i64) + (std.vec.i64.empty)) + +(fn append ((values (vec i64)) (value i64)) -> (vec i64) + (std.vec.i64.append values value)) + +(fn len ((values (vec i64))) -> i32 + (std.vec.i64.len values)) + +(fn at ((values (vec i64)) (position i32)) -> i64 + (std.vec.i64.index values position)) + +(fn singleton ((value i64)) -> (vec i64) + (append (empty) value)) + +(fn append2 ((values (vec i64)) (first i64) (second i64)) -> (vec i64) + (append (append values first) second)) + +(fn append3 ((values (vec i64)) (first i64) (second i64) (third i64)) -> (vec i64) + (append (append2 values first second) third)) + +(fn pair ((first i64) (second i64)) -> (vec i64) + (append2 (empty) first second)) + +(fn triple ((first i64) (second i64) (third i64)) -> (vec i64) + (append3 (empty) first second third)) + +(fn is_empty ((values (vec i64))) -> bool + (= (len values) 0)) + +(fn index_or ((values (vec i64)) (position i32) (fallback i64)) -> i64 + (if (< position 0) + fallback + (if (< position (len values)) + (at values position) + fallback))) + +(fn first_or ((values (vec i64)) (fallback i64)) -> i64 + (index_or values 0 fallback)) + +(fn last_or ((values (vec i64)) (fallback i64)) -> i64 + (if (is_empty values) + fallback + (at values (- (len values) 1)))) + +(fn index_option ((values (vec i64)) (position i32)) -> (option i64) + (if (< position 0) + (none i64) + (if (< position (len values)) + (some i64 (at values position)) + (none i64)))) + +(fn first_option ((values (vec i64))) -> (option i64) + (index_option values 0)) + +(fn last_option ((values (vec i64))) -> (option i64) + (if (is_empty values) + (none i64) + (some i64 (at values (- (len values) 1))))) + +(fn index_of_option_loop ((values (vec i64)) (target i64) (position i32) (values_len i32)) -> (option i32) + (if (< position values_len) + (if (= (at values position) target) + (some i32 position) + (index_of_option_loop values target (+ position 1) values_len)) + (none i32))) + +(fn index_of_option ((values (vec i64)) (target i64)) -> (option i32) + (index_of_option_loop values target 0 (len values))) + +(fn last_index_of_option_loop ((values (vec i64)) (target i64) (position i32) (values_len i32) (found (option i32))) -> (option i32) + (if (< position values_len) + (if (= (at values position) target) + (last_index_of_option_loop values target (+ position 1) values_len (some i32 position)) + (last_index_of_option_loop values target (+ position 1) values_len found)) + found)) + +(fn last_index_of_option ((values (vec i64)) (target i64)) -> (option i32) + (last_index_of_option_loop values target 0 (len values) (none i32))) + +(fn contains_loop ((values (vec i64)) (target i64) (position i32) (values_len i32)) -> bool + (if (< position values_len) + (if (= (at values position) target) + true + (contains_loop values target (+ position 1) values_len)) + false)) + +(fn contains ((values (vec i64)) (target i64)) -> bool + (contains_loop values target 0 (len values))) + +(fn sum_loop ((values (vec i64)) (position i32) (values_len i32) (total i64)) -> i64 + (if (< position values_len) + (sum_loop values (+ position 1) values_len (+ total (at values position))) + total)) + +(fn sum ((values (vec i64))) -> i64 + (sum_loop values 0 (len values) 0i64)) + +(fn concat_loop ((built (vec i64)) (right (vec i64)) (position i32) (right_len i32)) -> (vec i64) + (if (< position right_len) + (concat_loop (append built (at right position)) right (+ position 1) right_len) + built)) + +(fn concat ((left (vec i64)) (right (vec i64))) -> (vec i64) + (let right_len i32 (len right)) + (concat_loop left right 0 right_len)) + +(fn take_loop ((values (vec i64)) (position i32) (limit i32) (built (vec i64))) -> (vec i64) + (if (< position limit) + (take_loop values (+ position 1) limit (append built (at values position))) + built)) + +(fn take ((values (vec i64)) (count i32)) -> (vec i64) + (let values_len i32 (len values)) + (if (< count 0) + (empty) + (if (< count values_len) + (take_loop values 0 count (empty)) + values))) + +(fn drop_loop ((values (vec i64)) (position i32) (values_len i32) (built (vec i64))) -> (vec i64) + (if (< position values_len) + (drop_loop values (+ position 1) values_len (append built (at values position))) + built)) + +(fn drop ((values (vec i64)) (count i32)) -> (vec i64) + (let values_len i32 (len values)) + (if (< count 0) + values + (if (< count values_len) + (drop_loop values count values_len (empty)) + (empty)))) + +(fn reverse_loop ((values (vec i64)) (position i32) (built (vec i64))) -> (vec i64) + (if (< position 0) + built + (reverse_loop values (- position 1) (append built (at values position))))) + +(fn reverse ((values (vec i64))) -> (vec i64) + (reverse_loop values (- (len values) 1) (empty))) + +(fn subvec ((values (vec i64)) (start i32) (end_exclusive i32)) -> (vec i64) + (if (< start 0) + (empty) + (if (< start end_exclusive) + (take (drop values start) (- end_exclusive start)) + (empty)))) + +(fn insert_at ((values (vec i64)) (position i32) (value i64)) -> (vec i64) + (if (< position 0) + values + (if (< position (len values)) + (concat (append (take values position) value) (drop values position)) + (if (= position (len values)) + (append values value) + values)))) + +(fn insert_range ((values (vec i64)) (position i32) (inserted (vec i64))) -> (vec i64) + (if (< position 0) + values + (if (< position (len values)) + (concat (concat (take values position) inserted) (drop values position)) + (if (= position (len values)) + (concat values inserted) + values)))) + +(fn replace_at ((values (vec i64)) (position i32) (replacement i64)) -> (vec i64) + (if (< position 0) + values + (if (< position (len values)) + (concat (append (take values position) replacement) (drop values (+ position 1))) + values))) + +(fn replace_range ((values (vec i64)) (start i32) (end_exclusive i32) (replacement (vec i64))) -> (vec i64) + (let values_len i32 (len values)) + (if (< start 0) + values + (if (< start values_len) + (if (< start end_exclusive) + (concat (concat (take values start) replacement) (drop values end_exclusive)) + values) + values))) + +(fn remove_at ((values (vec i64)) (position i32)) -> (vec i64) + (if (< position 0) + values + (if (< position (len values)) + (concat (take values position) (drop values (+ position 1))) + values))) + +(fn remove_range ((values (vec i64)) (start i32) (end_exclusive i32)) -> (vec i64) + (let values_len i32 (len values)) + (if (< start 0) + values + (if (< start values_len) + (if (< start end_exclusive) + (concat (take values start) (drop values end_exclusive)) + values) + values))) diff --git a/examples/projects/std-layout-local-vec_string/slovo.toml b/examples/projects/std-layout-local-vec_string/slovo.toml new file mode 100644 index 0000000..8cb0af0 --- /dev/null +++ b/examples/projects/std-layout-local-vec_string/slovo.toml @@ -0,0 +1,4 @@ +[project] +name = "std-layout-local-vec-string" +source_root = "src" +entry = "main" diff --git a/examples/projects/std-layout-local-vec_string/src/main.slo b/examples/projects/std-layout-local-vec_string/src/main.slo new file mode 100644 index 0000000..497b18d --- /dev/null +++ b/examples/projects/std-layout-local-vec_string/src/main.slo @@ -0,0 +1,381 @@ +(module main) + +(import vec_string (empty append len at singleton append2 append3 pair triple is_empty index_or first_or last_or index_option first_option last_option index_of_option last_index_of_option contains count_of concat take starts_with without_prefix ends_with without_suffix drop reverse subvec insert_at insert_range replace_at replace_range remove_at remove_range)) + +(fn imported_empty_length () -> i32 + (len (empty))) + +(fn imported_pair_index () -> string + (at (pair "slovo" "tree") 1)) + +(fn original_len_after_append () -> i32 + (let values (vec string) (empty)) + (let more (vec string) (append values "branch")) + (len values)) + +(fn option_string_is_some_value ((value (option string)) (expected string)) -> bool + (if (is_some value) + (= (unwrap_some value) expected) + false)) + +(fn option_i32_is_some_value ((value (option i32)) (expected i32)) -> bool + (if (is_some value) + (= (unwrap_some value) expected) + false)) + +(fn imported_builder_helpers_ok () -> bool + (if (= (len (singleton "seed")) 1) + (if (= (len (append2 (empty) "alpha" "beta")) 2) + (if (= (len (append3 (singleton "alpha") "beta" "gamma" "delta")) 4) + (if (= (pair "left" "right") (append2 (empty) "left" "right")) + (= (triple "red" "green" "blue") (append3 (empty) "red" "green" "blue")) + false) + false) + false) + false)) + +(fn imported_query_helpers_ok () -> bool + (if (is_empty (empty)) + (if (= (first_or (empty) "fallback") "fallback") + (if (= (first_or (pair "alpha" "beta") "fallback") "alpha") + (if (= (last_or (triple "red" "green" "blue") "fallback") "blue") + (if (= (index_or (pair "alpha" "beta") -1 "fallback") "fallback") + (if (= (index_or (pair "alpha" "beta") 9 "fallback") "fallback") + (= (index_or (pair "alpha" "beta") 1 "fallback") "beta") + false) + false) + false) + false) + false) + false)) + +(fn imported_option_query_helpers_ok () -> bool + (if (is_none (first_option (empty))) + (if (option_string_is_some_value (first_option (pair "alpha" "beta")) "alpha") + (if (is_none (last_option (empty))) + (if (option_string_is_some_value (last_option (triple "red" "green" "blue")) "blue") + (if (is_none (index_option (pair "alpha" "beta") -1)) + (if (is_none (index_option (pair "alpha" "beta") 9)) + (if (option_string_is_some_value (index_option (pair "alpha" "beta") 1) "beta") + (if (is_none (index_of_option (append3 (pair "oak" "pine") "birch" "pine" "oak") "cedar")) + (if (option_i32_is_some_value (index_of_option (append3 (pair "oak" "pine") "birch" "pine" "oak") "pine") 1) + (if (is_none (last_index_of_option (append3 (pair "oak" "pine") "birch" "pine" "oak") "cedar")) + (option_i32_is_some_value (last_index_of_option (append3 (pair "oak" "pine") "birch" "pine" "oak") "pine") 3) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_starts_with_helper_ok () -> bool + (let values (vec string) (append2 (triple "oak" "pine" "birch") "cedar" "maple")) + (let prefix (vec string) (pair "oak" "pine")) + (let longer_prefix (vec string) (append2 values "spruce" "elm")) + (let mismatched_prefix (vec string) (pair "pine" "birch")) + (if (starts_with values (empty)) + (if (starts_with values prefix) + (if (starts_with values values) + (if (starts_with values longer_prefix) + false + (if (starts_with values mismatched_prefix) + false + (if (= values (append2 (triple "oak" "pine" "birch") "cedar" "maple")) + (= prefix (pair "oak" "pine")) + false))) + false) + false) + false)) + +(fn imported_ends_with_helper_ok () -> bool + (let values (vec string) (append2 (triple "oak" "pine" "birch") "cedar" "maple")) + (let suffix (vec string) (pair "cedar" "maple")) + (let longer_suffix (vec string) (append2 values "spruce" "elm")) + (let mismatched_suffix (vec string) (pair "cedar" "elm")) + (if (ends_with values (empty)) + (if (ends_with values suffix) + (if (ends_with values values) + (if (ends_with values longer_suffix) + false + (if (ends_with values mismatched_suffix) + false + (if (= values (append2 (triple "oak" "pine" "birch") "cedar" "maple")) + (= suffix (pair "cedar" "maple")) + false))) + false) + false) + false)) + +(fn imported_without_suffix_helper_ok () -> bool + (let values (vec string) (append2 (triple "oak" "pine" "birch") "cedar" "maple")) + (let suffix (vec string) (pair "cedar" "maple")) + (let longer_suffix (vec string) (append2 values "spruce" "elm")) + (let mismatched_suffix (vec string) (pair "cedar" "elm")) + (if (= (without_suffix values (empty)) values) + (if (= (without_suffix values values) (empty)) + (if (= (without_suffix values suffix) (triple "oak" "pine" "birch")) + (if (= (without_suffix values longer_suffix) values) + (if (= (without_suffix values mismatched_suffix) values) + (if (= values (append2 (triple "oak" "pine" "birch") "cedar" "maple")) + (= suffix (pair "cedar" "maple")) + false) + false) + false) + false) + false) + false)) + +(fn imported_without_prefix_helper_ok () -> bool + (let values (vec string) (append2 (triple "oak" "pine" "birch") "cedar" "maple")) + (let prefix (vec string) (pair "oak" "pine")) + (let longer_prefix (vec string) (append2 values "spruce" "elm")) + (let mismatched_prefix (vec string) (pair "oak" "birch")) + (if (= (without_prefix values (empty)) values) + (if (= (without_prefix values values) (empty)) + (if (= (without_prefix values prefix) (triple "birch" "cedar" "maple")) + (if (= (without_prefix values longer_prefix) values) + (if (= (without_prefix values mismatched_prefix) values) + (if (= values (append2 (triple "oak" "pine" "birch") "cedar" "maple")) + (= prefix (pair "oak" "pine")) + false) + false) + false) + false) + false) + false)) + +(fn imported_transform_helpers_ok () -> bool + (if (= (concat (empty) (pair "oak" "pine")) (pair "oak" "pine")) + (if (= (concat (pair "a" "b") (triple "c" "d" "e")) (append3 (pair "a" "b") "c" "d" "e")) + (if (= (take (triple "red" "green" "blue") -4) (empty)) + (if (= (take (triple "red" "green" "blue") 2) (pair "red" "green")) + (if (= (take (pair "red" "green") 9) (pair "red" "green")) + (if (= (drop (triple "red" "green" "blue") -4) (triple "red" "green" "blue")) + (if (= (drop (triple "red" "green" "blue") 2) (singleton "blue")) + (if (= (drop (pair "red" "green") 9) (empty)) + (if (= (reverse (empty)) (empty)) + (= (reverse (append3 (pair "oak" "pine") "birch" "cedar" "maple")) (append3 (pair "maple" "cedar") "birch" "pine" "oak")) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_subvec_helper_ok () -> bool + (let original (vec string) (append2 (triple "oak" "pine" "birch") "cedar" "maple")) + (if (= (subvec original -1 2) (empty)) + (if (= (subvec original 3 3) (empty)) + (if (= (subvec original 9 10) (empty)) + (if (= (subvec original 2 99) (triple "birch" "cedar" "maple")) + (if (= (subvec original 1 4) (triple "pine" "birch" "cedar")) + (= original (append2 (triple "oak" "pine" "birch") "cedar" "maple")) + false) + false) + false) + false) + false)) + +(fn imported_insert_helper_ok () -> bool + (let values (vec string) (triple "oak" "pine" "birch")) + (if (= (insert_at values 1 "cedar") (append2 (pair "oak" "cedar") "pine" "birch")) + (if (= (insert_at values 3 "cedar") (append values "cedar")) + (if (= values (triple "oak" "pine" "birch")) + (if (= (insert_at values -1 "cedar") values) + (= (insert_at values 4 "cedar") values) + false) + false) + false) + false)) + +(fn imported_insert_range_helper_ok () -> bool + (let values (vec string) (append2 (triple "oak" "pine" "birch") "cedar" "maple")) + (let inserted (vec string) (pair "elm" "fir")) + (if (= (insert_range values -1 inserted) values) + (if (= (insert_range values 2 inserted) (append3 (append2 (pair "oak" "pine") "elm" "fir") "birch" "cedar" "maple")) + (if (= (insert_range values 5 inserted) (append2 (append2 (triple "oak" "pine" "birch") "cedar" "maple") "elm" "fir")) + (if (= values (append2 (triple "oak" "pine" "birch") "cedar" "maple")) + (if (= inserted (pair "elm" "fir")) + (= (insert_range values 6 inserted) values) + false) + false) + false) + false) + false)) + +(fn imported_replace_helper_ok () -> bool + (if (= (replace_at (triple "oak" "pine" "birch") 1 "cedar") (triple "oak" "cedar" "birch")) + (if (= (replace_at (triple "oak" "pine" "birch") -1 "cedar") (triple "oak" "pine" "birch")) + (= (replace_at (pair "oak" "pine") 2 "cedar") (pair "oak" "pine")) + false) + false)) + +(fn imported_replace_range_helper_ok () -> bool + (let original (vec string) (append2 (triple "oak" "pine" "birch") "cedar" "maple")) + (let replacement (vec string) (pair "elm" "fir")) + (if (= (replace_range original -1 2 replacement) original) + (if (= (replace_range original 3 3 replacement) original) + (if (= (replace_range original 4 2 replacement) original) + (if (= (replace_range original 5 9 replacement) original) + (if (= (replace_range original 2 99 replacement) (append2 (pair "oak" "pine") "elm" "fir")) + (if (= (replace_range original 1 4 replacement) (append2 (pair "oak" "elm") "fir" "maple")) + (if (= original (append2 (triple "oak" "pine" "birch") "cedar" "maple")) + (= replacement (pair "elm" "fir")) + false) + false) + false) + false) + false) + false) + false)) + +(fn imported_remove_helper_ok () -> bool + (let original (vec string) (triple "oak" "pine" "birch")) + (let removed_middle (vec string) (remove_at original 1)) + (if (= removed_middle (pair "oak" "birch")) + (if (= (len removed_middle) 2) + (if (= (remove_at original 0) (pair "pine" "birch")) + (if (= (remove_at original 2) (pair "oak" "pine")) + (if (= original (triple "oak" "pine" "birch")) + (if (= (remove_at original -1) original) + (= (remove_at original 3) original) + false) + false) + false) + false) + false) + false)) + +(fn imported_remove_range_helper_ok () -> bool + (let original (vec string) (append2 (triple "oak" "pine" "birch") "cedar" "maple")) + (if (= (remove_range original -1 2) original) + (if (= (remove_range original 3 3) original) + (if (= (remove_range original 4 2) original) + (if (= (remove_range original 5 9) original) + (if (= (remove_range original 2 99) (pair "oak" "pine")) + (if (= (remove_range original 1 4) (pair "oak" "maple")) + (= original (append2 (triple "oak" "pine" "birch") "cedar" "maple")) + false) + false) + false) + false) + false) + false)) + +(fn imported_real_program_helpers_ok () -> bool + (let values (vec string) (append2 (triple "oak" "pine" "oak") "birch" "oak")) + (if (= (original_len_after_append) 0) + (if (contains values "oak") + (if (contains values "cedar") + false + (if (= (count_of values "oak") 3) + (= (count_of values "cedar") 0) + false)) + false) + false)) + +(fn helpers_all_ok () -> bool + (if (= (imported_empty_length) 0) + (if (= (imported_pair_index) "tree") + (if (imported_builder_helpers_ok) + (if (imported_query_helpers_ok) + (if (imported_option_query_helpers_ok) + (if (imported_starts_with_helper_ok) + (if (imported_ends_with_helper_ok) + (if (imported_without_suffix_helper_ok) + (if (imported_without_prefix_helper_ok) + (if (imported_transform_helpers_ok) + (if (imported_subvec_helper_ok) + (if (imported_insert_helper_ok) + (if (imported_insert_range_helper_ok) + (if (imported_replace_helper_ok) + (if (imported_replace_range_helper_ok) + (if (imported_remove_helper_ok) + (if (imported_remove_range_helper_ok) + (imported_real_program_helpers_ok) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false) + false)) + +(fn main () -> i32 + (if (helpers_all_ok) + 42 + 1)) + +(test "explicit local vec_string empty len facade" + (= (imported_empty_length) 0)) + +(test "explicit local vec_string direct at facade" + (= (imported_pair_index) "tree")) + +(test "explicit local vec_string builder helpers" + (imported_builder_helpers_ok)) + +(test "explicit local vec_string query helpers" + (imported_query_helpers_ok)) + +(test "explicit local vec_string option query helpers" + (imported_option_query_helpers_ok)) + +(test "explicit local vec_string starts_with helper" + (imported_starts_with_helper_ok)) + +(test "explicit local vec_string ends_with helper" + (imported_ends_with_helper_ok)) + +(test "explicit local vec_string without_suffix helper" + (imported_without_suffix_helper_ok)) + +(test "explicit local vec_string without_prefix helper" + (imported_without_prefix_helper_ok)) + +(test "explicit local vec_string transform helpers" + (imported_transform_helpers_ok)) + +(test "explicit local vec_string subvec helper" + (imported_subvec_helper_ok)) + +(test "explicit local vec_string insert helper" + (imported_insert_helper_ok)) + +(test "explicit local vec_string insert range helper" + (imported_insert_range_helper_ok)) + +(test "explicit local vec_string replace helper" + (imported_replace_helper_ok)) + +(test "explicit local vec_string replace range helper" + (imported_replace_range_helper_ok)) + +(test "explicit local vec_string remove helper" + (imported_remove_helper_ok)) + +(test "explicit local vec_string remove range helper" + (imported_remove_range_helper_ok)) + +(test "explicit local vec_string real-program helpers" + (imported_real_program_helpers_ok)) + +(test "explicit local vec_string helpers all" + (= (main) 42)) diff --git a/examples/projects/std-layout-local-vec_string/src/vec_string.slo b/examples/projects/std-layout-local-vec_string/src/vec_string.slo new file mode 100644 index 0000000..d72c4b5 --- /dev/null +++ b/examples/projects/std-layout-local-vec_string/src/vec_string.slo @@ -0,0 +1,228 @@ +(module vec_string (export empty append len at singleton append2 append3 pair triple is_empty index_or first_or last_or index_option first_option last_option index_of_option last_index_of_option contains count_of concat take starts_with without_prefix ends_with without_suffix drop reverse subvec insert_at insert_range replace_at replace_range remove_at remove_range)) + +(fn empty () -> (vec string) + (std.vec.string.empty)) + +(fn append ((values (vec string)) (value string)) -> (vec string) + (std.vec.string.append values value)) + +(fn len ((values (vec string))) -> i32 + (std.vec.string.len values)) + +(fn at ((values (vec string)) (position i32)) -> string + (std.vec.string.index values position)) + +(fn singleton ((value string)) -> (vec string) + (append (empty) value)) + +(fn append2 ((values (vec string)) (first string) (second string)) -> (vec string) + (append (append values first) second)) + +(fn append3 ((values (vec string)) (first string) (second string) (third string)) -> (vec string) + (append (append2 values first second) third)) + +(fn pair ((first string) (second string)) -> (vec string) + (append2 (empty) first second)) + +(fn triple ((first string) (second string) (third string)) -> (vec string) + (append3 (empty) first second third)) + +(fn is_empty ((values (vec string))) -> bool + (= (len values) 0)) + +(fn index_or ((values (vec string)) (position i32) (fallback string)) -> string + (if (< position 0) + fallback + (if (< position (len values)) + (at values position) + fallback))) + +(fn first_or ((values (vec string)) (fallback string)) -> string + (index_or values 0 fallback)) + +(fn last_or ((values (vec string)) (fallback string)) -> string + (if (is_empty values) + fallback + (at values (- (len values) 1)))) + +(fn index_option ((values (vec string)) (position i32)) -> (option string) + (if (< position 0) + (none string) + (if (< position (len values)) + (some string (at values position)) + (none string)))) + +(fn first_option ((values (vec string))) -> (option string) + (index_option values 0)) + +(fn last_option ((values (vec string))) -> (option string) + (if (is_empty values) + (none string) + (some string (at values (- (len values) 1))))) + +(fn index_of_option_loop ((values (vec string)) (target string) (position i32) (values_len i32)) -> (option i32) + (if (< position values_len) + (if (= (at values position) target) + (some i32 position) + (index_of_option_loop values target (+ position 1) values_len)) + (none i32))) + +(fn index_of_option ((values (vec string)) (target string)) -> (option i32) + (index_of_option_loop values target 0 (len values))) + +(fn last_index_of_option_loop ((values (vec string)) (target string) (position i32) (values_len i32) (found (option i32))) -> (option i32) + (if (< position values_len) + (if (= (at values position) target) + (last_index_of_option_loop values target (+ position 1) values_len (some i32 position)) + (last_index_of_option_loop values target (+ position 1) values_len found)) + found)) + +(fn last_index_of_option ((values (vec string)) (target string)) -> (option i32) + (last_index_of_option_loop values target 0 (len values) (none i32))) + +(fn contains_loop ((values (vec string)) (target string) (position i32) (values_len i32)) -> bool + (if (< position values_len) + (if (= (at values position) target) + true + (contains_loop values target (+ position 1) values_len)) + false)) + +(fn contains ((values (vec string)) (target string)) -> bool + (contains_loop values target 0 (len values))) + +(fn count_of_loop ((values (vec string)) (target string) (position i32) (values_len i32) (hits i32)) -> i32 + (if (< position values_len) + (count_of_loop values target (+ position 1) values_len (+ hits (if (= (at values position) target) + 1 + 0))) + hits)) + +(fn count_of ((values (vec string)) (target string)) -> i32 + (count_of_loop values target 0 (len values) 0)) + +(fn concat_loop ((built (vec string)) (right (vec string)) (position i32) (right_len i32)) -> (vec string) + (if (< position right_len) + (concat_loop (append built (at right position)) right (+ position 1) right_len) + built)) + +(fn concat ((left (vec string)) (right (vec string))) -> (vec string) + (let right_len i32 (len right)) + (concat_loop left right 0 right_len)) + +(fn take_loop ((values (vec string)) (position i32) (limit i32) (built (vec string))) -> (vec string) + (if (< position limit) + (take_loop values (+ position 1) limit (append built (at values position))) + built)) + +(fn take ((values (vec string)) (count i32)) -> (vec string) + (let values_len i32 (len values)) + (if (< count 0) + (empty) + (if (< count values_len) + (take_loop values 0 count (empty)) + values))) + +(fn starts_with ((values (vec string)) (prefix (vec string))) -> bool + (let prefix_len i32 (len prefix)) + (if (< (len values) prefix_len) + false + (= (take values prefix_len) prefix))) + +(fn without_prefix ((values (vec string)) (prefix (vec string))) -> (vec string) + (if (starts_with values prefix) + (drop values (len prefix)) + values)) + +(fn ends_with ((values (vec string)) (suffix (vec string))) -> bool + (let values_len i32 (len values)) + (let suffix_len i32 (len suffix)) + (if (< values_len suffix_len) + false + (= (drop values (- values_len suffix_len)) suffix))) + +(fn without_suffix ((values (vec string)) (suffix (vec string))) -> (vec string) + (if (ends_with values suffix) + (take values (- (len values) (len suffix))) + values)) + +(fn drop_loop ((values (vec string)) (position i32) (values_len i32) (built (vec string))) -> (vec string) + (if (< position values_len) + (drop_loop values (+ position 1) values_len (append built (at values position))) + built)) + +(fn drop ((values (vec string)) (count i32)) -> (vec string) + (let values_len i32 (len values)) + (if (< count 0) + values + (if (< count values_len) + (drop_loop values count values_len (empty)) + (empty)))) + +(fn reverse_loop ((values (vec string)) (position i32) (built (vec string))) -> (vec string) + (if (< position 0) + built + (reverse_loop values (- position 1) (append built (at values position))))) + +(fn reverse ((values (vec string))) -> (vec string) + (reverse_loop values (- (len values) 1) (empty))) + +(fn subvec ((values (vec string)) (start i32) (end_exclusive i32)) -> (vec string) + (if (< start 0) + (empty) + (if (< start end_exclusive) + (take (drop values start) (- end_exclusive start)) + (empty)))) + +(fn insert_at ((values (vec string)) (position i32) (value string)) -> (vec string) + (let values_len i32 (len values)) + (if (< position 0) + values + (if (< position values_len) + (concat (append (take values position) value) (drop values position)) + (if (= position values_len) + (append values value) + values)))) + +(fn insert_range ((values (vec string)) (position i32) (inserted (vec string))) -> (vec string) + (let values_len i32 (len values)) + (if (< position 0) + values + (if (< position values_len) + (concat (concat (take values position) inserted) (drop values position)) + (if (= position values_len) + (concat values inserted) + values)))) + +(fn replace_at ((values (vec string)) (position i32) (replacement string)) -> (vec string) + (if (< position 0) + values + (if (< position (len values)) + (concat (append (take values position) replacement) (drop values (+ position 1))) + values))) + +(fn replace_range ((values (vec string)) (start i32) (end_exclusive i32) (replacement (vec string))) -> (vec string) + (let values_len i32 (len values)) + (if (< start 0) + values + (if (< start values_len) + (if (< start end_exclusive) + (concat (concat (take values start) replacement) (drop values end_exclusive)) + values) + values))) + +(fn remove_at ((values (vec string)) (position i32)) -> (vec string) + (if (< position 0) + values + (if (< position (len values)) + (concat (take values position) (drop values (+ position 1))) + values))) + +(fn remove_range ((values (vec string)) (start i32) (end_exclusive i32)) -> (vec string) + (let values_len i32 (len values)) + (if (< start 0) + values + (if (< start values_len) + (if (< start end_exclusive) + (concat (take values start) (drop values end_exclusive)) + values) + values))) diff --git a/examples/random.slo b/examples/random.slo new file mode 100644 index 0000000..fb387ce --- /dev/null +++ b/examples/random.slo @@ -0,0 +1,16 @@ +(module main) + +(fn random_i32 () -> i32 + (std.random.i32)) + +(fn random_is_non_negative () -> bool + (if (< (random_i32) 0) + false + true)) + +(test "random i32 is non-negative" + (random_is_non_negative)) + +(fn main () -> i32 + (std.io.print_bool (random_is_non_negative)) + 0) diff --git a/examples/result-f64-bool-match.slo b/examples/result-f64-bool-match.slo new file mode 100644 index 0000000..9f9801c --- /dev/null +++ b/examples/result-f64-bool-match.slo @@ -0,0 +1,62 @@ +(module main) + +(fn f64_ok () -> (result f64 i32) + (ok f64 i32 1.5)) + +(fn f64_err () -> (result f64 i32) + (err f64 i32 7)) + +(fn bool_ok () -> (result bool i32) + (ok bool i32 true)) + +(fn bool_err () -> (result bool i32) + (err bool i32 9)) + +(fn f64_ok_value () -> f64 + (match (f64_ok) + ((ok value) + value) + ((err code) + 0.0))) + +(fn f64_err_code () -> i32 + (match (f64_err) + ((ok value) + 0) + ((err code) + code))) + +(fn bool_ok_value () -> bool + (match (bool_ok) + ((ok value) + value) + ((err code) + false))) + +(fn bool_err_code () -> i32 + (match (bool_err) + ((ok value) + 0) + ((err code) + code))) + +(fn main () -> i32 + (if (= (f64_ok_value) 1.5) + (if (= (f64_err_code) 7) + (if (bool_ok_value) + (bool_err_code) + 1) + 1) + 1)) + +(test "result f64 ok constructor and match" + (= (f64_ok_value) 1.5)) + +(test "result f64 err constructor and match" + (= (f64_err_code) 7)) + +(test "result bool ok constructor and match" + (bool_ok_value)) + +(test "result bool err constructor and match" + (= (bool_err_code) 9)) diff --git a/examples/result-helpers.slo b/examples/result-helpers.slo new file mode 100644 index 0000000..70ace9d --- /dev/null +++ b/examples/result-helpers.slo @@ -0,0 +1,71 @@ +(module main) + +(fn i32_ok () -> (result i32 i32) + (ok i32 i32 42)) + +(fn i32_err () -> (result i32 i32) + (err i32 i32 7)) + +(fn text_ok () -> (result string i32) + (ok string i32 "value")) + +(fn text_err () -> (result string i32) + (err string i32 9)) + +(fn observe_i32_ok () -> bool + (std.result.is_ok (i32_ok))) + +(fn observe_i32_err () -> bool + (std.result.is_err (i32_err))) + +(fn unwrap_i32_ok () -> i32 + (std.result.unwrap_ok (i32_ok))) + +(fn unwrap_i32_err () -> i32 + (std.result.unwrap_err (i32_err))) + +(fn observe_text_ok () -> bool + (std.result.is_ok (text_ok))) + +(fn observe_text_err () -> bool + (std.result.is_err (text_err))) + +(fn unwrap_text_ok () -> string + (std.result.unwrap_ok (text_ok))) + +(fn unwrap_text_err () -> i32 + (std.result.unwrap_err (text_err))) + +(fn legacy_i32_ok () -> bool + (is_ok (i32_ok))) + +(fn legacy_text_err () -> i32 + (unwrap_err (text_err))) + +(test "std result i32 observers" + (if (std.result.is_ok (i32_ok)) + (std.result.is_err (i32_err)) + false)) + +(test "std result i32 unwraps" + (= (+ (std.result.unwrap_ok (i32_ok)) (std.result.unwrap_err (i32_err))) 49)) + +(test "std result string observers" + (if (std.result.is_ok (text_ok)) + (std.result.is_err (text_err)) + false)) + +(test "std result string unwraps" + (if (= (std.result.unwrap_ok (text_ok)) "value") + (= (std.result.unwrap_err (text_err)) 9) + false)) + +(test "legacy result helpers still work" + (if (is_ok (i32_ok)) + (= (unwrap_err (text_err)) 9) + false)) + +(fn main () -> i32 + (if (std.result.is_ok (i32_ok)) + (std.result.unwrap_ok (i32_ok)) + (std.result.unwrap_err (i32_err)))) diff --git a/examples/standard-runtime.slo b/examples/standard-runtime.slo new file mode 100644 index 0000000..607fd52 --- /dev/null +++ b/examples/standard-runtime.slo @@ -0,0 +1,22 @@ +(module main) + +(fn label () -> string + "standard") + +(fn echo ((value string)) -> string + value) + +(fn label_len () -> i32 + (std.string.len (echo "standard"))) + +(test "std string equality" + (= (echo "standard") "standard")) + +(test "std string byte length" + (= (label_len) 8)) + +(fn main () -> i32 + (std.io.print_string (echo "standard")) + (std.io.print_bool (= (echo "standard") "standard")) + (std.io.print_i32 (std.string.len "standard")) + 0) diff --git a/examples/stdin-result.slo b/examples/stdin-result.slo new file mode 100644 index 0000000..2a55836 --- /dev/null +++ b/examples/stdin-result.slo @@ -0,0 +1,38 @@ +(module main) + +(fn stdin_result () -> (result string i32) + (std.io.read_stdin_result)) + +(fn stdin_text_or_empty ((value (result string i32))) -> string + (match value + ((ok text) + text) + ((err code) + ""))) + +(fn stdin_len_or_code ((value (result string i32))) -> i32 + (match value + ((ok text) + (std.string.len text)) + ((err code) + code))) + +(fn stdin_ok_len () -> i32 + (std.string.len (unwrap_ok (stdin_result)))) + +(test "stdin result test runner returns ok" + (is_ok (stdin_result))) + +(test "stdin result payload length matches match" + (let value (result string i32) (stdin_result)) + (= (std.string.len (unwrap_ok value)) (stdin_len_or_code value))) + +(test "stdin result match observes ok payload" + (let value (result string i32) (stdin_result)) + (= (std.string.len (stdin_text_or_empty value)) (stdin_len_or_code value))) + +(fn main () -> i32 + (let value (result string i32) (stdin_result)) + (if (is_ok value) + (std.string.len (unwrap_ok value)) + 1)) diff --git a/examples/string-parse-bool-result.slo b/examples/string-parse-bool-result.slo new file mode 100644 index 0000000..881f083 --- /dev/null +++ b/examples/string-parse-bool-result.slo @@ -0,0 +1,55 @@ +(module main) + +(fn parse_bool ((text string)) -> (result bool i32) + (std.string.parse_bool_result text)) + +(fn bool_score ((text string)) -> i32 + (let value (result bool i32) (parse_bool text)) + (if (std.result.is_ok value) + (if (std.result.unwrap_ok value) + 1 + 0) + (std.result.unwrap_err value))) + +(test "parse bool true ok" + (let value (result bool i32) (parse_bool "true")) + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + false)) + +(test "parse bool false ok" + (let value (result bool i32) (parse_bool "false")) + (if (std.result.is_ok value) + (if (std.result.unwrap_ok value) + false + true) + false)) + +(test "parse bool uppercase err" + (let value (result bool i32) (parse_bool "TRUE")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "parse bool empty err" + (let value (result bool i32) (parse_bool "")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "parse bool whitespace err" + (let value (result bool i32) (parse_bool " true")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "parse bool helper flow" + (= (+ (bool_score "true") (bool_score "false")) 1)) + +(fn main () -> i32 + (let value (result bool i32) (parse_bool "true")) + (if (std.result.is_ok value) + (if (std.result.unwrap_ok value) + 0 + 1) + (std.result.unwrap_err value))) diff --git a/examples/string-parse-f64-result.slo b/examples/string-parse-f64-result.slo new file mode 100644 index 0000000..6d48b57 --- /dev/null +++ b/examples/string-parse-f64-result.slo @@ -0,0 +1,36 @@ +(module main) + +(fn parse_f64 ((text string)) -> (result f64 i32) + (std.string.parse_f64_result text)) + +(test "parse f64 decimal ok" + (let value (result f64 i32) (parse_f64 "12.5")) + (if (std.result.is_ok value) + (= (std.result.unwrap_ok value) 12.5) + false)) + +(test "parse f64 negative decimal ok" + (let value (result f64 i32) (parse_f64 "-0.25")) + (if (std.result.is_ok value) + (= (std.result.unwrap_ok value) (- 0.0 0.25)) + false)) + +(test "parse f64 text err" + (let value (result f64 i32) (parse_f64 "abc")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "parse f64 nan err" + (let value (result f64 i32) (parse_f64 "nan")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(fn main () -> i32 + (let value (result f64 i32) (parse_f64 "-0.25")) + (if (std.result.is_ok value) + (if (= (std.result.unwrap_ok value) (- 0.0 0.25)) + 0 + 1) + (std.result.unwrap_err value))) diff --git a/examples/string-parse-i32-result.slo b/examples/string-parse-i32-result.slo new file mode 100644 index 0000000..6f02489 --- /dev/null +++ b/examples/string-parse-i32-result.slo @@ -0,0 +1,41 @@ +(module main) + +(fn parse_text ((text string)) -> (result i32 i32) + (std.string.parse_i32_result text)) + +(fn parse_stdin_text () -> (result i32 i32) + (let input (result string i32) (std.io.read_stdin_result)) + (match input + ((ok text) + (std.string.parse_i32_result text)) + ((err code) + (err i32 i32 code)))) + +(test "parse 42 ok" + (= (unwrap_ok (parse_text "42")) 42)) + +(test "parse negative 7 ok" + (= (unwrap_ok (parse_text "-7")) -7)) + +(test "parse empty err" + (= (unwrap_err (parse_text "")) 1)) + +(test "parse trailing byte err" + (= (unwrap_err (parse_text "12x")) 1)) + +(test "parse overflow err" + (= (unwrap_err (parse_text "2147483648")) 1)) + +(test "parse stdin text structurally" + (let value (result i32 i32) (parse_stdin_text)) + (match value + ((ok parsed) + (= parsed parsed)) + ((err code) + (= code 1)))) + +(fn main () -> i32 + (let value (result i32 i32) (parse_stdin_text)) + (if (is_ok value) + (unwrap_ok value) + (unwrap_err value))) diff --git a/examples/string-parse-i64-result.slo b/examples/string-parse-i64-result.slo new file mode 100644 index 0000000..2b35533 --- /dev/null +++ b/examples/string-parse-i64-result.slo @@ -0,0 +1,45 @@ +(module main) + +(fn parse_i64 ((text string)) -> (result i64 i32) + (std.string.parse_i64_result text)) + +(fn parsed_text ((text string)) -> string + (std.num.i64_to_string (std.result.unwrap_ok (parse_i64 text)))) + +(test "parse i64 zero ok" + (= (parsed_text "0") "0")) + +(test "parse i64 negative ok" + (= (parsed_text "-7") "-7")) + +(test "parse i64 low ok" + (= (parsed_text "-9223372036854775808") "-9223372036854775808")) + +(test "parse i64 high ok" + (= (parsed_text "9223372036854775807") "9223372036854775807")) + +(test "parse i64 empty err" + (let value (result i64 i32) (parse_i64 "")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "parse i64 plus err" + (let value (result i64 i32) (parse_i64 "+1")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "parse i64 above range err" + (let value (result i64 i32) (parse_i64 "9223372036854775808")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(fn main () -> i32 + (let value (result i64 i32) (parse_i64 "-7")) + (if (std.result.is_ok value) + (if (= (std.num.i64_to_string (std.result.unwrap_ok value)) "-7") + 0 + 1) + (std.result.unwrap_err value))) diff --git a/examples/string-parse-u32-result.slo b/examples/string-parse-u32-result.slo new file mode 100644 index 0000000..76fad8d --- /dev/null +++ b/examples/string-parse-u32-result.slo @@ -0,0 +1,45 @@ +(module main) + +(fn parse_u32 ((text string)) -> (result u32 i32) + (std.string.parse_u32_result text)) + +(fn parsed_text ((text string)) -> string + (std.num.u32_to_string (std.result.unwrap_ok (parse_u32 text)))) + +(test "parse u32 zero ok" + (= (parsed_text "0") "0")) + +(test "parse u32 high ok" + (= (parsed_text "4294967295") "4294967295")) + +(test "parse u32 empty err" + (let value (result u32 i32) (parse_u32 "")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "parse u32 plus err" + (let value (result u32 i32) (parse_u32 "+1")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "parse u32 negative err" + (let value (result u32 i32) (parse_u32 "-1")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "parse u32 above range err" + (let value (result u32 i32) (parse_u32 "4294967296")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(fn main () -> i32 + (let value (result u32 i32) (parse_u32 "42")) + (if (std.result.is_ok value) + (if (= (std.num.u32_to_string (std.result.unwrap_ok value)) "42") + 0 + 1) + (std.result.unwrap_err value))) diff --git a/examples/string-parse-u64-result.slo b/examples/string-parse-u64-result.slo new file mode 100644 index 0000000..362ac5f --- /dev/null +++ b/examples/string-parse-u64-result.slo @@ -0,0 +1,45 @@ +(module main) + +(fn parse_u64 ((text string)) -> (result u64 i32) + (std.string.parse_u64_result text)) + +(fn parsed_text ((text string)) -> string + (std.num.u64_to_string (std.result.unwrap_ok (parse_u64 text)))) + +(test "parse u64 zero ok" + (= (parsed_text "0") "0")) + +(test "parse u64 high ok" + (= (parsed_text "18446744073709551615") "18446744073709551615")) + +(test "parse u64 empty err" + (let value (result u64 i32) (parse_u64 "")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "parse u64 plus err" + (let value (result u64 i32) (parse_u64 "+1")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "parse u64 negative err" + (let value (result u64 i32) (parse_u64 "-1")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "parse u64 above range err" + (let value (result u64 i32) (parse_u64 "18446744073709551616")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(fn main () -> i32 + (let value (result u64 i32) (parse_u64 "42")) + (if (std.result.is_ok value) + (if (= (std.num.u64_to_string (std.result.unwrap_ok value)) "42") + 0 + 1) + (std.result.unwrap_err value))) diff --git a/examples/string-print.slo b/examples/string-print.slo new file mode 100644 index 0000000..abd8fa5 --- /dev/null +++ b/examples/string-print.slo @@ -0,0 +1,6 @@ +(module main) + +(fn main () -> i32 + (print_string "hello") + (print_string "line\nquote\"slash\\tab\t") + 0) diff --git a/examples/string-value-flow.slo b/examples/string-value-flow.slo new file mode 100644 index 0000000..8780425 --- /dev/null +++ b/examples/string-value-flow.slo @@ -0,0 +1,30 @@ +(module main) + +(fn label () -> string + "slovo") + +(fn echo ((value string)) -> string + value) + +(fn local_label () -> string + (let value string (label)) + value) + +(fn label_len () -> i32 + (string_len (local_label))) + +(test "string literal equality" + (= "slovo" "slovo")) + +(test "string parameter equality" + (= (echo "runtime") "runtime")) + +(test "string call return equality" + (= (local_label) "slovo")) + +(test "string byte length" + (= (label_len) 5)) + +(fn main () -> i32 + (print_string (local_label)) + (label_len)) diff --git a/examples/struct-value-flow.slo b/examples/struct-value-flow.slo new file mode 100644 index 0000000..6b73912 --- /dev/null +++ b/examples/struct-value-flow.slo @@ -0,0 +1,31 @@ +(module main) + +(struct Point + (x i32) + (y i32)) + +(fn make_point ((x i32) (y i32)) -> Point + (Point (x x) (y y))) + +(fn point_x ((p Point)) -> i32 + (. p x)) + +(fn point_sum ((p Point)) -> i32 + (+ (. p x) (. p y))) + +(fn local_point_sum () -> i32 + (let p Point (make_point 20 22)) + (point_sum p)) + +(test "struct local value flow" + (= (local_point_sum) 42)) + +(test "struct parameter value flow" + (= (point_x (make_point 7 9)) 7)) + +(test "stored struct field access" + (let p Point (make_point 3 4)) + (= (. p y) 4)) + +(fn main () -> i32 + (local_point_sum)) diff --git a/examples/struct.slo b/examples/struct.slo new file mode 100644 index 0000000..1cecdcb --- /dev/null +++ b/examples/struct.slo @@ -0,0 +1,17 @@ +(module main) + +(struct Point + (x i32) + (y i32)) + +(fn point_sum () -> i32 + (+ (. (Point (x 20) (y 22)) x) (. (Point (x 20) (y 22)) y))) + +(test "struct field access" + (= (point_sum) 42)) + +(test "struct field compares" + (= (. (Point (x 7) (y 9)) y) 9)) + +(fn main () -> i32 + (point_sum)) diff --git a/examples/time-sleep.slo b/examples/time-sleep.slo new file mode 100644 index 0000000..f01577c --- /dev/null +++ b/examples/time-sleep.slo @@ -0,0 +1,21 @@ +(module main) + +(fn monotonic_self_equal () -> bool + (let now i32 (std.time.monotonic_ms)) + (= now now)) + +(fn sleep_zero_then_self_equal () -> bool + (std.time.sleep_ms 0) + (monotonic_self_equal)) + +(test "monotonic value is self equal" + (monotonic_self_equal)) + +(test "sleep zero returns" + (sleep_zero_then_self_equal)) + +(fn main () -> i32 + (std.time.sleep_ms 0) + (if (monotonic_self_equal) + 0 + 1)) diff --git a/examples/u32-numeric-primitive.slo b/examples/u32-numeric-primitive.slo new file mode 100644 index 0000000..672e636 --- /dev/null +++ b/examples/u32-numeric-primitive.slo @@ -0,0 +1,39 @@ +(module main) + +(fn base () -> u32 + 1073741824u32) + +(fn adjust ((value u32) (delta u32)) -> u32 + (+ value delta)) + +(fn doubled ((value u32)) -> u32 + (* value 2u32)) + +(fn local_total () -> u32 + (let offset u32 15u32) + (adjust (doubled (base)) offset)) + +(fn high_enough ((value u32)) -> bool + (if (> value 2147483660u32) + (< value 2147483670u32) + false)) + +(fn exact_u32 () -> bool + (= (local_total) 2147483663u32)) + +(fn main () -> i32 + (std.io.print_u32 (local_total)) + (if (high_enough (local_total)) + 0 + 1)) + +(test "u32 arithmetic returns exact fixture value" + (exact_u32)) + +(test "u32 comparison works in predicates" + (high_enough (local_total))) + +(test "u32 division and ordering" + (if (>= (/ (local_total) 3u32) 715827887u32) + (<= (/ (local_total) 3u32) 715827887u32) + false)) diff --git a/examples/u64-numeric-primitive.slo b/examples/u64-numeric-primitive.slo new file mode 100644 index 0000000..6d3441d --- /dev/null +++ b/examples/u64-numeric-primitive.slo @@ -0,0 +1,39 @@ +(module main) + +(fn base () -> u64 + 4294967296u64) + +(fn adjust ((value u64) (delta u64)) -> u64 + (+ value delta)) + +(fn doubled ((value u64)) -> u64 + (* value 2u64)) + +(fn local_total () -> u64 + (let offset u64 19u64) + (adjust (/ (doubled (base)) 2u64) offset)) + +(fn high_enough ((value u64)) -> bool + (if (> value 4294967300u64) + (< value 4294967320u64) + false)) + +(fn exact_u64 () -> bool + (= (local_total) 4294967315u64)) + +(fn main () -> i32 + (std.io.print_u64 (local_total)) + (if (high_enough (local_total)) + 0 + 1)) + +(test "u64 arithmetic returns exact fixture value" + (exact_u64)) + +(test "u64 comparison works in predicates" + (high_enough (local_total))) + +(test "u64 division and ordering" + (if (>= (/ (local_total) 5u64) 858993463u64) + (<= (/ (local_total) 5u64) 858993463u64) + false)) diff --git a/examples/unsafe.slo b/examples/unsafe.slo new file mode 100644 index 0000000..ab8651c --- /dev/null +++ b/examples/unsafe.slo @@ -0,0 +1,16 @@ +(module main) + +(fn add_one_in_unsafe ((value i32)) -> i32 + (unsafe + (let one i32 1) + (+ value one))) + +(test "unsafe block returns final value" + (= (add_one_in_unsafe 4) 5)) + +(test "unsafe block can return bool" + (unsafe + (= (add_one_in_unsafe 1) 2))) + +(fn main () -> i32 + (add_one_in_unsafe 41)) diff --git a/examples/unsigned-integer-to-string.slo b/examples/unsigned-integer-to-string.slo new file mode 100644 index 0000000..1ef0efe --- /dev/null +++ b/examples/unsigned-integer-to-string.slo @@ -0,0 +1,47 @@ +(module main) + +(fn u32_zero_text () -> string + (std.num.u32_to_string 0u32)) + +(fn u32_high_text () -> string + (std.num.u32_to_string 4294967295u32)) + +(fn u64_zero_text () -> string + (std.num.u64_to_string 0u64)) + +(fn u64_high_text () -> string + (std.num.u64_to_string 18446744073709551615u64)) + +(fn u64_beyond_u32_text () -> string + (std.num.u64_to_string 4294967296u64)) + +(test "u32 zero to string" + (= (u32_zero_text) "0")) + +(test "u32 high to string" + (= (u32_high_text) "4294967295")) + +(test "u32 high string length" + (= (std.string.len (u32_high_text)) 10)) + +(test "u64 zero to string" + (= (u64_zero_text) "0")) + +(test "u64 high to string" + (= (u64_high_text) "18446744073709551615")) + +(test "u64 beyond u32 to string" + (= (u64_beyond_u32_text) "4294967296")) + +(test "u64 high string length" + (= (std.string.len (u64_high_text)) 20)) + +(fn main () -> i32 + (std.io.print_string (u32_zero_text)) + (std.io.print_string (u32_high_text)) + (std.io.print_string (u64_zero_text)) + (std.io.print_string (u64_high_text)) + (std.io.print_string (u64_beyond_u32_text)) + (if (= (std.string.len (u64_beyond_u32_text)) 10) + 0 + 1)) diff --git a/examples/vec-bool.slo b/examples/vec-bool.slo new file mode 100644 index 0000000..4d327b3 --- /dev/null +++ b/examples/vec-bool.slo @@ -0,0 +1,45 @@ +(module main) + +(fn empty_values () -> (vec bool) + (std.vec.bool.empty)) + +(fn pair () -> (vec bool) + (let values (vec bool) (std.vec.bool.empty)) + (let first (vec bool) (std.vec.bool.append values true)) + (std.vec.bool.append first false)) + +(fn echo ((values (vec bool))) -> (vec bool) + values) + +(fn length ((values (vec bool))) -> i32 + (std.vec.bool.len values)) + +(fn at ((values (vec bool)) (i i32)) -> bool + (std.vec.bool.index values i)) + +(fn call_return_value () -> bool + (at (echo (pair)) 1)) + +(fn original_len_after_append () -> i32 + (let values (vec bool) (std.vec.bool.empty)) + (let appended (vec bool) (std.vec.bool.append values true)) + (std.vec.bool.len values)) + +(test "vec bool empty length" + (= (std.vec.bool.len (empty_values)) 0)) + +(test "vec bool append length" + (= (length (pair)) 2)) + +(test "vec bool index" + (= (at (pair) 1) false)) + +(test "vec bool append is immutable" + (= (original_len_after_append) 0)) + +(test "vec bool equality" + (= (pair) (std.vec.bool.append (std.vec.bool.append (std.vec.bool.empty) true) false))) + +(fn main () -> i32 + (std.io.print_bool (call_return_value)) + 0) diff --git a/examples/vec-f64.slo b/examples/vec-f64.slo new file mode 100644 index 0000000..d5e6d62 --- /dev/null +++ b/examples/vec-f64.slo @@ -0,0 +1,45 @@ +(module main) + +(fn empty_values () -> (vec f64) + (std.vec.f64.empty)) + +(fn pair ((base f64)) -> (vec f64) + (let values (vec f64) (std.vec.f64.empty)) + (let first (vec f64) (std.vec.f64.append values base)) + (std.vec.f64.append first (+ base 1.0))) + +(fn echo ((values (vec f64))) -> (vec f64) + values) + +(fn length ((values (vec f64))) -> i32 + (std.vec.f64.len values)) + +(fn at ((values (vec f64)) (i i32)) -> f64 + (std.vec.f64.index values i)) + +(fn call_return_value () -> f64 + (at (echo (pair 2147483648.0)) 1)) + +(fn original_len_after_append () -> i32 + (let values (vec f64) (std.vec.f64.empty)) + (let appended (vec f64) (std.vec.f64.append values 1.0)) + (std.vec.f64.len values)) + +(test "vec f64 empty length" + (= (std.vec.f64.len (empty_values)) 0)) + +(test "vec f64 append length" + (= (length (pair 40.0)) 2)) + +(test "vec f64 index" + (= (at (pair 40.0) 1) 41.0)) + +(test "vec f64 append is immutable" + (= (original_len_after_append) 0)) + +(test "vec f64 equality" + (= (pair 5.0) (std.vec.f64.append (std.vec.f64.append (std.vec.f64.empty) 5.0) 6.0))) + +(fn main () -> i32 + (std.io.print_f64 (call_return_value)) + 0) diff --git a/examples/vec-i32.slo b/examples/vec-i32.slo new file mode 100644 index 0000000..ce3cda0 --- /dev/null +++ b/examples/vec-i32.slo @@ -0,0 +1,45 @@ +(module main) + +(fn empty_values () -> (vec i32) + (std.vec.i32.empty)) + +(fn pair ((base i32)) -> (vec i32) + (let values (vec i32) (std.vec.i32.empty)) + (let first (vec i32) (std.vec.i32.append values base)) + (std.vec.i32.append first (+ base 1))) + +(fn echo ((values (vec i32))) -> (vec i32) + values) + +(fn length ((values (vec i32))) -> i32 + (std.vec.i32.len values)) + +(fn at ((values (vec i32)) (i i32)) -> i32 + (std.vec.i32.index values i)) + +(fn call_return_len () -> i32 + (std.vec.i32.len (echo (pair 20)))) + +(fn original_len_after_append () -> i32 + (let values (vec i32) (std.vec.i32.empty)) + (let appended (vec i32) (std.vec.i32.append values 1)) + (std.vec.i32.len values)) + +(test "vec i32 empty length" + (= (std.vec.i32.len (empty_values)) 0)) + +(test "vec i32 append length" + (= (length (pair 40)) 2)) + +(test "vec i32 index" + (= (at (pair 40) 1) 41)) + +(test "vec i32 append is immutable" + (= (original_len_after_append) 0)) + +(test "vec i32 equality" + (= (pair 5) (std.vec.i32.append (std.vec.i32.append (std.vec.i32.empty) 5) 6))) + +(fn main () -> i32 + (std.io.print_i32 (call_return_len)) + (at (pair 40) 1)) diff --git a/examples/vec-string.slo b/examples/vec-string.slo new file mode 100644 index 0000000..f7fcf42 --- /dev/null +++ b/examples/vec-string.slo @@ -0,0 +1,45 @@ +(module main) + +(fn empty_values () -> (vec string) + (std.vec.string.empty)) + +(fn pair ((first string) (second string)) -> (vec string) + (let values (vec string) (std.vec.string.empty)) + (let first_values (vec string) (std.vec.string.append values first)) + (std.vec.string.append first_values second)) + +(fn echo ((values (vec string))) -> (vec string) + values) + +(fn length ((values (vec string))) -> i32 + (std.vec.string.len values)) + +(fn at ((values (vec string)) (i i32)) -> string + (std.vec.string.index values i)) + +(fn call_return_value () -> string + (at (echo (pair "slovo" "tree")) 1)) + +(fn original_len_after_append () -> i32 + (let values (vec string) (std.vec.string.empty)) + (let appended (vec string) (std.vec.string.append values "branch")) + (std.vec.string.len values)) + +(test "vec string empty length" + (= (std.vec.string.len (empty_values)) 0)) + +(test "vec string append length" + (= (length (pair "alpha" "beta")) 2)) + +(test "vec string index" + (= (at (pair "slovo" "tree") 1) "tree")) + +(test "vec string append is immutable" + (= (original_len_after_append) 0)) + +(test "vec string equality" + (= (pair "leaf" "root") (std.vec.string.append (std.vec.string.append (std.vec.string.empty) "leaf") "root"))) + +(fn main () -> i32 + (std.io.print_string (call_return_value)) + 0) diff --git a/examples/while.slo b/examples/while.slo new file mode 100644 index 0000000..6c60d5b --- /dev/null +++ b/examples/while.slo @@ -0,0 +1,22 @@ +(module main) + +(fn count_to ((limit i32)) -> i32 + (var i i32 0) + (while (< i limit) + (set i (+ i 1))) + i) + +(test "while counts" + (var i i32 0) + (while (< i 3) + (set i (+ i 1))) + (= i 3)) + +(test "while false skips" + (var i i32 0) + (while false + (set i (+ i 1))) + (= i 0)) + +(fn main () -> i32 + (count_to 4)) diff --git a/examples/workspaces/exp-5-local/packages/app/slovo.toml b/examples/workspaces/exp-5-local/packages/app/slovo.toml new file mode 100644 index 0000000..1f1d13d --- /dev/null +++ b/examples/workspaces/exp-5-local/packages/app/slovo.toml @@ -0,0 +1,8 @@ +[package] +name = "app" +version = "0.1.0" +source_root = "src" +entry = "main" + +[dependencies] +mathlib = { path = "../mathlib" } diff --git a/examples/workspaces/exp-5-local/packages/app/src/main.slo b/examples/workspaces/exp-5-local/packages/app/src/main.slo new file mode 100644 index 0000000..69f19ed --- /dev/null +++ b/examples/workspaces/exp-5-local/packages/app/src/main.slo @@ -0,0 +1,10 @@ +(module main) + +(import mathlib.math (add_one)) + +(fn main () -> i32 + (print_i32 (add_one 41)) + 0) + +(test "package import add one" + (= (add_one 41) 42)) diff --git a/examples/workspaces/exp-5-local/packages/mathlib/slovo.toml b/examples/workspaces/exp-5-local/packages/mathlib/slovo.toml new file mode 100644 index 0000000..1db226b --- /dev/null +++ b/examples/workspaces/exp-5-local/packages/mathlib/slovo.toml @@ -0,0 +1,4 @@ +[package] +name = "mathlib" +version = "0.1.0" +source_root = "src" diff --git a/examples/workspaces/exp-5-local/packages/mathlib/src/math.slo b/examples/workspaces/exp-5-local/packages/mathlib/src/math.slo new file mode 100644 index 0000000..9933d58 --- /dev/null +++ b/examples/workspaces/exp-5-local/packages/mathlib/src/math.slo @@ -0,0 +1,7 @@ +(module math (export add_one)) + +(fn add_one ((value i32)) -> i32 + (+ value 1)) + +(test "mathlib add one" + (= (add_one 41) 42)) diff --git a/examples/workspaces/exp-5-local/slovo.toml b/examples/workspaces/exp-5-local/slovo.toml new file mode 100644 index 0000000..13f7ff8 --- /dev/null +++ b/examples/workspaces/exp-5-local/slovo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["packages/app", "packages/mathlib"] diff --git a/examples/workspaces/std-import-option/packages/app/slovo.toml b/examples/workspaces/std-import-option/packages/app/slovo.toml new file mode 100644 index 0000000..46096d0 --- /dev/null +++ b/examples/workspaces/std-import-option/packages/app/slovo.toml @@ -0,0 +1,5 @@ +[package] +name = "app" +version = "0.1.0" +source_root = "src" +entry = "main" diff --git a/examples/workspaces/std-import-option/packages/app/src/main.slo b/examples/workspaces/std-import-option/packages/app/src/main.slo new file mode 100644 index 0000000..69229cc --- /dev/null +++ b/examples/workspaces/std-import-option/packages/app/src/main.slo @@ -0,0 +1,111 @@ +(module main) + +(import std.option (is_some_i32 is_none_i32 unwrap_or_i32 some_or_err_i32 some_i64 none_i64 unwrap_or_i64 some_or_err_i64 some_f64 none_f64 unwrap_or_f64 some_or_err_f64 some_bool none_bool unwrap_or_bool some_or_err_bool some_string none_string unwrap_or_string some_or_err_string)) + +(fn some_value ((value i32)) -> (option i32) + (some i32 value)) + +(fn none_value () -> (option i32) + (none i32)) + +(fn workspace_i32_bridge_ok () -> bool + (if (match (some_or_err_i32 (some_value 42) 5) + ((ok payload) + (= payload 42)) + ((err code) + false)) + (match (some_or_err_i32 (none_value) 5) + ((ok payload) + false) + ((err code) + (= code 5))) + false)) + +(fn workspace_i64_bridge_ok () -> bool + (if (= (+ (unwrap_or_i64 (some_i64 40i64) 0i64) (unwrap_or_i64 (none_i64) 2i64)) 42i64) + (if (match (some_or_err_i64 (some_i64 42i64) 6) + ((ok payload) + (= payload 42i64)) + ((err code) + false)) + (match (some_or_err_i64 (none_i64) 6) + ((ok payload) + false) + ((err code) + (= code 6))) + false) + false)) + +(fn workspace_f64_bridge_ok () -> bool + (if (= (+ (unwrap_or_f64 (some_f64 40.0) 0.0) (unwrap_or_f64 (none_f64) 2.0)) 42.0) + (if (match (some_or_err_f64 (some_f64 42.5) 7) + ((ok payload) + (= payload 42.5)) + ((err code) + false)) + (match (some_or_err_f64 (none_f64) 7) + ((ok payload) + false) + ((err code) + (= code 7))) + false) + false)) + +(fn workspace_bool_bridge_ok () -> bool + (if (unwrap_or_bool (some_bool true) false) + (if (unwrap_or_bool (none_bool) true) + (if (match (some_or_err_bool (some_bool true) 8) + ((ok payload) + payload) + ((err code) + false)) + (match (some_or_err_bool (none_bool) 8) + ((ok payload) + false) + ((err code) + (= code 8))) + false) + false) + false)) + +(fn workspace_string_bridge_ok () -> bool + (if (= (unwrap_or_string (some_string "slovo") "fallback") "slovo") + (if (= (unwrap_or_string (none_string) "fallback") "fallback") + (if (match (some_or_err_string (some_string "slovo") 9) + ((ok payload) + (= payload "slovo")) + ((err code) + false)) + (match (some_or_err_string (none_string) 9) + ((ok payload) + false) + ((err code) + (= code 9))) + false) + false) + false)) + +(fn workspace_std_option_ok () -> bool + (if (is_some_i32 (some_value 40)) + (if (is_none_i32 (none_value)) + (if (= (+ (unwrap_or_i32 (some_value 40) 0) (unwrap_or_i32 (none_value) 2)) 42) + (if (workspace_i32_bridge_ok) + (if (workspace_i64_bridge_ok) + (if (workspace_f64_bridge_ok) + (if (workspace_bool_bridge_ok) + (workspace_string_bridge_ok) + false) + false) + false) + false) + false) + false) + false)) + +(fn main () -> i32 + (if (workspace_std_option_ok) + 42 + 1)) + +(test "workspace std option import" + (workspace_std_option_ok)) diff --git a/examples/workspaces/std-import-option/slovo.toml b/examples/workspaces/std-import-option/slovo.toml new file mode 100644 index 0000000..3bd58ef --- /dev/null +++ b/examples/workspaces/std-import-option/slovo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["packages/app"] diff --git a/lib/std/README.md b/lib/std/README.md new file mode 100644 index 0000000..6cd76c2 --- /dev/null +++ b/lib/std/README.md @@ -0,0 +1,555 @@ +# Slovo Standard Library Source Layout Alpha + +Release: established by `exp-30`, Standard Library Source Layout Alpha; +wrappers updated through `exp-31`, `exp-34`, `exp-37`, and `exp-38`; math +helpers updated through `exp-39`, `exp-56`, and `exp-57`; result helpers +updated through `exp-35`, `exp-74`, `exp-109`, and the final `exp-125` +unsigned precursor scope now absorbed into `1.0.0-beta`; option helpers +updated through `exp-36`, `exp-75`, `exp-95`, `exp-100`, `exp-102`, +`exp-109`, and the final `exp-125` unsigned precursor scope now absorbed into +`1.0.0-beta`; +explicit `std.math` source search +promoted in `exp-44`; explicit `std.result` and `std.option` source search +promoted in `exp-45`; workspace-package standard source search promoted in +`exp-46`; explicit host facade source search promoted in `exp-47`; explicit +core facade source search promoted in `exp-48`; explicit IO facade source +search promoted in `exp-49`; installed standard-library discovery promoted in +`exp-50`; ordered `SLOVO_STD_PATH` path-list discovery promoted in `exp-51`; +explicit process facade source search promoted in `exp-52`; explicit CLI +facade source search promoted in `exp-53`; io value helpers updated through +`exp-73`; io stdin helpers updated through `exp-111`; typed CLI argument +helpers updated through `exp-54`, `exp-55`, and `exp-72`; CLI option helpers +updated through `exp-110`; CLI local-source gate +aligned in `exp-78`; string fallback helpers updated through `exp-60` and +`exp-68`; string option helpers updated through `exp-110`; process fallback +helpers updated through `exp-61`; process typed helpers updated through +`exp-67` and `exp-71`; process option helpers updated through `exp-110`; env +fallback helpers updated through `exp-62` and `exp-69`; env typed helpers +updated through `exp-65`; env option helpers updated through `exp-110`; fs +fallback helpers updated through `exp-63` and `exp-70`; fs typed read helpers +updated through `exp-66`; fs option helpers updated through `exp-110`; num +fallback helpers updated through `exp-64`; concrete vec helpers updated +through released `exp-108`, including the current concrete +`std/vec_string.slo`, `std/vec_f64.slo`, and `std/vec_bool.slo` +prefix/suffix helper scopes. + +This directory is the source home for staged standard library modules and +examples. exp-44 lets project-mode source explicitly import `std/math.slo` as +`(import std.math (...))`; exp-45 extends that model to `std/result.slo` and +`std/option.slo`; exp-46 extends explicit source search to workspace packages; +exp-47 extends project-mode source search to `std/time.slo`, +`std/random.slo`, `std/env.slo`, and `std/fs.slo`; exp-48 extends +project-mode source search to `std/string.slo` and `std/num.slo`; exp-49 +extends project-mode source search to `std/io.slo`; exp-73 extends that +facade with value-returning print wrappers; exp-111 extends it again with +stdin source helpers over `std.io`, `std.string`, and `std.result`; exp-50 +lets explicit +standard-source imports find those staged files from installed +`share/slovo/std` layouts; exp-51 lets `SLOVO_STD_PATH` name an ordered OS +path list of standard-library roots; exp-52 extends project-mode source +search to `std/process.slo`; exp-53 extends project-mode source search to +`std/cli.slo`, a source-authored facade that imports `std.process` and +`std.string`; exp-54 extends that facade with typed argument parse helpers; +exp-55 makes the f64 and bool helpers result-based for missing argument +indexes; exp-72 extends that facade with typed custom-fallback helpers over +those same argument and parse result families; exp-78 records that unchanged +CLI helper surface as the Slovo-side contract for the sibling Glagol +local-source gate without widening `std.cli`; exp-60 extends `std/string.slo` +with source-authored parse fallback +helpers over the already promoted concrete parse result families; exp-61 +extends `std/process.slo` with source-authored string fallback helpers over +`arg_result`; exp-62 extends `std/env.slo` with source-authored presence and +custom-fallback helpers over `get_result`; exp-63 extends `std/fs.slo` with +source-authored text fallback/status helpers over the current filesystem +result families; exp-64 extends `std/num.slo` with source-authored checked +conversion fallback helpers over the current numeric result families; exp-65 +extends `std/env.slo` with typed parse result/fallback helpers over +environment lookup plus the existing concrete parse result families; exp-66 +extends `std/fs.slo` with typed read result/fallback helpers over +`read_text_result` plus the existing concrete parse result families; exp-67 +extends `std/process.slo` with typed argument result/fallback helpers over +`arg_result` plus the existing concrete parse result families; exp-68 extends +`std/string.slo` with typed parse custom-fallback helpers over those same +concrete parse result families; exp-69 extends `std/env.slo` with typed parse +custom-fallback helpers over environment lookup plus those same concrete parse +result families; exp-70 extends `std/fs.slo` with typed read custom-fallback +helpers over `read_text_result` plus those same concrete parse result +families; exp-71 extends `std/process.slo` with typed argument custom-fallback +helpers over `arg_result` plus those same concrete parse result families; +exp-76 extends project-mode source search to `std/vec_i32.slo`, a concrete +source-authored collection facade over the current promoted `std.vec.i32` +runtime family; exp-77 extends that facade with concrete option-returning +query helpers over the current `(option i32)` family via `std.option`; +exp-79 extends it with narrow recursive transform helpers over that same +concrete vec family; exp-80 extends it with narrow generated constructor +helpers over that same concrete vec family; exp-81 adds one public `range` +helper over that same concrete vec family; exp-82 adds one public +`replace_at` helper over that same concrete vec family; exp-83 adds one +public `remove_at` helper over that same concrete vec family; exp-84 adds +one public `insert_at` helper over that same concrete vec family; exp-85 +adds one public `subvec` helper over that same concrete vec family; exp-86 +adds one public `remove_range` helper over that same concrete vec family; +exp-87 adds one public `insert_range` helper over that same concrete vec +family; exp-88 adds one public `replace_range` helper over that same +concrete vec family; exp-89 adds one public `starts_with` helper over +that same concrete vec family; exp-90 adds one public `ends_with` +helper over that same concrete vec family; exp-91 adds one public +`without_suffix` helper over that same concrete vec family; and exp-92 adds +one public `without_prefix` helper over that same concrete vec family; exp-93 +adds one public `count_of` helper over that same concrete vec family; exp-94 +stages `std/vec_i64.slo` as a concrete source-authored `(vec i64)` baseline +facade over the sibling `std.vec.i64` runtime family; exp-96 extends that +same facade with concrete option-query helpers over raw current option forms; +exp-97 extends it with recursive immutable transform helpers plus `subvec`; +exp-98 adds connected immutable edit helpers over that same helper surface; +and exp-99 stages `std/vec_string.slo` as a concrete source-authored +`(vec string)` baseline facade over the sibling `std.vec.string` runtime +family; exp-103 stages `std/vec_f64.slo` as a concrete source-authored +`(vec f64)` baseline facade over the sibling `std.vec.f64` runtime family; +exp-104 stages `std/vec_bool.slo` as a concrete source-authored +`(vec bool)` baseline facade over the sibling `std.vec.bool` runtime family; +and exp-108 broadens all three non-i32 concrete collection lanes with one +connected prefix/suffix helper package. +It does not +make the standard library fully self-hosted, does not add automatic +standard-library imports, and does not replace the compiler-known `std.*` +standard-runtime calls cataloged in `../STANDARD_RUNTIME.md`. + +The current `.slo` files use flat module declarations such as `(module math)`. +For exp-44, exp-45, exp-47, exp-48, exp-49, exp-52, exp-53, exp-76, exp-94, +exp-96, exp-97, exp-98, exp-99, exp-103, exp-104, exp-105, exp-107, and +exp-108, `std/math.slo`, `std/result.slo`, +`std/option.slo`, `std/time.slo`, `std/random.slo`, `std/env.slo`, +`std/fs.slo`, `std/string.slo`, `std/num.slo`, `std/io.slo`, +`std/process.slo`, `std/cli.slo`, `std/vec_i32.slo`, `std/vec_f64.slo`, +`std/vec_i64.slo`, and `std/vec_string.slo` carry explicit export lists. +Glagol may address them externally as `std.math`, `std.result`, +`std.option`, `std.time`, `std.random`, `std.env`, `std.fs`, `std.string`, +`std.num`, `std.io`, `std.process`, `std.cli`, `std.vec_i32`, +`std.vec_f64`, `std.vec_bool`, `std.vec_i64`, and `std.vec_string`. +The file layout is the contract: + +- `std/cli.slo` +- `std/io.slo` +- `std/process.slo` +- `std/vec_i32.slo` +- `std/vec_f64.slo` +- `std/vec_bool.slo` +- `std/vec_i64.slo` +- `std/vec_string.slo` +- `std/string.slo` +- `std/num.slo` +- `std/result.slo` +- `std/option.slo` +- `std/math.slo` +- `std/time.slo` +- `std/random.slo` +- `std/env.slo` +- `std/fs.slo` + +This follows a Zig-like standard-library facade discipline in a Slovo-sized +form: flat `std/*.slo` facade files are the staged source surface now, and a +future `std.slo` aggregator/reexport layer remains deferred until broader +import/search semantics exist. + +`math.slo` contains narrow source-authored helpers expressible in current +Slovo. exp-39 includes `abs`, `neg`, `min`, `max`, `clamp`, `square`, `cube`, +zero/positive/negative predicates, and inclusive `in_range` helpers for +`i32`, `i64`, and finite `f64` where the current language can express those +helpers directly. exp-56 adds `rem_i32`, `is_even_i32`, `is_odd_i32`, +`rem_i64`, `is_even_i64`, and `is_odd_i64` over the promoted integer +remainder operator. exp-57 adds `bit_and_i32`, `bit_or_i32`, `bit_xor_i32`, +`bit_and_i64`, `bit_or_i64`, and `bit_xor_i64` over the promoted integer +bitwise heads. The other modules provide small wrapper functions over +already promoted compiler-known standard-runtime calls where the current +language can express a wrapper cleanly. +`std/string.slo` includes the exp-34 `parse_bool_result` wrapper over +`std.string.parse_bool_result`. exp-60 adds `parse_i32_or_zero`, +`parse_i64_or_zero`, `parse_f64_or_zero`, and `parse_bool_or_false` as +ordinary source `match` helpers over the existing concrete parse result +families. exp-68 adds `parse_i32_or`, `parse_i64_or`, `parse_f64_or`, and +`parse_bool_or` as ordinary source custom-fallback helpers over those same +concrete parse result families. exp-110 adds `parse_i32_option`, +`parse_i64_option`, `parse_f64_option`, and `parse_bool_option` as ordinary +source option helpers over those same concrete parse result families through +the exp-109 `std.result.ok_or_none_*` bridge helpers. The current Slovo-side +exp-125 target adds matching `parse_u32_result`, `parse_u32_option`, +`parse_u32_or_zero`, `parse_u32_or`, `parse_u64_result`, +`parse_u64_option`, `parse_u64_or_zero`, and `parse_u64_or` lanes. +`std/num.slo` includes the exp-31 `f64_to_i64_result` wrapper over +`std.num.f64_to_i64_result`. exp-64 adds `i64_to_i32_or`, `f64_to_i32_or`, +and `f64_to_i64_or` as ordinary source `match` helpers over the existing +checked numeric conversion result families. The current Slovo-side exp-125 +target adds `u32_to_string` and `u64_to_string`. +`std/result.slo` includes the exp-33 concrete `is_err_*` and `unwrap_err_*` +source helpers for the already promoted result families, plus +`unwrap_or_i32`, `unwrap_or_i64`, `unwrap_or_string`, and `unwrap_or_f64`. +exp-35 extends that source-authored concrete helper slice with +`is_ok_bool`, `is_err_bool`, `unwrap_ok_bool`, `unwrap_err_bool`, and +`unwrap_or_bool` for `(result bool i32)`, aligned with the exp-34 bool parse +result fixture flow. exp-74 adds concrete source constructors `ok_i32`, +`err_i32`, `ok_i64`, `err_i64`, `ok_string`, `err_string`, `ok_f64`, +`err_f64`, `ok_bool`, and `err_bool` so source can construct the current +promoted result families without repeating raw `ok` and `err` type heads. +exp-109 adds the concrete bridge helpers `ok_or_none_i32`, `ok_or_none_i64`, +`ok_or_none_string`, `ok_or_none_f64`, and `ok_or_none_bool`, returning the +matching `some` payload for `ok` results and the matching `none` value for +`err` results without importing `std.option`. The current Slovo-side exp-125 +target adds the same concrete helper families for `(result u32 i32)` and +`(result u64 i32)`. +`std/option.slo` includes the exp-36 concrete `(option i32)` helpers +`is_some_i32`, `is_none_i32`, `unwrap_some_i32`, and `unwrap_or_i32`, written +as ordinary Slovo source over existing `is_some`, `is_none`, `unwrap_some`, +and `if`. exp-75 adds `some_i32` and `none_i32` as concrete source +constructors for that same `(option i32)` family. exp-95 broadens the staged +module to concrete `(option i64)` with `some_i64`, `none_i64`, `is_some_i64`, +`is_none_i64`, `unwrap_some_i64`, and `unwrap_or_i64`, still as ordinary +source functions over the already promoted option forms. exp-100 broadens the +same staged module again to concrete `(option string)` with `some_string`, +`none_string`, `is_some_string`, `is_none_string`, `unwrap_some_string`, and +`unwrap_or_string`, still as ordinary source functions over the already +promoted option forms. exp-102 broadens the same staged module again to +concrete `(option f64)` with `some_f64`, `none_f64`, `is_some_f64`, +`is_none_f64`, `unwrap_some_f64`, and `unwrap_or_f64`, plus concrete +`(option bool)` with `some_bool`, `none_bool`, `is_some_bool`, +`is_none_bool`, `unwrap_some_bool`, and `unwrap_or_bool`, still as ordinary +source functions over the already promoted option forms. exp-109 adds the +concrete bridge helpers `some_or_err_i32`, `some_or_err_i64`, +`some_or_err_f64`, `some_or_err_bool`, and `some_or_err_string`, returning +`ok` with the matching payload for `some` values and `err err_code` for +`none` values without importing `std.result`. The current Slovo-side exp-125 +target adds the same concrete constructor/observer/unwrap/fallback/bridge +shape for `(option u32)` and `(option u64)`. +`std/time.slo` includes the exp-37 narrow source wrappers `monotonic_ms` +over `std.time.monotonic_ms` and `sleep_ms_zero` over `std.time.sleep_ms`, +calling `std.time.sleep_ms 0` and returning `0` because user-defined +unit-return functions are not generally supported. +`std/random.slo`, `std/env.slo`, and `std/fs.slo` include the exp-38 narrow +source wrappers over already released random, environment, and text filesystem +host/runtime calls. exp-62 adds `has` and `get_or` to `std/env.slo` as +ordinary source `match` helpers over the existing `(result string i32)` +environment lookup family. exp-65 extends `std/env.slo` with `get_i32_result`, +`get_i32_or_zero`, `get_i64_result`, `get_i64_or_zero`, `get_f64_result`, +`get_f64_or_zero`, `get_bool_result`, and `get_bool_or_false` as ordinary +source helpers over `get_result` plus the existing concrete parse result +families from `std.string`. exp-69 extends `std/env.slo` with `get_i32_or`, +`get_i64_or`, `get_f64_or`, and `get_bool_or` as ordinary source +custom-fallback helpers over `get_result` plus those same concrete parse +result families from `std.string`. exp-110 adds `get_option`, +`get_i32_option`, `get_i64_option`, `get_f64_option`, and `get_bool_option` +as ordinary source option helpers over the existing environment lookup and +typed parse result families through the exp-109 `std.result.ok_or_none_*` +bridge helpers. exp-63 adds `read_text_or` and +`write_text_ok` to `std/fs.slo` as ordinary source `match` helpers over the +existing `(result string i32)` and `(result i32 i32)` text filesystem +families. +exp-66 extends `std/fs.slo` with `read_i32_result`, `read_i32_or_zero`, +`read_i64_result`, `read_i64_or_zero`, `read_f64_result`, `read_f64_or_zero`, +`read_bool_result`, and `read_bool_or_false` as ordinary source helpers over +`read_text_result` plus the existing concrete parse result families from +`std.string`. exp-70 extends `std/fs.slo` with `read_i32_or`, `read_i64_or`, +`read_f64_or`, and `read_bool_or` as ordinary source custom-fallback helpers +over `read_text_result` plus those same concrete parse result families from +`std.string`. exp-110 adds `read_text_option`, `read_i32_option`, +`read_i64_option`, `read_f64_option`, and `read_bool_option` as ordinary +source option helpers over those same read/parse result families through the +exp-109 `std.result.ok_or_none_*` bridge helpers. +`std/process.slo` includes the exp-52 narrow source wrappers over already +released process argument runtime calls and a source-authored `has_arg` +predicate. exp-61 adds `arg_or` and `arg_or_empty` as ordinary source +fallback helpers over the existing `(result string i32)` process argument +family. exp-67 extends `std/process.slo` with `arg_i32_result`, +`arg_i32_or_zero`, `arg_i64_result`, `arg_i64_or_zero`, `arg_f64_result`, +`arg_f64_or_zero`, `arg_bool_result`, and `arg_bool_or_false` as ordinary +source helpers over `arg_result` plus the existing concrete parse result +families from `std.string`. exp-71 extends `std/process.slo` with `arg_i32_or`, +`arg_i64_or`, `arg_f64_or`, and `arg_bool_or` as ordinary source +custom-fallback helpers over `arg_result` plus those same concrete parse +result families from `std.string`. exp-110 adds `arg_option`, +`arg_i32_option`, `arg_i64_option`, `arg_f64_option`, and `arg_bool_option` +as ordinary source option helpers over those same argument/parse result +families through the exp-109 `std.result.ok_or_none_*` bridge helpers. +`std/io.slo` includes the exp-49 zero-returning print wrappers over the +promoted print runtime calls. exp-73 adds `print_i32_value`, +`print_i64_value`, `print_f64_value`, `print_string_value`, and +`print_bool_value` as ordinary source wrappers that preserve the printed +value for expression flow without adding new compiler-known runtime names. +exp-111 adds a connected stdin helper package over +`std.io.read_stdin_result`, `std.string.parse_*_result`, and the exp-109 +`std.result.ok_or_none_*` bridge helpers: +`read_stdin_result`, `read_stdin_option`, `read_stdin_or`, +`read_stdin_i32_result`, `read_stdin_i32_option`, `read_stdin_i32_or_zero`, +`read_stdin_i32_or`, `read_stdin_i64_result`, `read_stdin_i64_option`, +`read_stdin_i64_or_zero`, `read_stdin_i64_or`, `read_stdin_f64_result`, +`read_stdin_f64_option`, `read_stdin_f64_or_zero`, `read_stdin_f64_or`, +`read_stdin_bool_result`, `read_stdin_bool_option`, +`read_stdin_bool_or_false`, and `read_stdin_bool_or`. +`std/cli.slo` includes the exp-53 source-authored argument text and `i32` +parse facade helpers over `std.process` and `std.string`. exp-54 adds +`i64` result/fallback helpers. exp-55 adds result-based `f64` and `bool` +helpers plus `arg_f64_or_zero` and `arg_bool_or_false`. exp-72 adds +`arg_i32_or`, `arg_i64_or`, `arg_f64_or`, and `arg_bool_or` as ordinary +source custom-fallback helpers over those same CLI result helpers. exp-110 +adds `arg_text_option`, `arg_i32_option`, `arg_i64_option`, `arg_f64_option`, +and `arg_bool_option` as ordinary source option helpers over those same CLI +result helpers through the exp-109 `std.result.ok_or_none_*` bridge helpers. +The staged CLI facade now directly imports `std.result` alongside +`std.process` and `std.string`. exp-78 later records the earlier, narrower +local-source contract for the sibling Glagol gate without widening that +release's helper list or adding new compiler-known runtime names. +`std/vec_i32.slo` includes the exp-76 concrete `(vec i32)` collection facade +over the already promoted `std.vec.i32` runtime family. It adds direct +wrappers `empty`, `append`, `len`, and `at`, builder helpers `singleton`, +`append2`, `append3`, `pair`, and `triple`, query helpers `is_empty`, +`index_or`, `first_or`, and `last_or`, exp-77 option-query helpers +`index_option`, `first_option`, `last_option`, `index_of_option`, and +`last_index_of_option`, plus simple real-program helpers `contains` and +`sum` written as ordinary source `while` loops over the existing vector +runtime operations. exp-79 adds transform helpers `concat`, `take`, `drop`, +and `reverse` as ordinary recursive source helpers over `append`, `len`, +`at`, and `empty`. `take` treats negative counts as zero and returns the +original vector when the requested count reaches or exceeds the current +length; `drop` treats negative counts as zero and returns an empty vector +when the requested count reaches or exceeds the current length. exp-80 adds +generated constructor helpers `repeat` and `range_from_zero` as ordinary +recursive source helpers over `append` and `empty`. `repeat` returns an +empty vector when `count <= 0`. `range_from_zero` produces `0`, `1`, and so +on up to but excluding `count`, and returns an empty vector when +`count <= 0`. exp-81 adds public +`range ((start i32) (end_exclusive i32)) -> (vec i32)` with ascending +half-open semantics, allows negative bounds, returns an empty vector when +`end_exclusive <= start`, and may let `range_from_zero` delegate to +`range 0 count`. exp-82 adds public +`replace_at ((values (vec i32)) (position i32) (replacement i32)) -> +(vec i32)` as an ordinary compositional source helper over `append`, `take`, +`drop`, and `concat`; it returns a same-length replacement result for +in-range positions and returns `values` unchanged for negative or +out-of-range positions without mutating the source vector. exp-83 adds public +`remove_at ((values (vec i32)) (position i32)) -> (vec i32)` as an ordinary +compositional source helper over `take`, `drop`, and `concat`; it removes the +in-range `position` element, preserves the order of the remaining values, +returns a `len(values) - 1` result in that case, and returns `values` +unchanged for negative or out-of-range positions without mutating the source +vector. exp-84 adds public +`insert_at ((values (vec i32)) (position i32) (value i32)) -> (vec i32)` as +an ordinary compositional source helper over `append`, `take`, `drop`, and +`concat`; it inserts `value` before the current element at an in-range +`position`, appends when `position == len(values)`, returns a +`len(values) + 1` result for those valid insertions, and returns `values` +unchanged for negative or greater-than-length positions without mutating the +source vector. exp-85 adds public +`subvec ((values (vec i32)) (start i32) (end_exclusive i32)) -> (vec i32)` +as an ordinary compositional source helper over `take` and `drop`; it +returns a copied contiguous subvector `[start, end_exclusive)`, returns +`(empty)` when `start < 0`, `end_exclusive <= start`, or +`start >= len(values)`, returns the remaining tail from `start` when +`end_exclusive > len(values)`, preserves source order, and leaves the source +vector unchanged without introducing slice/view semantics. exp-86 adds public +`remove_range ((values (vec i32)) (start i32) (end_exclusive i32)) -> +(vec i32)` as an ordinary compositional source helper over `take`, `drop`, +and `concat`; it removes the half-open range `[start, end_exclusive)` from +`values`, returns `values` unchanged when `start < 0`, +`end_exclusive <= start`, or `start >= len(values)`, removes the tail from +`start` when `end_exclusive >= len(values)`, preserves the order of the +remaining elements, and leaves the source vector unchanged. exp-87 adds +public `insert_range ((values (vec i32)) (position i32) +(inserted (vec i32))) -> (vec i32)` as an ordinary compositional source +helper over `take`, `drop`, and `concat`; it inserts all of `inserted` +before the current element at `position` when `0 <= position < len(values)`, +appends `inserted` at the end when `position == len(values)`, returns +`values` unchanged when `position < 0` or `position > len(values)`, +preserves the order of both vectors, leaves both source vectors unchanged, +and returns a `len(values) + len(inserted)` result for valid positions. +exp-88 adds public +`replace_range ((values (vec i32)) (start i32) (end_exclusive i32) +(replacement (vec i32))) -> (vec i32)` as an ordinary compositional source +helper over `take`, `drop`, and `concat`; it replaces the half-open range +`[start, end_exclusive)` with all of `replacement`, returns `values` +unchanged when `start < 0`, `end_exclusive <= start`, or +`start >= len(values)`, replaces the tail from `start` when +`end_exclusive >= len(values)`, preserves the order of the prefix, +replacement, and surviving suffix, and leaves both source vectors +unchanged. exp-89 adds public +`starts_with ((values (vec i32)) (prefix (vec i32))) -> bool` as an ordinary +compositional source helper over `take`, `len`, and vec equality; it returns +`true` when `values` begins with all elements of `prefix` in order, returns +`true` for an empty prefix, returns `false` when `len(prefix) > len(values)`, +and leaves both source vectors unchanged. exp-90 adds public +`ends_with ((values (vec i32)) (suffix (vec i32))) -> bool` as an ordinary +compositional source helper over `drop`, `len`, and vec equality; it returns +`true` when `values` ends with all elements of `suffix` in order, returns +`true` for an empty suffix, returns `false` when `len(suffix) > len(values)`, +and leaves both source vectors unchanged. exp-91 adds public +`without_suffix ((values (vec i32)) (suffix (vec i32))) -> (vec i32)` as an +ordinary compositional source helper over `ends_with`, `take`, and `len`; it +returns `values` with a matching trailing suffix removed, returns `values` +unchanged for empty, longer, or mismatched suffixes, returns `(empty)` for an +exact match, and leaves both source vectors unchanged. exp-92 adds public +`without_prefix ((values (vec i32)) (prefix (vec i32))) -> (vec i32)` as an +ordinary compositional source helper over `starts_with`, `drop`, and `len`; +it returns `values` with a matching leading prefix removed, returns `values` +unchanged for empty, longer, or mismatched prefixes, returns `(empty)` for an +exact match, and leaves both source vectors unchanged. exp-93 adds public +`count_of ((values (vec i32)) (target i32)) -> i32` as an ordinary source +helper over `len`, `at`, equality, and `while`; it returns the number of +elements equal to `target`, returns `0` for empty or absent matches, counts +repeated matches exactly, and leaves the source vector unchanged. The +option-returning helpers stay concrete to `(option i32)` and construct +results through `std.option.some_i32` and `std.option.none_i32`. The module +stays concrete to `(vec i32)` and does not claim generic vectors, slice +types, capacity management, mutation-heavy container APIs, sorting, mapping, +filtering, or broader collection families. +`std/vec_i64.slo` includes the exp-94 concrete `(vec i64)` baseline facade +over the sibling `std.vec.i64` runtime family. It adds direct wrappers +`empty`, `append`, `len`, and `at`, builder helpers `singleton`, `append2`, +`append3`, `pair`, and `triple`, query helpers `is_empty`, `index_or`, +`first_or`, and `last_or`, plus simple real-program helpers `contains` and +`sum` written as ordinary source control-flow helpers over the direct runtime +wrappers. exp-96 extends that same facade with option-query helpers +`index_option`, `first_option`, `last_option`, `index_of_option`, and +`last_index_of_option`. Those helpers stay concrete to `(option i64)` and +`(option i32)`, construct raw current option values directly instead of +importing `std.option`, and keep the full `vec_i64` source family recursive +and immutable with no `var` or `set`. `index_or` returns its fallback for +negative and out-of-range positions; `first_or` delegates to `index_or values +0 fallback`; `last_or` returns its fallback for empty vectors and the final +element otherwise; `index_option` returns `(none i64)` for negative and +out-of-range positions; `first_option` delegates to `index_option values 0`; +`last_option` returns `(none i64)` for empty vectors and `(some i64 ...)` +otherwise; `index_of_option` and `last_index_of_option` return `(none i32)` +when the target is absent; `contains` returns `true` exactly when any element +equals the target; and `sum` returns `0i64` for empty vectors. exp-97 adds +transform helpers `concat`, `take`, `drop`, and `reverse` plus the copied +half-open range helper `subvec` as ordinary recursive source helpers over +`append`, `len`, `at`, and `empty`. `take` treats negative counts as zero and +saturates to the full vector. `drop` treats negative counts as zero and +saturates to the empty vector. `subvec` returns `(empty)` for negative or +empty ranges and saturates `end_exclusive` to the source tail. exp-98 adds +edit helpers `insert_at`, `insert_range`, `replace_at`, `replace_range`, +`remove_at`, and `remove_range` as ordinary compositional source helpers over +the already released vec_i64 helper surface, especially `concat`, `take`, and +`drop`. `insert_at` and `insert_range` append at exact tail positions and +otherwise return `values` unchanged for negative or greater-than-length +positions. `replace_at` and `remove_at` return `values` unchanged for +negative or out-of-range positions. `replace_range` and `remove_range` +return `values` unchanged for negative, empty, or past-end starts and +saturate `end_exclusive` to the source tail. The module +stays concrete to `(vec i64)` and does not claim generic vectors, slice +types, capacity management, mutation-heavy container APIs, transform/range/edit +helper families beyond `concat`, `take`, `drop`, `reverse`, `subvec`, +`insert_at`, `insert_range`, `replace_at`, `replace_range`, `remove_at`, and +`remove_range`, +sorting, mapping, filtering, or broader collection families. +`std/vec_f64.slo` includes the exp-103 concrete `(vec f64)` baseline facade +over the sibling `std.vec.f64` runtime family. exp-105 broadens the same +module with the transform helpers `concat`, `take`, `drop`, `reverse`, and +`subvec` written as ordinary recursive source helpers over the direct runtime +wrappers. exp-106 further broadens the same module with the option-query +helpers `index_option`, `first_option`, `last_option`, `index_of_option`, +and `last_index_of_option`, still written as ordinary recursive immutable +source helpers over the same direct runtime wrappers plus the current +concrete `(option f64)` and `(option i32)` forms where needed. The module +gains exp-107 immutable edit helpers `insert_at`, `insert_range`, +`replace_at`, `replace_range`, `remove_at`, and `remove_range`, still using +only existing helper composition and the same direct runtime wrappers. +exp-108 further broadens the same module with `starts_with`, +`without_prefix`, `ends_with`, and `without_suffix`, still using only +existing helper composition and the same direct runtime wrappers. The module +stays concrete to `(vec f64)` and does not claim generic vectors, capacity +management, mutation-heavy container APIs, sorting, mapping, filtering, or +broader collection families. +`std/vec_bool.slo` is the released exp-104 Slovo-side contract for a +concrete `(vec bool)` baseline facade over the sibling `std.vec.bool` runtime +family. exp-105 broadens the same module with the transform helpers +`concat`, `take`, `drop`, `reverse`, and `subvec`, all written as ordinary +deterministic recursive source helpers over the direct runtime wrappers and +bool equality. exp-106 further broadens the same module with the option-query +helpers `index_option`, `first_option`, `last_option`, `index_of_option`, +and `last_index_of_option`, still written as ordinary recursive immutable +source helpers over the same direct runtime wrappers plus the current +concrete `(option bool)` and `(option i32)` forms where needed. exp-107 +further broadens the same module with immutable edit helpers `insert_at`, +`insert_range`, `replace_at`, `replace_range`, `remove_at`, and +`remove_range`, still written as ordinary helper composition over the same +direct runtime wrappers. exp-108 further broadens the same module with +`starts_with`, `without_prefix`, `ends_with`, and `without_suffix`, again +written as ordinary helper composition over the same direct runtime +wrappers. This file is present in the released Slovo source tree, and +explicit external `std.vec_bool` resolution now depends on the paired +Glagol exp-108 gate set. +`std/vec_string.slo` includes the exp-99 concrete `(vec string)` baseline +facade over the sibling `std.vec.string` runtime family. It adds direct +wrappers `empty`, `append`, `len`, and `at`, builder helpers `singleton`, +`append2`, `append3`, `pair`, and `triple`, query helpers `is_empty`, +`index_or`, `first_or`, and `last_or`, plus simple real-program helpers +`contains` and `count_of` written as ordinary recursive source helpers over +the direct runtime wrappers and string equality. exp-101 broadens the same +module with the option-query helpers `index_option`, `first_option`, +`last_option`, `index_of_option`, and `last_index_of_option`, plus the +transform helpers `concat`, `take`, `drop`, `reverse`, and `subvec`, all +still written as ordinary recursive immutable source helpers over the same +direct runtime wrappers plus raw current `(option string)` / `(option i32)` +forms where needed. `index_or` returns its fallback for negative and +out-of-range positions; `first_or` delegates to `index_or values 0 fallback`; +`last_or` provides a stable empty-vector fallback; `contains` returns `true` +exactly when any element equals the target; `count_of` returns `0` for empty +vectors or absent targets while counting repeated matches exactly; and the +new transform helpers preserve the current immutable-vector contract. The +module gains exp-107 immutable edit helpers `insert_at`, `insert_range`, +`replace_at`, `replace_range`, `remove_at`, and `remove_range`, still +written as ordinary helper composition over the same direct runtime wrappers +and current concrete option forms where needed. exp-108 further broadens +the same module with `starts_with`, `without_prefix`, `ends_with`, and +`without_suffix`, still written as ordinary helper composition over the same +direct runtime wrappers and current concrete option forms where needed. The +module stays concrete to `(vec string)` and does not claim generics, nested +vecs or vecs inside other containers, mutating/capacity APIs, or broader +collection families. +The string fallback helper uses the existing `(result string i32)` `match` +shape instead of a string-valued `if`. + +Deferred from exp-30, exp-32, exp-33, exp-34, exp-35, exp-36, exp-37, exp-38, +exp-39, exp-44, exp-45, exp-46, exp-47, exp-48, exp-49, exp-50, exp-51, +exp-52, exp-53, exp-54, exp-55, exp-56, exp-57, exp-60, exp-61, exp-62, +exp-63, exp-64, exp-65, exp-66, exp-67, exp-68, exp-69, exp-70, exp-71, +exp-72, exp-73, exp-74, exp-75, exp-76, exp-77, exp-78, exp-79, exp-80, +exp-81, exp-82, exp-83, exp-84, exp-85, exp-86, exp-87, exp-88, exp-89, +exp-90, exp-91, exp-92, exp-93, exp-94, exp-95, exp-96, exp-97, exp-98, +and exp-99: +automatic `std` imports, workspace dependency syntax for std, installed +toolchain stdlib paths beyond the exp-50 alpha discovery candidate, a +`std.slo` aggregator, package registry behavior, lockfiles, semantic version +solving, replacing compiler-known +`std.*` calls with source implementations, stable APIs, stable ABI/layout, +package registry behavior, generics, traits, overloads, generic result +helpers, generic option helpers, option payload families beyond `(option i32)`, +`(option i64)`, and `(option string)`, +`std.result.map`, generic +`std.result.unwrap_or`, `std.result.and_then`, broad math, trigonometry, +`sqrt`, `pow`, floating-point remainder, shifts, bit-not, mixed numeric arithmetic, generic +parse, string/bytes parse, +bool parsing beyond exact lowercase `true`/`false`, rich parse errors, broad +result payload families beyond promoted concrete fixture flows, +wall-clock/calendar/timezone APIs, +high-resolution timers, async timers, cancellation, scheduling guarantees, +random seeds, ranges, bytes, floats, UUIDs, crypto/security promises, stable +random sequences, environment mutation/enumeration, environment writes, rich +host error ADTs, typed file writes, +process spawning, exit/status control, current-directory APIs, signal +handling, shell parsing, option/flag parsing, subcommands, environment-backed +configuration, stable CLI framework APIs, binary file APIs, directory +traversal, streaming file IO, async file IO, +generic collections, vector payload families beyond `(vec i32)`, +`(vec i64)`, `(vec f64)`, and `(vec string)`, option payload families beyond +`(option i32)`, `(option i64)`, `(option f64)`, `(option bool)`, and +`(option string)`, +slice types, push/pop, capacity/reserve/shrink, descending or stepped ranges, +inclusive ranges, broader prefix/suffix helpers beyond `starts_with`, +`without_prefix`, `ends_with`, and `without_suffix`, subvector/range-removal/edit helpers +beyond `insert_range`, `replace_range`, `remove_range`, `subvec`, +`insert_at`, `replace_at`, `remove_at`, `without_prefix`, and `without_suffix`, +transform/range/edit helpers for `(vec i64)` beyond `concat`, `take`, `drop`, +`reverse`, `subvec`, `insert_at`, `insert_range`, `replace_at`, +`replace_range`, `remove_at`, and `remove_range`, sorting, mapping, filtering, +iterators, optimizer guarantees, benchmark thresholds, cross-machine +performance claims, and beta maturity. diff --git a/lib/std/cli.slo b/lib/std/cli.slo new file mode 100644 index 0000000..528ffa1 --- /dev/null +++ b/lib/std/cli.slo @@ -0,0 +1,157 @@ +(module cli (export arg_text_result arg_text_option arg_i32_result arg_i32_option arg_i32_or_zero arg_i32_or arg_u32_result arg_u32_option arg_u32_or_zero arg_u32_or arg_i64_result arg_i64_option arg_i64_or_zero arg_i64_or arg_u64_result arg_u64_option arg_u64_or_zero arg_u64_or arg_f64_result arg_f64_option arg_f64_or_zero arg_f64_or arg_bool_result arg_bool_option arg_bool_or_false arg_bool_or)) + +(import std.process (arg_result)) + +(import std.result (ok_or_none_string ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool)) + +(import std.string (parse_i32_result parse_u32_result parse_i64_result parse_u64_result parse_f64_result parse_bool_result)) + +(fn arg_text_result ((index i32)) -> (result string i32) + (arg_result index)) + +(fn arg_text_option ((index i32)) -> (option string) + (ok_or_none_string (arg_text_result index))) + +(fn arg_i32_result ((index i32)) -> (result i32 i32) + (match (arg_result index) + ((ok text) + (parse_i32_result text)) + ((err code) + (err i32 i32 code)))) + +(fn arg_i32_option ((index i32)) -> (option i32) + (ok_or_none_i32 (arg_i32_result index))) + +(fn arg_i32_or_zero ((index i32)) -> i32 + (match (arg_i32_result index) + ((ok value) + value) + ((err code) + 0))) + +(fn arg_i32_or ((index i32) (fallback i32)) -> i32 + (match (arg_i32_result index) + ((ok value) + value) + ((err code) + fallback))) + +(fn arg_u32_result ((index i32)) -> (result u32 i32) + (match (arg_result index) + ((ok text) + (parse_u32_result text)) + ((err code) + (err u32 i32 code)))) + +(fn arg_u32_option ((index i32)) -> (option u32) + (ok_or_none_u32 (arg_u32_result index))) + +(fn arg_u32_or_zero ((index i32)) -> u32 + (match (arg_u32_result index) + ((ok value) + value) + ((err code) + 0u32))) + +(fn arg_u32_or ((index i32) (fallback u32)) -> u32 + (match (arg_u32_result index) + ((ok value) + value) + ((err code) + fallback))) + +(fn arg_i64_result ((index i32)) -> (result i64 i32) + (match (arg_result index) + ((ok text) + (parse_i64_result text)) + ((err code) + (err i64 i32 code)))) + +(fn arg_i64_option ((index i32)) -> (option i64) + (ok_or_none_i64 (arg_i64_result index))) + +(fn arg_i64_or_zero ((index i32)) -> i64 + (match (arg_i64_result index) + ((ok value) + value) + ((err code) + 0i64))) + +(fn arg_i64_or ((index i32) (fallback i64)) -> i64 + (match (arg_i64_result index) + ((ok value) + value) + ((err code) + fallback))) + +(fn arg_u64_result ((index i32)) -> (result u64 i32) + (match (arg_result index) + ((ok text) + (parse_u64_result text)) + ((err code) + (err u64 i32 code)))) + +(fn arg_u64_option ((index i32)) -> (option u64) + (ok_or_none_u64 (arg_u64_result index))) + +(fn arg_u64_or_zero ((index i32)) -> u64 + (match (arg_u64_result index) + ((ok value) + value) + ((err code) + 0u64))) + +(fn arg_u64_or ((index i32) (fallback u64)) -> u64 + (match (arg_u64_result index) + ((ok value) + value) + ((err code) + fallback))) + +(fn arg_f64_result ((index i32)) -> (result f64 i32) + (match (arg_result index) + ((ok text) + (parse_f64_result text)) + ((err code) + (err f64 i32 code)))) + +(fn arg_f64_option ((index i32)) -> (option f64) + (ok_or_none_f64 (arg_f64_result index))) + +(fn arg_f64_or_zero ((index i32)) -> f64 + (match (arg_f64_result index) + ((ok value) + value) + ((err code) + 0.0))) + +(fn arg_f64_or ((index i32) (fallback f64)) -> f64 + (match (arg_f64_result index) + ((ok value) + value) + ((err code) + fallback))) + +(fn arg_bool_result ((index i32)) -> (result bool i32) + (match (arg_result index) + ((ok text) + (parse_bool_result text)) + ((err code) + (err bool i32 code)))) + +(fn arg_bool_option ((index i32)) -> (option bool) + (ok_or_none_bool (arg_bool_result index))) + +(fn arg_bool_or_false ((index i32)) -> bool + (match (arg_bool_result index) + ((ok value) + value) + ((err code) + false))) + +(fn arg_bool_or ((index i32) (fallback bool)) -> bool + (match (arg_bool_result index) + ((ok value) + value) + ((err code) + fallback))) diff --git a/lib/std/env.slo b/lib/std/env.slo new file mode 100644 index 0000000..e0d449c --- /dev/null +++ b/lib/std/env.slo @@ -0,0 +1,170 @@ +(module env (export get get_result get_option has get_or get_i32_result get_i32_option get_i32_or_zero get_i32_or get_u32_result get_u32_option get_u32_or_zero get_u32_or get_i64_result get_i64_option get_i64_or_zero get_i64_or get_u64_result get_u64_option get_u64_or_zero get_u64_or get_f64_result get_f64_option get_f64_or_zero get_f64_or get_bool_result get_bool_option get_bool_or_false get_bool_or)) + +(import std.result (ok_or_none_string ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool)) + +(fn get ((name string)) -> string + (std.env.get name)) + +(fn get_result ((name string)) -> (result string i32) + (std.env.get_result name)) + +(fn get_option ((name string)) -> (option string) + (ok_or_none_string (get_result name))) + +(fn has ((name string)) -> bool + (match (get_result name) + ((ok payload) + true) + ((err code) + false))) + +(fn get_or ((name string) (fallback string)) -> string + (match (get_result name) + ((ok payload) + payload) + ((err code) + fallback))) + +(fn get_i32_result ((name string)) -> (result i32 i32) + (match (get_result name) + ((ok text) + (std.string.parse_i32_result text)) + ((err code) + (err i32 i32 code)))) + +(fn get_i32_option ((name string)) -> (option i32) + (ok_or_none_i32 (get_i32_result name))) + +(fn get_i32_or_zero ((name string)) -> i32 + (match (get_i32_result name) + ((ok value) + value) + ((err code) + 0))) + +(fn get_i32_or ((name string) (fallback i32)) -> i32 + (match (get_i32_result name) + ((ok value) + value) + ((err code) + fallback))) + +(fn get_u32_result ((name string)) -> (result u32 i32) + (match (get_result name) + ((ok text) + (std.string.parse_u32_result text)) + ((err code) + (err u32 i32 code)))) + +(fn get_u32_option ((name string)) -> (option u32) + (ok_or_none_u32 (get_u32_result name))) + +(fn get_u32_or_zero ((name string)) -> u32 + (match (get_u32_result name) + ((ok value) + value) + ((err code) + 0u32))) + +(fn get_u32_or ((name string) (fallback u32)) -> u32 + (match (get_u32_result name) + ((ok value) + value) + ((err code) + fallback))) + +(fn get_i64_result ((name string)) -> (result i64 i32) + (match (get_result name) + ((ok text) + (std.string.parse_i64_result text)) + ((err code) + (err i64 i32 code)))) + +(fn get_i64_option ((name string)) -> (option i64) + (ok_or_none_i64 (get_i64_result name))) + +(fn get_i64_or_zero ((name string)) -> i64 + (match (get_i64_result name) + ((ok value) + value) + ((err code) + 0i64))) + +(fn get_i64_or ((name string) (fallback i64)) -> i64 + (match (get_i64_result name) + ((ok value) + value) + ((err code) + fallback))) + +(fn get_u64_result ((name string)) -> (result u64 i32) + (match (get_result name) + ((ok text) + (std.string.parse_u64_result text)) + ((err code) + (err u64 i32 code)))) + +(fn get_u64_option ((name string)) -> (option u64) + (ok_or_none_u64 (get_u64_result name))) + +(fn get_u64_or_zero ((name string)) -> u64 + (match (get_u64_result name) + ((ok value) + value) + ((err code) + 0u64))) + +(fn get_u64_or ((name string) (fallback u64)) -> u64 + (match (get_u64_result name) + ((ok value) + value) + ((err code) + fallback))) + +(fn get_f64_result ((name string)) -> (result f64 i32) + (match (get_result name) + ((ok text) + (std.string.parse_f64_result text)) + ((err code) + (err f64 i32 code)))) + +(fn get_f64_option ((name string)) -> (option f64) + (ok_or_none_f64 (get_f64_result name))) + +(fn get_f64_or_zero ((name string)) -> f64 + (match (get_f64_result name) + ((ok value) + value) + ((err code) + 0.0))) + +(fn get_f64_or ((name string) (fallback f64)) -> f64 + (match (get_f64_result name) + ((ok value) + value) + ((err code) + fallback))) + +(fn get_bool_result ((name string)) -> (result bool i32) + (match (get_result name) + ((ok text) + (std.string.parse_bool_result text)) + ((err code) + (err bool i32 code)))) + +(fn get_bool_option ((name string)) -> (option bool) + (ok_or_none_bool (get_bool_result name))) + +(fn get_bool_or_false ((name string)) -> bool + (match (get_bool_result name) + ((ok value) + value) + ((err code) + false))) + +(fn get_bool_or ((name string) (fallback bool)) -> bool + (match (get_bool_result name) + ((ok value) + value) + ((err code) + fallback))) diff --git a/lib/std/fs.slo b/lib/std/fs.slo new file mode 100644 index 0000000..d215a61 --- /dev/null +++ b/lib/std/fs.slo @@ -0,0 +1,176 @@ +(module fs (export read_text read_text_result read_text_option write_text_status write_text_result read_text_or write_text_ok read_i32_result read_i32_option read_i32_or_zero read_i32_or read_u32_result read_u32_option read_u32_or_zero read_u32_or read_i64_result read_i64_option read_i64_or_zero read_i64_or read_u64_result read_u64_option read_u64_or_zero read_u64_or read_f64_result read_f64_option read_f64_or_zero read_f64_or read_bool_result read_bool_option read_bool_or_false read_bool_or)) + +(import std.result (ok_or_none_string ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool)) + +(fn read_text ((path string)) -> string + (std.fs.read_text path)) + +(fn read_text_result ((path string)) -> (result string i32) + (std.fs.read_text_result path)) + +(fn read_text_option ((path string)) -> (option string) + (ok_or_none_string (read_text_result path))) + +(fn write_text_status ((path string) (text string)) -> i32 + (std.fs.write_text path text)) + +(fn write_text_result ((path string) (text string)) -> (result i32 i32) + (std.fs.write_text_result path text)) + +(fn read_text_or ((path string) (fallback string)) -> string + (match (read_text_result path) + ((ok text) + text) + ((err code) + fallback))) + +(fn write_text_ok ((path string) (text string)) -> bool + (match (write_text_result path text) + ((ok status) + true) + ((err code) + false))) + +(fn read_i32_result ((path string)) -> (result i32 i32) + (match (read_text_result path) + ((ok text) + (std.string.parse_i32_result text)) + ((err code) + (err i32 i32 code)))) + +(fn read_i32_option ((path string)) -> (option i32) + (ok_or_none_i32 (read_i32_result path))) + +(fn read_i32_or_zero ((path string)) -> i32 + (match (read_i32_result path) + ((ok value) + value) + ((err code) + 0))) + +(fn read_i32_or ((path string) (fallback i32)) -> i32 + (match (read_i32_result path) + ((ok value) + value) + ((err code) + fallback))) + +(fn read_u32_result ((path string)) -> (result u32 i32) + (match (read_text_result path) + ((ok text) + (std.string.parse_u32_result text)) + ((err code) + (err u32 i32 code)))) + +(fn read_u32_option ((path string)) -> (option u32) + (ok_or_none_u32 (read_u32_result path))) + +(fn read_u32_or_zero ((path string)) -> u32 + (match (read_u32_result path) + ((ok value) + value) + ((err code) + 0u32))) + +(fn read_u32_or ((path string) (fallback u32)) -> u32 + (match (read_u32_result path) + ((ok value) + value) + ((err code) + fallback))) + +(fn read_i64_result ((path string)) -> (result i64 i32) + (match (read_text_result path) + ((ok text) + (std.string.parse_i64_result text)) + ((err code) + (err i64 i32 code)))) + +(fn read_i64_option ((path string)) -> (option i64) + (ok_or_none_i64 (read_i64_result path))) + +(fn read_i64_or_zero ((path string)) -> i64 + (match (read_i64_result path) + ((ok value) + value) + ((err code) + 0i64))) + +(fn read_i64_or ((path string) (fallback i64)) -> i64 + (match (read_i64_result path) + ((ok value) + value) + ((err code) + fallback))) + +(fn read_u64_result ((path string)) -> (result u64 i32) + (match (read_text_result path) + ((ok text) + (std.string.parse_u64_result text)) + ((err code) + (err u64 i32 code)))) + +(fn read_u64_option ((path string)) -> (option u64) + (ok_or_none_u64 (read_u64_result path))) + +(fn read_u64_or_zero ((path string)) -> u64 + (match (read_u64_result path) + ((ok value) + value) + ((err code) + 0u64))) + +(fn read_u64_or ((path string) (fallback u64)) -> u64 + (match (read_u64_result path) + ((ok value) + value) + ((err code) + fallback))) + +(fn read_f64_result ((path string)) -> (result f64 i32) + (match (read_text_result path) + ((ok text) + (std.string.parse_f64_result text)) + ((err code) + (err f64 i32 code)))) + +(fn read_f64_option ((path string)) -> (option f64) + (ok_or_none_f64 (read_f64_result path))) + +(fn read_f64_or_zero ((path string)) -> f64 + (match (read_f64_result path) + ((ok value) + value) + ((err code) + 0.0))) + +(fn read_f64_or ((path string) (fallback f64)) -> f64 + (match (read_f64_result path) + ((ok value) + value) + ((err code) + fallback))) + +(fn read_bool_result ((path string)) -> (result bool i32) + (match (read_text_result path) + ((ok text) + (std.string.parse_bool_result text)) + ((err code) + (err bool i32 code)))) + +(fn read_bool_option ((path string)) -> (option bool) + (ok_or_none_bool (read_bool_result path))) + +(fn read_bool_or_false ((path string)) -> bool + (match (read_bool_result path) + ((ok value) + value) + ((err code) + false))) + +(fn read_bool_or ((path string) (fallback bool)) -> bool + (match (read_bool_result path) + ((ok value) + value) + ((err code) + fallback))) diff --git a/lib/std/io.slo b/lib/std/io.slo new file mode 100644 index 0000000..1fe7e31 --- /dev/null +++ b/lib/std/io.slo @@ -0,0 +1,218 @@ +(module io (export print_i32_zero print_u32_zero print_i64_zero print_u64_zero print_f64_zero print_string_zero print_bool_zero print_i32_value print_u32_value print_i64_value print_u64_value print_f64_value print_string_value print_bool_value read_stdin_result read_stdin_option read_stdin_or read_stdin_i32_result read_stdin_i32_option read_stdin_i32_or_zero read_stdin_i32_or read_stdin_u32_result read_stdin_u32_option read_stdin_u32_or_zero read_stdin_u32_or read_stdin_i64_result read_stdin_i64_option read_stdin_i64_or_zero read_stdin_i64_or read_stdin_u64_result read_stdin_u64_option read_stdin_u64_or_zero read_stdin_u64_or read_stdin_f64_result read_stdin_f64_option read_stdin_f64_or_zero read_stdin_f64_or read_stdin_bool_result read_stdin_bool_option read_stdin_bool_or_false read_stdin_bool_or)) + +(import std.result (ok_or_none_string ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool)) + +(import std.string (parse_i32_result parse_u32_result parse_i64_result parse_u64_result parse_f64_result parse_bool_result)) + +(fn print_i32_zero ((value i32)) -> i32 + (std.io.print_i32 value) + 0) + +(fn print_u32_zero ((value u32)) -> i32 + (std.io.print_u32 value) + 0) + +(fn print_i64_zero ((value i64)) -> i32 + (std.io.print_i64 value) + 0) + +(fn print_u64_zero ((value u64)) -> i32 + (std.io.print_u64 value) + 0) + +(fn print_f64_zero ((value f64)) -> i32 + (std.io.print_f64 value) + 0) + +(fn print_string_zero ((value string)) -> i32 + (std.io.print_string value) + 0) + +(fn print_bool_zero ((value bool)) -> i32 + (std.io.print_bool value) + 0) + +(fn print_i32_value ((value i32)) -> i32 + (std.io.print_i32 value) + value) + +(fn print_u32_value ((value u32)) -> u32 + (std.io.print_u32 value) + value) + +(fn print_i64_value ((value i64)) -> i64 + (std.io.print_i64 value) + value) + +(fn print_u64_value ((value u64)) -> u64 + (std.io.print_u64 value) + value) + +(fn print_f64_value ((value f64)) -> f64 + (std.io.print_f64 value) + value) + +(fn print_string_value ((value string)) -> string + (std.io.print_string value) + value) + +(fn print_bool_value ((value bool)) -> bool + (std.io.print_bool value) + value) + +(fn read_stdin_result () -> (result string i32) + (std.io.read_stdin_result)) + +(fn read_stdin_option () -> (option string) + (ok_or_none_string (read_stdin_result))) + +(fn read_stdin_or ((fallback string)) -> string + (match (read_stdin_result) + ((ok payload) + payload) + ((err code) + fallback))) + +(fn read_stdin_i32_result () -> (result i32 i32) + (match (read_stdin_result) + ((ok text) + (parse_i32_result text)) + ((err code) + (err i32 i32 code)))) + +(fn read_stdin_i32_option () -> (option i32) + (ok_or_none_i32 (read_stdin_i32_result))) + +(fn read_stdin_i32_or_zero () -> i32 + (match (read_stdin_i32_result) + ((ok value) + value) + ((err code) + 0))) + +(fn read_stdin_i32_or ((fallback i32)) -> i32 + (match (read_stdin_i32_result) + ((ok value) + value) + ((err code) + fallback))) + +(fn read_stdin_u32_result () -> (result u32 i32) + (match (read_stdin_result) + ((ok text) + (parse_u32_result text)) + ((err code) + (err u32 i32 code)))) + +(fn read_stdin_u32_option () -> (option u32) + (ok_or_none_u32 (read_stdin_u32_result))) + +(fn read_stdin_u32_or_zero () -> u32 + (match (read_stdin_u32_result) + ((ok value) + value) + ((err code) + 0u32))) + +(fn read_stdin_u32_or ((fallback u32)) -> u32 + (match (read_stdin_u32_result) + ((ok value) + value) + ((err code) + fallback))) + +(fn read_stdin_i64_result () -> (result i64 i32) + (match (read_stdin_result) + ((ok text) + (parse_i64_result text)) + ((err code) + (err i64 i32 code)))) + +(fn read_stdin_i64_option () -> (option i64) + (ok_or_none_i64 (read_stdin_i64_result))) + +(fn read_stdin_i64_or_zero () -> i64 + (match (read_stdin_i64_result) + ((ok value) + value) + ((err code) + 0i64))) + +(fn read_stdin_i64_or ((fallback i64)) -> i64 + (match (read_stdin_i64_result) + ((ok value) + value) + ((err code) + fallback))) + +(fn read_stdin_u64_result () -> (result u64 i32) + (match (read_stdin_result) + ((ok text) + (parse_u64_result text)) + ((err code) + (err u64 i32 code)))) + +(fn read_stdin_u64_option () -> (option u64) + (ok_or_none_u64 (read_stdin_u64_result))) + +(fn read_stdin_u64_or_zero () -> u64 + (match (read_stdin_u64_result) + ((ok value) + value) + ((err code) + 0u64))) + +(fn read_stdin_u64_or ((fallback u64)) -> u64 + (match (read_stdin_u64_result) + ((ok value) + value) + ((err code) + fallback))) + +(fn read_stdin_f64_result () -> (result f64 i32) + (match (read_stdin_result) + ((ok text) + (parse_f64_result text)) + ((err code) + (err f64 i32 code)))) + +(fn read_stdin_f64_option () -> (option f64) + (ok_or_none_f64 (read_stdin_f64_result))) + +(fn read_stdin_f64_or_zero () -> f64 + (match (read_stdin_f64_result) + ((ok value) + value) + ((err code) + 0.0))) + +(fn read_stdin_f64_or ((fallback f64)) -> f64 + (match (read_stdin_f64_result) + ((ok value) + value) + ((err code) + fallback))) + +(fn read_stdin_bool_result () -> (result bool i32) + (match (read_stdin_result) + ((ok text) + (parse_bool_result text)) + ((err code) + (err bool i32 code)))) + +(fn read_stdin_bool_option () -> (option bool) + (ok_or_none_bool (read_stdin_bool_result))) + +(fn read_stdin_bool_or_false () -> bool + (match (read_stdin_bool_result) + ((ok value) + value) + ((err code) + false))) + +(fn read_stdin_bool_or ((fallback bool)) -> bool + (match (read_stdin_bool_result) + ((ok value) + value) + ((err code) + fallback))) diff --git a/lib/std/math.slo b/lib/std/math.slo new file mode 100644 index 0000000..4e49595 --- /dev/null +++ b/lib/std/math.slo @@ -0,0 +1,164 @@ +(module math (export abs_i32 neg_i32 rem_i32 bit_and_i32 bit_or_i32 bit_xor_i32 is_even_i32 is_odd_i32 min_i32 max_i32 clamp_i32 square_i32 cube_i32 is_zero_i32 is_positive_i32 is_negative_i32 in_range_i32 abs_i64 neg_i64 rem_i64 bit_and_i64 bit_or_i64 bit_xor_i64 is_even_i64 is_odd_i64 min_i64 max_i64 clamp_i64 square_i64 cube_i64 is_zero_i64 is_positive_i64 is_negative_i64 in_range_i64 abs_f64 neg_f64 min_f64 max_f64 clamp_f64 square_f64 cube_f64 is_zero_f64 is_positive_f64 is_negative_f64 in_range_f64)) + +(fn abs_i32 ((value i32)) -> i32 + (if (< value 0) + (- 0 value) + value)) + +(fn neg_i32 ((value i32)) -> i32 + (- 0 value)) + +(fn rem_i32 ((left i32) (right i32)) -> i32 + (% left right)) + +(fn bit_and_i32 ((left i32) (right i32)) -> i32 + (bit_and left right)) + +(fn bit_or_i32 ((left i32) (right i32)) -> i32 + (bit_or left right)) + +(fn bit_xor_i32 ((left i32) (right i32)) -> i32 + (bit_xor left right)) + +(fn is_even_i32 ((value i32)) -> bool + (= (rem_i32 value 2) 0)) + +(fn is_odd_i32 ((value i32)) -> bool + (if (= (rem_i32 value 2) 0) + false + true)) + +(fn min_i32 ((left i32) (right i32)) -> i32 + (if (< left right) + left + right)) + +(fn max_i32 ((left i32) (right i32)) -> i32 + (if (> left right) + left + right)) + +(fn clamp_i32 ((value i32) (low i32) (high i32)) -> i32 + (max_i32 low (min_i32 value high))) + +(fn square_i32 ((value i32)) -> i32 + (* value value)) + +(fn cube_i32 ((value i32)) -> i32 + (* (square_i32 value) value)) + +(fn is_zero_i32 ((value i32)) -> bool + (= value 0)) + +(fn is_positive_i32 ((value i32)) -> bool + (> value 0)) + +(fn is_negative_i32 ((value i32)) -> bool + (< value 0)) + +(fn in_range_i32 ((value i32) (low i32) (high i32)) -> bool + (if (>= value low) + (<= value high) + false)) + +(fn abs_i64 ((value i64)) -> i64 + (if (< value 0i64) + (- 0i64 value) + value)) + +(fn neg_i64 ((value i64)) -> i64 + (- 0i64 value)) + +(fn rem_i64 ((left i64) (right i64)) -> i64 + (% left right)) + +(fn bit_and_i64 ((left i64) (right i64)) -> i64 + (bit_and left right)) + +(fn bit_or_i64 ((left i64) (right i64)) -> i64 + (bit_or left right)) + +(fn bit_xor_i64 ((left i64) (right i64)) -> i64 + (bit_xor left right)) + +(fn is_even_i64 ((value i64)) -> bool + (= (rem_i64 value 2i64) 0i64)) + +(fn is_odd_i64 ((value i64)) -> bool + (if (= (rem_i64 value 2i64) 0i64) + false + true)) + +(fn min_i64 ((left i64) (right i64)) -> i64 + (if (< left right) + left + right)) + +(fn max_i64 ((left i64) (right i64)) -> i64 + (if (> left right) + left + right)) + +(fn clamp_i64 ((value i64) (low i64) (high i64)) -> i64 + (max_i64 low (min_i64 value high))) + +(fn square_i64 ((value i64)) -> i64 + (* value value)) + +(fn cube_i64 ((value i64)) -> i64 + (* (square_i64 value) value)) + +(fn is_zero_i64 ((value i64)) -> bool + (= value 0i64)) + +(fn is_positive_i64 ((value i64)) -> bool + (> value 0i64)) + +(fn is_negative_i64 ((value i64)) -> bool + (< value 0i64)) + +(fn in_range_i64 ((value i64) (low i64) (high i64)) -> bool + (if (>= value low) + (<= value high) + false)) + +(fn abs_f64 ((value f64)) -> f64 + (if (< value 0.0) + (- 0.0 value) + value)) + +(fn neg_f64 ((value f64)) -> f64 + (- 0.0 value)) + +(fn min_f64 ((left f64) (right f64)) -> f64 + (if (< left right) + left + right)) + +(fn max_f64 ((left f64) (right f64)) -> f64 + (if (> left right) + left + right)) + +(fn clamp_f64 ((value f64) (low f64) (high f64)) -> f64 + (max_f64 low (min_f64 value high))) + +(fn square_f64 ((value f64)) -> f64 + (* value value)) + +(fn cube_f64 ((value f64)) -> f64 + (* (square_f64 value) value)) + +(fn is_zero_f64 ((value f64)) -> bool + (= value 0.0)) + +(fn is_positive_f64 ((value f64)) -> bool + (> value 0.0)) + +(fn is_negative_f64 ((value f64)) -> bool + (< value 0.0)) + +(fn in_range_f64 ((value f64) (low f64) (high f64)) -> bool + (if (>= value low) + (<= value high) + false)) diff --git a/lib/std/num.slo b/lib/std/num.slo new file mode 100644 index 0000000..d1ab07f --- /dev/null +++ b/lib/std/num.slo @@ -0,0 +1,55 @@ +(module num (export i32_to_i64 i32_to_f64 i64_to_f64 i64_to_i32_result f64_to_i32_result f64_to_i64_result i32_to_string u32_to_string i64_to_string u64_to_string f64_to_string i64_to_i32_or f64_to_i32_or f64_to_i64_or)) + +(fn i32_to_i64 ((value i32)) -> i64 + (std.num.i32_to_i64 value)) + +(fn i32_to_f64 ((value i32)) -> f64 + (std.num.i32_to_f64 value)) + +(fn i64_to_f64 ((value i64)) -> f64 + (std.num.i64_to_f64 value)) + +(fn i64_to_i32_result ((value i64)) -> (result i32 i32) + (std.num.i64_to_i32_result value)) + +(fn f64_to_i32_result ((value f64)) -> (result i32 i32) + (std.num.f64_to_i32_result value)) + +(fn f64_to_i64_result ((value f64)) -> (result i64 i32) + (std.num.f64_to_i64_result value)) + +(fn i32_to_string ((value i32)) -> string + (std.num.i32_to_string value)) + +(fn u32_to_string ((value u32)) -> string + (std.num.u32_to_string value)) + +(fn i64_to_string ((value i64)) -> string + (std.num.i64_to_string value)) + +(fn u64_to_string ((value u64)) -> string + (std.num.u64_to_string value)) + +(fn f64_to_string ((value f64)) -> string + (std.num.f64_to_string value)) + +(fn i64_to_i32_or ((value i64) (fallback i32)) -> i32 + (match (i64_to_i32_result value) + ((ok payload) + payload) + ((err code) + fallback))) + +(fn f64_to_i32_or ((value f64) (fallback i32)) -> i32 + (match (f64_to_i32_result value) + ((ok payload) + payload) + ((err code) + fallback))) + +(fn f64_to_i64_or ((value f64) (fallback i64)) -> i64 + (match (f64_to_i64_result value) + ((ok payload) + payload) + ((err code) + fallback))) diff --git a/lib/std/option.slo b/lib/std/option.slo new file mode 100644 index 0000000..1076cfd --- /dev/null +++ b/lib/std/option.slo @@ -0,0 +1,176 @@ +(module option (export some_i32 none_i32 is_some_i32 is_none_i32 unwrap_some_i32 unwrap_or_i32 some_or_err_i32 some_u32 none_u32 is_some_u32 is_none_u32 unwrap_some_u32 unwrap_or_u32 some_or_err_u32 some_i64 none_i64 is_some_i64 is_none_i64 unwrap_some_i64 unwrap_or_i64 some_or_err_i64 some_u64 none_u64 is_some_u64 is_none_u64 unwrap_some_u64 unwrap_or_u64 some_or_err_u64 some_f64 none_f64 is_some_f64 is_none_f64 unwrap_some_f64 unwrap_or_f64 some_or_err_f64 some_bool none_bool is_some_bool is_none_bool unwrap_some_bool unwrap_or_bool some_or_err_bool some_string none_string is_some_string is_none_string unwrap_some_string unwrap_or_string some_or_err_string)) + +(fn some_i32 ((value i32)) -> (option i32) + (some i32 value)) + +(fn none_i32 () -> (option i32) + (none i32)) + +(fn is_some_i32 ((value (option i32))) -> bool + (is_some value)) + +(fn is_none_i32 ((value (option i32))) -> bool + (is_none value)) + +(fn unwrap_some_i32 ((value (option i32))) -> i32 + (unwrap_some value)) + +(fn unwrap_or_i32 ((value (option i32)) (fallback i32)) -> i32 + (if (is_some value) + (unwrap_some value) + fallback)) + +(fn some_or_err_i32 ((value (option i32)) (err_code i32)) -> (result i32 i32) + (if (is_some value) + (ok i32 i32 (unwrap_some value)) + (err i32 i32 err_code))) + +(fn some_u32 ((value u32)) -> (option u32) + (some u32 value)) + +(fn none_u32 () -> (option u32) + (none u32)) + +(fn is_some_u32 ((value (option u32))) -> bool + (is_some value)) + +(fn is_none_u32 ((value (option u32))) -> bool + (is_none value)) + +(fn unwrap_some_u32 ((value (option u32))) -> u32 + (unwrap_some value)) + +(fn unwrap_or_u32 ((value (option u32)) (fallback u32)) -> u32 + (if (is_some value) + (unwrap_some value) + fallback)) + +(fn some_or_err_u32 ((value (option u32)) (err_code i32)) -> (result u32 i32) + (if (is_some value) + (ok u32 i32 (unwrap_some value)) + (err u32 i32 err_code))) + +(fn some_i64 ((value i64)) -> (option i64) + (some i64 value)) + +(fn none_i64 () -> (option i64) + (none i64)) + +(fn is_some_i64 ((value (option i64))) -> bool + (is_some value)) + +(fn is_none_i64 ((value (option i64))) -> bool + (is_none value)) + +(fn unwrap_some_i64 ((value (option i64))) -> i64 + (unwrap_some value)) + +(fn unwrap_or_i64 ((value (option i64)) (fallback i64)) -> i64 + (if (is_some value) + (unwrap_some value) + fallback)) + +(fn some_or_err_i64 ((value (option i64)) (err_code i32)) -> (result i64 i32) + (if (is_some value) + (ok i64 i32 (unwrap_some value)) + (err i64 i32 err_code))) + +(fn some_u64 ((value u64)) -> (option u64) + (some u64 value)) + +(fn none_u64 () -> (option u64) + (none u64)) + +(fn is_some_u64 ((value (option u64))) -> bool + (is_some value)) + +(fn is_none_u64 ((value (option u64))) -> bool + (is_none value)) + +(fn unwrap_some_u64 ((value (option u64))) -> u64 + (unwrap_some value)) + +(fn unwrap_or_u64 ((value (option u64)) (fallback u64)) -> u64 + (if (is_some value) + (unwrap_some value) + fallback)) + +(fn some_or_err_u64 ((value (option u64)) (err_code i32)) -> (result u64 i32) + (if (is_some value) + (ok u64 i32 (unwrap_some value)) + (err u64 i32 err_code))) + +(fn some_f64 ((value f64)) -> (option f64) + (some f64 value)) + +(fn none_f64 () -> (option f64) + (none f64)) + +(fn is_some_f64 ((value (option f64))) -> bool + (is_some value)) + +(fn is_none_f64 ((value (option f64))) -> bool + (is_none value)) + +(fn unwrap_some_f64 ((value (option f64))) -> f64 + (unwrap_some value)) + +(fn unwrap_or_f64 ((value (option f64)) (fallback f64)) -> f64 + (if (is_some value) + (unwrap_some value) + fallback)) + +(fn some_or_err_f64 ((value (option f64)) (err_code i32)) -> (result f64 i32) + (if (is_some value) + (ok f64 i32 (unwrap_some value)) + (err f64 i32 err_code))) + +(fn some_bool ((value bool)) -> (option bool) + (some bool value)) + +(fn none_bool () -> (option bool) + (none bool)) + +(fn is_some_bool ((value (option bool))) -> bool + (is_some value)) + +(fn is_none_bool ((value (option bool))) -> bool + (is_none value)) + +(fn unwrap_some_bool ((value (option bool))) -> bool + (unwrap_some value)) + +(fn unwrap_or_bool ((value (option bool)) (fallback bool)) -> bool + (if (is_some value) + (unwrap_some value) + fallback)) + +(fn some_or_err_bool ((value (option bool)) (err_code i32)) -> (result bool i32) + (if (is_some value) + (ok bool i32 (unwrap_some value)) + (err bool i32 err_code))) + +(fn some_string ((value string)) -> (option string) + (some string value)) + +(fn none_string () -> (option string) + (none string)) + +(fn is_some_string ((value (option string))) -> bool + (is_some value)) + +(fn is_none_string ((value (option string))) -> bool + (is_none value)) + +(fn unwrap_some_string ((value (option string))) -> string + (unwrap_some value)) + +(fn unwrap_or_string ((value (option string)) (fallback string)) -> string + (if (is_some value) + (unwrap_some value) + fallback)) + +(fn some_or_err_string ((value (option string)) (err_code i32)) -> (result string i32) + (if (is_some value) + (ok string i32 (unwrap_some value)) + (err string i32 err_code))) diff --git a/lib/std/process.slo b/lib/std/process.slo new file mode 100644 index 0000000..881c896 --- /dev/null +++ b/lib/std/process.slo @@ -0,0 +1,174 @@ +(module process (export argc arg arg_result arg_option has_arg arg_or arg_or_empty arg_i32_result arg_i32_option arg_i32_or_zero arg_i32_or arg_u32_result arg_u32_option arg_u32_or_zero arg_u32_or arg_i64_result arg_i64_option arg_i64_or_zero arg_i64_or arg_u64_result arg_u64_option arg_u64_or_zero arg_u64_or arg_f64_result arg_f64_option arg_f64_or_zero arg_f64_or arg_bool_result arg_bool_option arg_bool_or_false arg_bool_or)) + +(import std.result (ok_or_none_string ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool)) + +(fn argc () -> i32 + (std.process.argc)) + +(fn arg ((index i32)) -> string + (std.process.arg index)) + +(fn arg_result ((index i32)) -> (result string i32) + (std.process.arg_result index)) + +(fn arg_option ((index i32)) -> (option string) + (ok_or_none_string (arg_result index))) + +(fn has_arg ((index i32)) -> bool + (if (< index 0) + false + (< index (std.process.argc)))) + +(fn arg_or ((index i32) (fallback string)) -> string + (match (arg_result index) + ((ok payload) + payload) + ((err code) + fallback))) + +(fn arg_or_empty ((index i32)) -> string + (arg_or index "")) + +(fn arg_i32_result ((index i32)) -> (result i32 i32) + (match (arg_result index) + ((ok text) + (std.string.parse_i32_result text)) + ((err code) + (err i32 i32 code)))) + +(fn arg_i32_option ((index i32)) -> (option i32) + (ok_or_none_i32 (arg_i32_result index))) + +(fn arg_i32_or_zero ((index i32)) -> i32 + (match (arg_i32_result index) + ((ok value) + value) + ((err code) + 0))) + +(fn arg_i32_or ((index i32) (fallback i32)) -> i32 + (match (arg_i32_result index) + ((ok value) + value) + ((err code) + fallback))) + +(fn arg_u32_result ((index i32)) -> (result u32 i32) + (match (arg_result index) + ((ok text) + (std.string.parse_u32_result text)) + ((err code) + (err u32 i32 code)))) + +(fn arg_u32_option ((index i32)) -> (option u32) + (ok_or_none_u32 (arg_u32_result index))) + +(fn arg_u32_or_zero ((index i32)) -> u32 + (match (arg_u32_result index) + ((ok value) + value) + ((err code) + 0u32))) + +(fn arg_u32_or ((index i32) (fallback u32)) -> u32 + (match (arg_u32_result index) + ((ok value) + value) + ((err code) + fallback))) + +(fn arg_i64_result ((index i32)) -> (result i64 i32) + (match (arg_result index) + ((ok text) + (std.string.parse_i64_result text)) + ((err code) + (err i64 i32 code)))) + +(fn arg_i64_option ((index i32)) -> (option i64) + (ok_or_none_i64 (arg_i64_result index))) + +(fn arg_i64_or_zero ((index i32)) -> i64 + (match (arg_i64_result index) + ((ok value) + value) + ((err code) + 0i64))) + +(fn arg_i64_or ((index i32) (fallback i64)) -> i64 + (match (arg_i64_result index) + ((ok value) + value) + ((err code) + fallback))) + +(fn arg_u64_result ((index i32)) -> (result u64 i32) + (match (arg_result index) + ((ok text) + (std.string.parse_u64_result text)) + ((err code) + (err u64 i32 code)))) + +(fn arg_u64_option ((index i32)) -> (option u64) + (ok_or_none_u64 (arg_u64_result index))) + +(fn arg_u64_or_zero ((index i32)) -> u64 + (match (arg_u64_result index) + ((ok value) + value) + ((err code) + 0u64))) + +(fn arg_u64_or ((index i32) (fallback u64)) -> u64 + (match (arg_u64_result index) + ((ok value) + value) + ((err code) + fallback))) + +(fn arg_f64_result ((index i32)) -> (result f64 i32) + (match (arg_result index) + ((ok text) + (std.string.parse_f64_result text)) + ((err code) + (err f64 i32 code)))) + +(fn arg_f64_option ((index i32)) -> (option f64) + (ok_or_none_f64 (arg_f64_result index))) + +(fn arg_f64_or_zero ((index i32)) -> f64 + (match (arg_f64_result index) + ((ok value) + value) + ((err code) + 0.0))) + +(fn arg_f64_or ((index i32) (fallback f64)) -> f64 + (match (arg_f64_result index) + ((ok value) + value) + ((err code) + fallback))) + +(fn arg_bool_result ((index i32)) -> (result bool i32) + (match (arg_result index) + ((ok text) + (std.string.parse_bool_result text)) + ((err code) + (err bool i32 code)))) + +(fn arg_bool_option ((index i32)) -> (option bool) + (ok_or_none_bool (arg_bool_result index))) + +(fn arg_bool_or_false ((index i32)) -> bool + (match (arg_bool_result index) + ((ok value) + value) + ((err code) + false))) + +(fn arg_bool_or ((index i32) (fallback bool)) -> bool + (match (arg_bool_result index) + ((ok value) + value) + ((err code) + fallback))) diff --git a/lib/std/random.slo b/lib/std/random.slo new file mode 100644 index 0000000..3f59dd4 --- /dev/null +++ b/lib/std/random.slo @@ -0,0 +1,7 @@ +(module random (export random_i32 random_i32_non_negative)) + +(fn random_i32 () -> i32 + (std.random.i32)) + +(fn random_i32_non_negative () -> bool + (>= (random_i32) 0)) diff --git a/lib/std/result.slo b/lib/std/result.slo new file mode 100644 index 0000000..4a7b77b --- /dev/null +++ b/lib/std/result.slo @@ -0,0 +1,199 @@ +(module result (export ok_i32 err_i32 is_ok_i32 is_err_i32 unwrap_ok_i32 unwrap_err_i32 unwrap_or_i32 ok_or_none_i32 ok_u32 err_u32 is_ok_u32 is_err_u32 unwrap_ok_u32 unwrap_err_u32 unwrap_or_u32 ok_or_none_u32 ok_i64 err_i64 is_ok_i64 is_err_i64 unwrap_ok_i64 unwrap_err_i64 unwrap_or_i64 ok_or_none_i64 ok_u64 err_u64 is_ok_u64 is_err_u64 unwrap_ok_u64 unwrap_err_u64 unwrap_or_u64 ok_or_none_u64 ok_string err_string is_ok_string is_err_string unwrap_ok_string unwrap_err_string unwrap_or_string ok_or_none_string ok_f64 err_f64 is_ok_f64 is_err_f64 unwrap_ok_f64 unwrap_err_f64 unwrap_or_f64 ok_or_none_f64 ok_bool err_bool is_ok_bool is_err_bool unwrap_ok_bool unwrap_err_bool unwrap_or_bool ok_or_none_bool)) + +(fn ok_i32 ((value i32)) -> (result i32 i32) + (ok i32 i32 value)) + +(fn err_i32 ((code i32)) -> (result i32 i32) + (err i32 i32 code)) + +(fn is_ok_i32 ((value (result i32 i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_i32 ((value (result i32 i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_i32 ((value (result i32 i32))) -> i32 + (std.result.unwrap_ok value)) + +(fn unwrap_err_i32 ((value (result i32 i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_i32 ((value (result i32 i32)) (fallback i32)) -> i32 + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + fallback)) + +(fn ok_or_none_i32 ((value (result i32 i32))) -> (option i32) + (if (std.result.is_ok value) + (some i32 (std.result.unwrap_ok value)) + (none i32))) + +(fn ok_u32 ((value u32)) -> (result u32 i32) + (ok u32 i32 value)) + +(fn err_u32 ((code i32)) -> (result u32 i32) + (err u32 i32 code)) + +(fn is_ok_u32 ((value (result u32 i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_u32 ((value (result u32 i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_u32 ((value (result u32 i32))) -> u32 + (std.result.unwrap_ok value)) + +(fn unwrap_err_u32 ((value (result u32 i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_u32 ((value (result u32 i32)) (fallback u32)) -> u32 + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + fallback)) + +(fn ok_or_none_u32 ((value (result u32 i32))) -> (option u32) + (if (std.result.is_ok value) + (some u32 (std.result.unwrap_ok value)) + (none u32))) + +(fn ok_i64 ((value i64)) -> (result i64 i32) + (ok i64 i32 value)) + +(fn err_i64 ((code i32)) -> (result i64 i32) + (err i64 i32 code)) + +(fn is_ok_i64 ((value (result i64 i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_i64 ((value (result i64 i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_i64 ((value (result i64 i32))) -> i64 + (std.result.unwrap_ok value)) + +(fn unwrap_err_i64 ((value (result i64 i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_i64 ((value (result i64 i32)) (fallback i64)) -> i64 + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + fallback)) + +(fn ok_or_none_i64 ((value (result i64 i32))) -> (option i64) + (if (std.result.is_ok value) + (some i64 (std.result.unwrap_ok value)) + (none i64))) + +(fn ok_u64 ((value u64)) -> (result u64 i32) + (ok u64 i32 value)) + +(fn err_u64 ((code i32)) -> (result u64 i32) + (err u64 i32 code)) + +(fn is_ok_u64 ((value (result u64 i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_u64 ((value (result u64 i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_u64 ((value (result u64 i32))) -> u64 + (std.result.unwrap_ok value)) + +(fn unwrap_err_u64 ((value (result u64 i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_u64 ((value (result u64 i32)) (fallback u64)) -> u64 + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + fallback)) + +(fn ok_or_none_u64 ((value (result u64 i32))) -> (option u64) + (if (std.result.is_ok value) + (some u64 (std.result.unwrap_ok value)) + (none u64))) + +(fn ok_string ((value string)) -> (result string i32) + (ok string i32 value)) + +(fn err_string ((code i32)) -> (result string i32) + (err string i32 code)) + +(fn is_ok_string ((value (result string i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_string ((value (result string i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_string ((value (result string i32))) -> string + (std.result.unwrap_ok value)) + +(fn unwrap_err_string ((value (result string i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_string ((value (result string i32)) (fallback string)) -> string + (match value + ((ok payload) + payload) + ((err code) + fallback))) + +(fn ok_or_none_string ((value (result string i32))) -> (option string) + (if (std.result.is_ok value) + (some string (std.result.unwrap_ok value)) + (none string))) + +(fn ok_f64 ((value f64)) -> (result f64 i32) + (ok f64 i32 value)) + +(fn err_f64 ((code i32)) -> (result f64 i32) + (err f64 i32 code)) + +(fn is_ok_f64 ((value (result f64 i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_f64 ((value (result f64 i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_f64 ((value (result f64 i32))) -> f64 + (std.result.unwrap_ok value)) + +(fn unwrap_err_f64 ((value (result f64 i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_f64 ((value (result f64 i32)) (fallback f64)) -> f64 + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + fallback)) + +(fn ok_or_none_f64 ((value (result f64 i32))) -> (option f64) + (if (std.result.is_ok value) + (some f64 (std.result.unwrap_ok value)) + (none f64))) + +(fn ok_bool ((value bool)) -> (result bool i32) + (ok bool i32 value)) + +(fn err_bool ((code i32)) -> (result bool i32) + (err bool i32 code)) + +(fn is_ok_bool ((value (result bool i32))) -> bool + (std.result.is_ok value)) + +(fn is_err_bool ((value (result bool i32))) -> bool + (std.result.is_err value)) + +(fn unwrap_ok_bool ((value (result bool i32))) -> bool + (std.result.unwrap_ok value)) + +(fn unwrap_err_bool ((value (result bool i32))) -> i32 + (std.result.unwrap_err value)) + +(fn unwrap_or_bool ((value (result bool i32)) (fallback bool)) -> bool + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + fallback)) + +(fn ok_or_none_bool ((value (result bool i32))) -> (option bool) + (if (std.result.is_ok value) + (some bool (std.result.unwrap_ok value)) + (none bool))) diff --git a/lib/std/string.slo b/lib/std/string.slo new file mode 100644 index 0000000..f5245f2 --- /dev/null +++ b/lib/std/string.slo @@ -0,0 +1,129 @@ +(module string (export len concat parse_i32_result parse_i32_option parse_u32_result parse_u32_option parse_i64_result parse_i64_option parse_u64_result parse_u64_option parse_f64_result parse_f64_option parse_bool_result parse_bool_option parse_i32_or_zero parse_u32_or_zero parse_i64_or_zero parse_u64_or_zero parse_f64_or_zero parse_bool_or_false parse_i32_or parse_u32_or parse_i64_or parse_u64_or parse_f64_or parse_bool_or)) + +(import std.result (ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool)) + +(fn len ((value string)) -> i32 + (std.string.len value)) + +(fn concat ((left string) (right string)) -> string + (std.string.concat left right)) + +(fn parse_i32_result ((value string)) -> (result i32 i32) + (std.string.parse_i32_result value)) + +(fn parse_i32_option ((value string)) -> (option i32) + (ok_or_none_i32 (parse_i32_result value))) + +(fn parse_u32_result ((value string)) -> (result u32 i32) + (std.string.parse_u32_result value)) + +(fn parse_u32_option ((value string)) -> (option u32) + (ok_or_none_u32 (parse_u32_result value))) + +(fn parse_i64_result ((value string)) -> (result i64 i32) + (std.string.parse_i64_result value)) + +(fn parse_i64_option ((value string)) -> (option i64) + (ok_or_none_i64 (parse_i64_result value))) + +(fn parse_u64_result ((value string)) -> (result u64 i32) + (std.string.parse_u64_result value)) + +(fn parse_u64_option ((value string)) -> (option u64) + (ok_or_none_u64 (parse_u64_result value))) + +(fn parse_f64_result ((value string)) -> (result f64 i32) + (std.string.parse_f64_result value)) + +(fn parse_f64_option ((value string)) -> (option f64) + (ok_or_none_f64 (parse_f64_result value))) + +(fn parse_bool_result ((value string)) -> (result bool i32) + (std.string.parse_bool_result value)) + +(fn parse_bool_option ((value string)) -> (option bool) + (ok_or_none_bool (parse_bool_result value))) + +(fn parse_i32_or_zero ((value string)) -> i32 + (match (parse_i32_result value) + ((ok payload) + payload) + ((err code) + 0))) + +(fn parse_u32_or_zero ((value string)) -> u32 + (match (parse_u32_result value) + ((ok payload) + payload) + ((err code) + 0u32))) + +(fn parse_i64_or_zero ((value string)) -> i64 + (match (parse_i64_result value) + ((ok payload) + payload) + ((err code) + 0i64))) + +(fn parse_u64_or_zero ((value string)) -> u64 + (match (parse_u64_result value) + ((ok payload) + payload) + ((err code) + 0u64))) + +(fn parse_f64_or_zero ((value string)) -> f64 + (match (parse_f64_result value) + ((ok payload) + payload) + ((err code) + 0.0))) + +(fn parse_bool_or_false ((value string)) -> bool + (match (parse_bool_result value) + ((ok payload) + payload) + ((err code) + false))) + +(fn parse_i32_or ((value string) (fallback i32)) -> i32 + (match (parse_i32_result value) + ((ok payload) + payload) + ((err code) + fallback))) + +(fn parse_u32_or ((value string) (fallback u32)) -> u32 + (match (parse_u32_result value) + ((ok payload) + payload) + ((err code) + fallback))) + +(fn parse_i64_or ((value string) (fallback i64)) -> i64 + (match (parse_i64_result value) + ((ok payload) + payload) + ((err code) + fallback))) + +(fn parse_u64_or ((value string) (fallback u64)) -> u64 + (match (parse_u64_result value) + ((ok payload) + payload) + ((err code) + fallback))) + +(fn parse_f64_or ((value string) (fallback f64)) -> f64 + (match (parse_f64_result value) + ((ok payload) + payload) + ((err code) + fallback))) + +(fn parse_bool_or ((value string) (fallback bool)) -> bool + (match (parse_bool_result value) + ((ok payload) + payload) + ((err code) + fallback))) diff --git a/lib/std/time.slo b/lib/std/time.slo new file mode 100644 index 0000000..e261544 --- /dev/null +++ b/lib/std/time.slo @@ -0,0 +1,8 @@ +(module time (export monotonic_ms sleep_ms_zero)) + +(fn monotonic_ms () -> i32 + (std.time.monotonic_ms)) + +(fn sleep_ms_zero () -> i32 + (std.time.sleep_ms 0) + 0) diff --git a/lib/std/vec_bool.slo b/lib/std/vec_bool.slo new file mode 100644 index 0000000..bc08933 --- /dev/null +++ b/lib/std/vec_bool.slo @@ -0,0 +1,228 @@ +(module vec_bool (export empty append len at singleton append2 append3 pair triple is_empty index_or first_or last_or index_option first_option last_option index_of_option last_index_of_option contains count_of concat take starts_with without_prefix ends_with without_suffix drop reverse subvec insert_at insert_range replace_at replace_range remove_at remove_range)) + +(fn empty () -> (vec bool) + (std.vec.bool.empty)) + +(fn append ((values (vec bool)) (value bool)) -> (vec bool) + (std.vec.bool.append values value)) + +(fn len ((values (vec bool))) -> i32 + (std.vec.bool.len values)) + +(fn at ((values (vec bool)) (position i32)) -> bool + (std.vec.bool.index values position)) + +(fn singleton ((value bool)) -> (vec bool) + (append (empty) value)) + +(fn append2 ((values (vec bool)) (first bool) (second bool)) -> (vec bool) + (append (append values first) second)) + +(fn append3 ((values (vec bool)) (first bool) (second bool) (third bool)) -> (vec bool) + (append (append2 values first second) third)) + +(fn pair ((first bool) (second bool)) -> (vec bool) + (append2 (empty) first second)) + +(fn triple ((first bool) (second bool) (third bool)) -> (vec bool) + (append3 (empty) first second third)) + +(fn is_empty ((values (vec bool))) -> bool + (= (len values) 0)) + +(fn index_or ((values (vec bool)) (position i32) (fallback bool)) -> bool + (if (< position 0) + fallback + (if (< position (len values)) + (at values position) + fallback))) + +(fn first_or ((values (vec bool)) (fallback bool)) -> bool + (index_or values 0 fallback)) + +(fn last_or ((values (vec bool)) (fallback bool)) -> bool + (if (is_empty values) + fallback + (at values (- (len values) 1)))) + +(fn index_option ((values (vec bool)) (position i32)) -> (option bool) + (if (< position 0) + (none bool) + (if (< position (len values)) + (some bool (at values position)) + (none bool)))) + +(fn first_option ((values (vec bool))) -> (option bool) + (index_option values 0)) + +(fn last_option ((values (vec bool))) -> (option bool) + (if (is_empty values) + (none bool) + (some bool (at values (- (len values) 1))))) + +(fn index_of_option_loop ((values (vec bool)) (target bool) (position i32) (values_len i32)) -> (option i32) + (if (< position values_len) + (if (= (at values position) target) + (some i32 position) + (index_of_option_loop values target (+ position 1) values_len)) + (none i32))) + +(fn index_of_option ((values (vec bool)) (target bool)) -> (option i32) + (index_of_option_loop values target 0 (len values))) + +(fn last_index_of_option_loop ((values (vec bool)) (target bool) (position i32) (values_len i32) (found_position (option i32))) -> (option i32) + (if (< position values_len) + (last_index_of_option_loop values target (+ position 1) values_len (if (= (at values position) target) + (some i32 position) + found_position)) + found_position)) + +(fn last_index_of_option ((values (vec bool)) (target bool)) -> (option i32) + (last_index_of_option_loop values target 0 (len values) (none i32))) + +(fn contains_loop ((values (vec bool)) (target bool) (position i32) (values_len i32)) -> bool + (if (< position values_len) + (if (= (at values position) target) + true + (contains_loop values target (+ position 1) values_len)) + false)) + +(fn contains ((values (vec bool)) (target bool)) -> bool + (contains_loop values target 0 (len values))) + +(fn count_of_loop ((values (vec bool)) (target bool) (position i32) (values_len i32) (hits i32)) -> i32 + (if (< position values_len) + (count_of_loop values target (+ position 1) values_len (+ hits (if (= (at values position) target) + 1 + 0))) + hits)) + +(fn count_of ((values (vec bool)) (target bool)) -> i32 + (count_of_loop values target 0 (len values) 0)) + +(fn concat_loop ((result (vec bool)) (right (vec bool)) (position i32) (right_len i32)) -> (vec bool) + (if (< position right_len) + (concat_loop (append result (at right position)) right (+ position 1) right_len) + result)) + +(fn concat ((left (vec bool)) (right (vec bool))) -> (vec bool) + (let right_len i32 (len right)) + (concat_loop left right 0 right_len)) + +(fn take_loop ((values (vec bool)) (position i32) (limit i32) (result (vec bool))) -> (vec bool) + (if (< position limit) + (take_loop values (+ position 1) limit (append result (at values position))) + result)) + +(fn take ((values (vec bool)) (count i32)) -> (vec bool) + (let values_len i32 (len values)) + (if (< count 0) + (empty) + (if (< count values_len) + (take_loop values 0 count (empty)) + values))) + +(fn starts_with ((values (vec bool)) (prefix (vec bool))) -> bool + (let prefix_len i32 (len prefix)) + (if (< (len values) prefix_len) + false + (= (take values prefix_len) prefix))) + +(fn without_prefix ((values (vec bool)) (prefix (vec bool))) -> (vec bool) + (if (starts_with values prefix) + (drop values (len prefix)) + values)) + +(fn ends_with ((values (vec bool)) (suffix (vec bool))) -> bool + (let values_len i32 (len values)) + (let suffix_len i32 (len suffix)) + (if (< values_len suffix_len) + false + (= (drop values (- values_len suffix_len)) suffix))) + +(fn without_suffix ((values (vec bool)) (suffix (vec bool))) -> (vec bool) + (if (ends_with values suffix) + (take values (- (len values) (len suffix))) + values)) + +(fn drop_loop ((values (vec bool)) (position i32) (values_len i32) (result (vec bool))) -> (vec bool) + (if (< position values_len) + (drop_loop values (+ position 1) values_len (append result (at values position))) + result)) + +(fn drop ((values (vec bool)) (count i32)) -> (vec bool) + (let values_len i32 (len values)) + (if (< count 0) + values + (if (< count values_len) + (drop_loop values count values_len (empty)) + (empty)))) + +(fn reverse_loop ((values (vec bool)) (position i32) (result (vec bool))) -> (vec bool) + (if (< position 0) + result + (reverse_loop values (- position 1) (append result (at values position))))) + +(fn reverse ((values (vec bool))) -> (vec bool) + (reverse_loop values (- (len values) 1) (empty))) + +(fn subvec ((values (vec bool)) (start i32) (end_exclusive i32)) -> (vec bool) + (if (< start 0) + (empty) + (if (< start end_exclusive) + (take (drop values start) (- end_exclusive start)) + (empty)))) + +(fn insert_at ((values (vec bool)) (position i32) (value bool)) -> (vec bool) + (let values_len i32 (len values)) + (if (< position 0) + values + (if (< position values_len) + (concat (append (take values position) value) (drop values position)) + (if (= position values_len) + (append values value) + values)))) + +(fn insert_range ((values (vec bool)) (position i32) (inserted (vec bool))) -> (vec bool) + (let values_len i32 (len values)) + (if (< position 0) + values + (if (< position values_len) + (concat (concat (take values position) inserted) (drop values position)) + (if (= position values_len) + (concat values inserted) + values)))) + +(fn replace_at ((values (vec bool)) (position i32) (replacement bool)) -> (vec bool) + (if (< position 0) + values + (if (< position (len values)) + (concat (append (take values position) replacement) (drop values (+ position 1))) + values))) + +(fn replace_range ((values (vec bool)) (start i32) (end_exclusive i32) (replacement (vec bool))) -> (vec bool) + (let values_len i32 (len values)) + (if (< start 0) + values + (if (< start values_len) + (if (< start end_exclusive) + (concat (concat (take values start) replacement) (drop values end_exclusive)) + values) + values))) + +(fn remove_at ((values (vec bool)) (position i32)) -> (vec bool) + (if (< position 0) + values + (if (< position (len values)) + (concat (take values position) (drop values (+ position 1))) + values))) + +(fn remove_range ((values (vec bool)) (start i32) (end_exclusive i32)) -> (vec bool) + (let values_len i32 (len values)) + (if (< start 0) + values + (if (< start values_len) + (if (< start end_exclusive) + (concat (take values start) (drop values end_exclusive)) + values) + values))) diff --git a/lib/std/vec_f64.slo b/lib/std/vec_f64.slo new file mode 100644 index 0000000..0b36953 --- /dev/null +++ b/lib/std/vec_f64.slo @@ -0,0 +1,226 @@ +(module vec_f64 (export empty append len at singleton append2 append3 pair triple is_empty index_or first_or last_or index_option first_option last_option index_of_option last_index_of_option contains sum concat take starts_with without_prefix ends_with without_suffix drop reverse subvec insert_at insert_range replace_at replace_range remove_at remove_range)) + +(fn empty () -> (vec f64) + (std.vec.f64.empty)) + +(fn append ((values (vec f64)) (value f64)) -> (vec f64) + (std.vec.f64.append values value)) + +(fn len ((values (vec f64))) -> i32 + (std.vec.f64.len values)) + +(fn at ((values (vec f64)) (position i32)) -> f64 + (std.vec.f64.index values position)) + +(fn singleton ((value f64)) -> (vec f64) + (append (empty) value)) + +(fn append2 ((values (vec f64)) (first f64) (second f64)) -> (vec f64) + (append (append values first) second)) + +(fn append3 ((values (vec f64)) (first f64) (second f64) (third f64)) -> (vec f64) + (append (append2 values first second) third)) + +(fn pair ((first f64) (second f64)) -> (vec f64) + (append2 (empty) first second)) + +(fn triple ((first f64) (second f64) (third f64)) -> (vec f64) + (append3 (empty) first second third)) + +(fn is_empty ((values (vec f64))) -> bool + (= (len values) 0)) + +(fn index_or ((values (vec f64)) (position i32) (fallback f64)) -> f64 + (if (< position 0) + fallback + (if (< position (len values)) + (at values position) + fallback))) + +(fn first_or ((values (vec f64)) (fallback f64)) -> f64 + (index_or values 0 fallback)) + +(fn last_or ((values (vec f64)) (fallback f64)) -> f64 + (if (is_empty values) + fallback + (at values (- (len values) 1)))) + +(fn index_option ((values (vec f64)) (position i32)) -> (option f64) + (if (< position 0) + (none f64) + (if (< position (len values)) + (some f64 (at values position)) + (none f64)))) + +(fn first_option ((values (vec f64))) -> (option f64) + (index_option values 0)) + +(fn last_option ((values (vec f64))) -> (option f64) + (if (is_empty values) + (none f64) + (some f64 (at values (- (len values) 1))))) + +(fn index_of_option_loop ((values (vec f64)) (target f64) (position i32) (values_len i32)) -> (option i32) + (if (< position values_len) + (if (= (at values position) target) + (some i32 position) + (index_of_option_loop values target (+ position 1) values_len)) + (none i32))) + +(fn index_of_option ((values (vec f64)) (target f64)) -> (option i32) + (index_of_option_loop values target 0 (len values))) + +(fn last_index_of_option_loop ((values (vec f64)) (target f64) (position i32) (values_len i32) (found_position (option i32))) -> (option i32) + (if (< position values_len) + (last_index_of_option_loop values target (+ position 1) values_len (if (= (at values position) target) + (some i32 position) + found_position)) + found_position)) + +(fn last_index_of_option ((values (vec f64)) (target f64)) -> (option i32) + (last_index_of_option_loop values target 0 (len values) (none i32))) + +(fn contains_loop ((values (vec f64)) (target f64) (position i32) (values_len i32)) -> bool + (if (< position values_len) + (if (= (at values position) target) + true + (contains_loop values target (+ position 1) values_len)) + false)) + +(fn contains ((values (vec f64)) (target f64)) -> bool + (contains_loop values target 0 (len values))) + +(fn sum_loop ((values (vec f64)) (position i32) (values_len i32) (total f64)) -> f64 + (if (< position values_len) + (sum_loop values (+ position 1) values_len (+ total (at values position))) + total)) + +(fn sum ((values (vec f64))) -> f64 + (sum_loop values 0 (len values) 0.0)) + +(fn concat_loop ((result (vec f64)) (right (vec f64)) (position i32) (right_len i32)) -> (vec f64) + (if (< position right_len) + (concat_loop (append result (at right position)) right (+ position 1) right_len) + result)) + +(fn concat ((left (vec f64)) (right (vec f64))) -> (vec f64) + (let right_len i32 (len right)) + (concat_loop left right 0 right_len)) + +(fn take_loop ((values (vec f64)) (position i32) (limit i32) (result (vec f64))) -> (vec f64) + (if (< position limit) + (take_loop values (+ position 1) limit (append result (at values position))) + result)) + +(fn take ((values (vec f64)) (count i32)) -> (vec f64) + (let values_len i32 (len values)) + (if (< count 0) + (empty) + (if (< count values_len) + (take_loop values 0 count (empty)) + values))) + +(fn starts_with ((values (vec f64)) (prefix (vec f64))) -> bool + (let prefix_len i32 (len prefix)) + (if (< (len values) prefix_len) + false + (= (take values prefix_len) prefix))) + +(fn without_prefix ((values (vec f64)) (prefix (vec f64))) -> (vec f64) + (if (starts_with values prefix) + (drop values (len prefix)) + values)) + +(fn ends_with ((values (vec f64)) (suffix (vec f64))) -> bool + (let values_len i32 (len values)) + (let suffix_len i32 (len suffix)) + (if (< values_len suffix_len) + false + (= (drop values (- values_len suffix_len)) suffix))) + +(fn without_suffix ((values (vec f64)) (suffix (vec f64))) -> (vec f64) + (if (ends_with values suffix) + (take values (- (len values) (len suffix))) + values)) + +(fn drop_loop ((values (vec f64)) (position i32) (values_len i32) (result (vec f64))) -> (vec f64) + (if (< position values_len) + (drop_loop values (+ position 1) values_len (append result (at values position))) + result)) + +(fn drop ((values (vec f64)) (count i32)) -> (vec f64) + (let values_len i32 (len values)) + (if (< count 0) + values + (if (< count values_len) + (drop_loop values count values_len (empty)) + (empty)))) + +(fn reverse_loop ((values (vec f64)) (position i32) (result (vec f64))) -> (vec f64) + (if (< position 0) + result + (reverse_loop values (- position 1) (append result (at values position))))) + +(fn reverse ((values (vec f64))) -> (vec f64) + (reverse_loop values (- (len values) 1) (empty))) + +(fn subvec ((values (vec f64)) (start i32) (end_exclusive i32)) -> (vec f64) + (if (< start 0) + (empty) + (if (< start end_exclusive) + (take (drop values start) (- end_exclusive start)) + (empty)))) + +(fn insert_at ((values (vec f64)) (position i32) (value f64)) -> (vec f64) + (let values_len i32 (len values)) + (if (< position 0) + values + (if (< position values_len) + (concat (append (take values position) value) (drop values position)) + (if (= position values_len) + (append values value) + values)))) + +(fn insert_range ((values (vec f64)) (position i32) (inserted (vec f64))) -> (vec f64) + (let values_len i32 (len values)) + (if (< position 0) + values + (if (< position values_len) + (concat (concat (take values position) inserted) (drop values position)) + (if (= position values_len) + (concat values inserted) + values)))) + +(fn replace_at ((values (vec f64)) (position i32) (replacement f64)) -> (vec f64) + (if (< position 0) + values + (if (< position (len values)) + (concat (append (take values position) replacement) (drop values (+ position 1))) + values))) + +(fn replace_range ((values (vec f64)) (start i32) (end_exclusive i32) (replacement (vec f64))) -> (vec f64) + (let values_len i32 (len values)) + (if (< start 0) + values + (if (< start values_len) + (if (< start end_exclusive) + (concat (concat (take values start) replacement) (drop values end_exclusive)) + values) + values))) + +(fn remove_at ((values (vec f64)) (position i32)) -> (vec f64) + (if (< position 0) + values + (if (< position (len values)) + (concat (take values position) (drop values (+ position 1))) + values))) + +(fn remove_range ((values (vec f64)) (start i32) (end_exclusive i32)) -> (vec f64) + (let values_len i32 (len values)) + (if (< start 0) + values + (if (< start values_len) + (if (< start end_exclusive) + (concat (take values start) (drop values end_exclusive)) + values) + values))) diff --git a/lib/std/vec_i32.slo b/lib/std/vec_i32.slo new file mode 100644 index 0000000..c2705f9 --- /dev/null +++ b/lib/std/vec_i32.slo @@ -0,0 +1,270 @@ +(module vec_i32 (export empty append len at singleton append2 append3 pair triple repeat range range_from_zero is_empty index_or first_or last_or index_option first_option last_option index_of_option last_index_of_option count_of contains sum concat take starts_with without_prefix ends_with without_suffix drop reverse subvec insert_at insert_range replace_at replace_range remove_at remove_range)) + +(import std.option (some_i32 none_i32)) + +(fn empty () -> (vec i32) + (std.vec.i32.empty)) + +(fn append ((values (vec i32)) (value i32)) -> (vec i32) + (std.vec.i32.append values value)) + +(fn len ((values (vec i32))) -> i32 + (std.vec.i32.len values)) + +(fn at ((values (vec i32)) (position i32)) -> i32 + (std.vec.i32.index values position)) + +(fn singleton ((value i32)) -> (vec i32) + (append (empty) value)) + +(fn append2 ((values (vec i32)) (first i32) (second i32)) -> (vec i32) + (append (append values first) second)) + +(fn append3 ((values (vec i32)) (first i32) (second i32) (third i32)) -> (vec i32) + (append (append2 values first second) third)) + +(fn pair ((first i32) (second i32)) -> (vec i32) + (append2 (empty) first second)) + +(fn triple ((first i32) (second i32) (third i32)) -> (vec i32) + (append3 (empty) first second third)) + +(fn repeat_loop ((value i32) (remaining i32) (result (vec i32))) -> (vec i32) + (if (< remaining 1) + result + (repeat_loop value (- remaining 1) (append result value)))) + +(fn repeat ((value i32) (count i32)) -> (vec i32) + (if (< count 1) + (empty) + (repeat_loop value count (empty)))) + +(fn range_from_zero_loop ((current i32) (count i32) (result (vec i32))) -> (vec i32) + (if (< current count) + (range_from_zero_loop (+ current 1) count (append result current)) + result)) + +(fn range_loop ((current i32) (end_exclusive i32) (result (vec i32))) -> (vec i32) + (if (< current end_exclusive) + (range_loop (+ current 1) end_exclusive (append result current)) + result)) + +(fn range ((start i32) (end_exclusive i32)) -> (vec i32) + (if (< start end_exclusive) + (range_loop start end_exclusive (empty)) + (empty))) + +(fn range_from_zero ((count i32)) -> (vec i32) + (if (< count 1) + (empty) + (range_from_zero_loop 0 count (empty)))) + +(fn is_empty ((values (vec i32))) -> bool + (= (len values) 0)) + +(fn index_or ((values (vec i32)) (position i32) (fallback i32)) -> i32 + (if (< position 0) + fallback + (if (< position (len values)) + (at values position) + fallback))) + +(fn first_or ((values (vec i32)) (fallback i32)) -> i32 + (index_or values 0 fallback)) + +(fn last_or ((values (vec i32)) (fallback i32)) -> i32 + (if (is_empty values) + fallback + (at values (- (len values) 1)))) + +(fn index_option ((values (vec i32)) (position i32)) -> (option i32) + (if (< position 0) + (none_i32) + (if (< position (len values)) + (some_i32 (at values position)) + (none_i32)))) + +(fn first_option ((values (vec i32))) -> (option i32) + (index_option values 0)) + +(fn last_option ((values (vec i32))) -> (option i32) + (if (is_empty values) + (none_i32) + (some_i32 (at values (- (len values) 1))))) + +(fn index_of_option ((values (vec i32)) (target i32)) -> (option i32) + (var position i32 0) + (var found_position i32 -1) + (while (and (< position (len values)) (< found_position 0)) + (set found_position (if (= (at values position) target) + position + found_position)) + (set position (+ position 1))) + (if (< found_position 0) + (none_i32) + (some_i32 found_position))) + +(fn last_index_of_option ((values (vec i32)) (target i32)) -> (option i32) + (var position i32 0) + (var found_position i32 -1) + (while (< position (len values)) + (set found_position (if (= (at values position) target) + position + found_position)) + (set position (+ position 1))) + (if (< found_position 0) + (none_i32) + (some_i32 found_position))) + +(fn count_of ((values (vec i32)) (target i32)) -> i32 + (var position i32 0) + (var hits i32 0) + (while (< position (len values)) + (set hits (+ hits (if (= (at values position) target) + 1 + 0))) + (set position (+ position 1))) + hits) + +(fn contains ((values (vec i32)) (target i32)) -> bool + (var position i32 0) + (var hits i32 0) + (while (< position (len values)) + (set hits (+ hits (if (= (at values position) target) + 1 + 0))) + (set position (+ position 1))) + (> hits 0)) + +(fn sum ((values (vec i32))) -> i32 + (var position i32 0) + (var total i32 0) + (while (< position (len values)) + (set total (+ total (at values position))) + (set position (+ position 1))) + total) + +(fn concat_loop ((result (vec i32)) (right (vec i32)) (position i32) (right_len i32)) -> (vec i32) + (if (< position right_len) + (concat_loop (append result (at right position)) right (+ position 1) right_len) + result)) + +(fn concat ((left (vec i32)) (right (vec i32))) -> (vec i32) + (let right_len i32 (len right)) + (concat_loop left right 0 right_len)) + +(fn take_loop ((values (vec i32)) (position i32) (limit i32) (result (vec i32))) -> (vec i32) + (if (< position limit) + (take_loop values (+ position 1) limit (append result (at values position))) + result)) + +(fn take ((values (vec i32)) (count i32)) -> (vec i32) + (let values_len i32 (len values)) + (if (< count 0) + (empty) + (if (< count values_len) + (take_loop values 0 count (empty)) + values))) + +(fn starts_with ((values (vec i32)) (prefix (vec i32))) -> bool + (let prefix_len i32 (len prefix)) + (if (< (len values) prefix_len) + false + (= (take values prefix_len) prefix))) + +(fn without_prefix ((values (vec i32)) (prefix (vec i32))) -> (vec i32) + (if (starts_with values prefix) + (drop values (len prefix)) + values)) + +(fn ends_with ((values (vec i32)) (suffix (vec i32))) -> bool + (let values_len i32 (len values)) + (let suffix_len i32 (len suffix)) + (if (< values_len suffix_len) + false + (= (drop values (- values_len suffix_len)) suffix))) + +(fn without_suffix ((values (vec i32)) (suffix (vec i32))) -> (vec i32) + (if (ends_with values suffix) + (take values (- (len values) (len suffix))) + values)) + +(fn drop_loop ((values (vec i32)) (position i32) (values_len i32) (result (vec i32))) -> (vec i32) + (if (< position values_len) + (drop_loop values (+ position 1) values_len (append result (at values position))) + result)) + +(fn drop ((values (vec i32)) (count i32)) -> (vec i32) + (let values_len i32 (len values)) + (if (< count 0) + values + (if (< count values_len) + (drop_loop values count values_len (empty)) + (empty)))) + +(fn reverse_loop ((values (vec i32)) (position i32) (result (vec i32))) -> (vec i32) + (if (< position 0) + result + (reverse_loop values (- position 1) (append result (at values position))))) + +(fn reverse ((values (vec i32))) -> (vec i32) + (reverse_loop values (- (len values) 1) (empty))) + +(fn subvec ((values (vec i32)) (start i32) (end_exclusive i32)) -> (vec i32) + (if (< start 0) + (empty) + (if (< start end_exclusive) + (take (drop values start) (- end_exclusive start)) + (empty)))) + +(fn insert_at ((values (vec i32)) (position i32) (value i32)) -> (vec i32) + (if (< position 0) + values + (if (< position (len values)) + (concat (append (take values position) value) (drop values position)) + (if (= position (len values)) + (append values value) + values)))) + +(fn insert_range ((values (vec i32)) (position i32) (inserted (vec i32))) -> (vec i32) + (if (< position 0) + values + (if (< position (len values)) + (concat (concat (take values position) inserted) (drop values position)) + (if (= position (len values)) + (concat values inserted) + values)))) + +(fn replace_at ((values (vec i32)) (position i32) (replacement i32)) -> (vec i32) + (if (< position 0) + values + (if (< position (len values)) + (concat (append (take values position) replacement) (drop values (+ position 1))) + values))) + +(fn replace_range ((values (vec i32)) (start i32) (end_exclusive i32) (replacement (vec i32))) -> (vec i32) + (let values_len i32 (len values)) + (if (< start 0) + values + (if (< start values_len) + (if (< start end_exclusive) + (concat (concat (take values start) replacement) (drop values end_exclusive)) + values) + values))) + +(fn remove_at ((values (vec i32)) (position i32)) -> (vec i32) + (if (< position 0) + values + (if (< position (len values)) + (concat (take values position) (drop values (+ position 1))) + values))) + +(fn remove_range ((values (vec i32)) (start i32) (end_exclusive i32)) -> (vec i32) + (let values_len i32 (len values)) + (if (< start 0) + values + (if (< start values_len) + (if (< start end_exclusive) + (concat (take values start) (drop values end_exclusive)) + values) + values))) diff --git a/lib/std/vec_i64.slo b/lib/std/vec_i64.slo new file mode 100644 index 0000000..d041e3e --- /dev/null +++ b/lib/std/vec_i64.slo @@ -0,0 +1,203 @@ +(module vec_i64 (export empty append len at singleton append2 append3 pair triple is_empty index_or first_or last_or index_option first_option last_option index_of_option last_index_of_option contains sum concat take drop reverse subvec insert_at insert_range replace_at replace_range remove_at remove_range)) + +(fn empty () -> (vec i64) + (std.vec.i64.empty)) + +(fn append ((values (vec i64)) (value i64)) -> (vec i64) + (std.vec.i64.append values value)) + +(fn len ((values (vec i64))) -> i32 + (std.vec.i64.len values)) + +(fn at ((values (vec i64)) (position i32)) -> i64 + (std.vec.i64.index values position)) + +(fn singleton ((value i64)) -> (vec i64) + (append (empty) value)) + +(fn append2 ((values (vec i64)) (first i64) (second i64)) -> (vec i64) + (append (append values first) second)) + +(fn append3 ((values (vec i64)) (first i64) (second i64) (third i64)) -> (vec i64) + (append (append2 values first second) third)) + +(fn pair ((first i64) (second i64)) -> (vec i64) + (append2 (empty) first second)) + +(fn triple ((first i64) (second i64) (third i64)) -> (vec i64) + (append3 (empty) first second third)) + +(fn is_empty ((values (vec i64))) -> bool + (= (len values) 0)) + +(fn index_or ((values (vec i64)) (position i32) (fallback i64)) -> i64 + (if (< position 0) + fallback + (if (< position (len values)) + (at values position) + fallback))) + +(fn first_or ((values (vec i64)) (fallback i64)) -> i64 + (index_or values 0 fallback)) + +(fn last_or ((values (vec i64)) (fallback i64)) -> i64 + (if (is_empty values) + fallback + (at values (- (len values) 1)))) + +(fn index_option ((values (vec i64)) (position i32)) -> (option i64) + (if (< position 0) + (none i64) + (if (< position (len values)) + (some i64 (at values position)) + (none i64)))) + +(fn first_option ((values (vec i64))) -> (option i64) + (index_option values 0)) + +(fn last_option ((values (vec i64))) -> (option i64) + (if (is_empty values) + (none i64) + (some i64 (at values (- (len values) 1))))) + +(fn index_of_option_loop ((values (vec i64)) (target i64) (position i32) (values_len i32)) -> (option i32) + (if (< position values_len) + (if (= (at values position) target) + (some i32 position) + (index_of_option_loop values target (+ position 1) values_len)) + (none i32))) + +(fn index_of_option ((values (vec i64)) (target i64)) -> (option i32) + (index_of_option_loop values target 0 (len values))) + +(fn last_index_of_option_loop ((values (vec i64)) (target i64) (position i32) (values_len i32) (found_position (option i32))) -> (option i32) + (if (< position values_len) + (last_index_of_option_loop values target (+ position 1) values_len (if (= (at values position) target) + (some i32 position) + found_position)) + found_position)) + +(fn last_index_of_option ((values (vec i64)) (target i64)) -> (option i32) + (last_index_of_option_loop values target 0 (len values) (none i32))) + +(fn contains_loop ((values (vec i64)) (target i64) (position i32) (values_len i32)) -> bool + (if (< position values_len) + (if (= (at values position) target) + true + (contains_loop values target (+ position 1) values_len)) + false)) + +(fn contains ((values (vec i64)) (target i64)) -> bool + (contains_loop values target 0 (len values))) + +(fn sum_loop ((values (vec i64)) (position i32) (values_len i32) (total i64)) -> i64 + (if (< position values_len) + (sum_loop values (+ position 1) values_len (+ total (at values position))) + total)) + +(fn sum ((values (vec i64))) -> i64 + (sum_loop values 0 (len values) 0i64)) + +(fn concat_loop ((result (vec i64)) (right (vec i64)) (position i32) (right_len i32)) -> (vec i64) + (if (< position right_len) + (concat_loop (append result (at right position)) right (+ position 1) right_len) + result)) + +(fn concat ((left (vec i64)) (right (vec i64))) -> (vec i64) + (let right_len i32 (len right)) + (concat_loop left right 0 right_len)) + +(fn take_loop ((values (vec i64)) (position i32) (limit i32) (result (vec i64))) -> (vec i64) + (if (< position limit) + (take_loop values (+ position 1) limit (append result (at values position))) + result)) + +(fn take ((values (vec i64)) (count i32)) -> (vec i64) + (let values_len i32 (len values)) + (if (< count 0) + (empty) + (if (< count values_len) + (take_loop values 0 count (empty)) + values))) + +(fn drop_loop ((values (vec i64)) (position i32) (values_len i32) (result (vec i64))) -> (vec i64) + (if (< position values_len) + (drop_loop values (+ position 1) values_len (append result (at values position))) + result)) + +(fn drop ((values (vec i64)) (count i32)) -> (vec i64) + (let values_len i32 (len values)) + (if (< count 0) + values + (if (< count values_len) + (drop_loop values count values_len (empty)) + (empty)))) + +(fn reverse_loop ((values (vec i64)) (position i32) (result (vec i64))) -> (vec i64) + (if (< position 0) + result + (reverse_loop values (- position 1) (append result (at values position))))) + +(fn reverse ((values (vec i64))) -> (vec i64) + (reverse_loop values (- (len values) 1) (empty))) + +(fn subvec ((values (vec i64)) (start i32) (end_exclusive i32)) -> (vec i64) + (if (< start 0) + (empty) + (if (< start end_exclusive) + (take (drop values start) (- end_exclusive start)) + (empty)))) + +(fn insert_at ((values (vec i64)) (position i32) (value i64)) -> (vec i64) + (let values_len i32 (len values)) + (if (< position 0) + values + (if (< position values_len) + (concat (append (take values position) value) (drop values position)) + (if (= position values_len) + (append values value) + values)))) + +(fn insert_range ((values (vec i64)) (position i32) (inserted (vec i64))) -> (vec i64) + (let values_len i32 (len values)) + (if (< position 0) + values + (if (< position values_len) + (concat (concat (take values position) inserted) (drop values position)) + (if (= position values_len) + (concat values inserted) + values)))) + +(fn replace_at ((values (vec i64)) (position i32) (replacement i64)) -> (vec i64) + (if (< position 0) + values + (if (< position (len values)) + (concat (append (take values position) replacement) (drop values (+ position 1))) + values))) + +(fn replace_range ((values (vec i64)) (start i32) (end_exclusive i32) (replacement (vec i64))) -> (vec i64) + (let values_len i32 (len values)) + (if (< start 0) + values + (if (< start values_len) + (if (< start end_exclusive) + (concat (concat (take values start) replacement) (drop values end_exclusive)) + values) + values))) + +(fn remove_at ((values (vec i64)) (position i32)) -> (vec i64) + (if (< position 0) + values + (if (< position (len values)) + (concat (take values position) (drop values (+ position 1))) + values))) + +(fn remove_range ((values (vec i64)) (start i32) (end_exclusive i32)) -> (vec i64) + (let values_len i32 (len values)) + (if (< start 0) + values + (if (< start values_len) + (if (< start end_exclusive) + (concat (take values start) (drop values end_exclusive)) + values) + values))) diff --git a/lib/std/vec_string.slo b/lib/std/vec_string.slo new file mode 100644 index 0000000..d72c4b5 --- /dev/null +++ b/lib/std/vec_string.slo @@ -0,0 +1,228 @@ +(module vec_string (export empty append len at singleton append2 append3 pair triple is_empty index_or first_or last_or index_option first_option last_option index_of_option last_index_of_option contains count_of concat take starts_with without_prefix ends_with without_suffix drop reverse subvec insert_at insert_range replace_at replace_range remove_at remove_range)) + +(fn empty () -> (vec string) + (std.vec.string.empty)) + +(fn append ((values (vec string)) (value string)) -> (vec string) + (std.vec.string.append values value)) + +(fn len ((values (vec string))) -> i32 + (std.vec.string.len values)) + +(fn at ((values (vec string)) (position i32)) -> string + (std.vec.string.index values position)) + +(fn singleton ((value string)) -> (vec string) + (append (empty) value)) + +(fn append2 ((values (vec string)) (first string) (second string)) -> (vec string) + (append (append values first) second)) + +(fn append3 ((values (vec string)) (first string) (second string) (third string)) -> (vec string) + (append (append2 values first second) third)) + +(fn pair ((first string) (second string)) -> (vec string) + (append2 (empty) first second)) + +(fn triple ((first string) (second string) (third string)) -> (vec string) + (append3 (empty) first second third)) + +(fn is_empty ((values (vec string))) -> bool + (= (len values) 0)) + +(fn index_or ((values (vec string)) (position i32) (fallback string)) -> string + (if (< position 0) + fallback + (if (< position (len values)) + (at values position) + fallback))) + +(fn first_or ((values (vec string)) (fallback string)) -> string + (index_or values 0 fallback)) + +(fn last_or ((values (vec string)) (fallback string)) -> string + (if (is_empty values) + fallback + (at values (- (len values) 1)))) + +(fn index_option ((values (vec string)) (position i32)) -> (option string) + (if (< position 0) + (none string) + (if (< position (len values)) + (some string (at values position)) + (none string)))) + +(fn first_option ((values (vec string))) -> (option string) + (index_option values 0)) + +(fn last_option ((values (vec string))) -> (option string) + (if (is_empty values) + (none string) + (some string (at values (- (len values) 1))))) + +(fn index_of_option_loop ((values (vec string)) (target string) (position i32) (values_len i32)) -> (option i32) + (if (< position values_len) + (if (= (at values position) target) + (some i32 position) + (index_of_option_loop values target (+ position 1) values_len)) + (none i32))) + +(fn index_of_option ((values (vec string)) (target string)) -> (option i32) + (index_of_option_loop values target 0 (len values))) + +(fn last_index_of_option_loop ((values (vec string)) (target string) (position i32) (values_len i32) (found (option i32))) -> (option i32) + (if (< position values_len) + (if (= (at values position) target) + (last_index_of_option_loop values target (+ position 1) values_len (some i32 position)) + (last_index_of_option_loop values target (+ position 1) values_len found)) + found)) + +(fn last_index_of_option ((values (vec string)) (target string)) -> (option i32) + (last_index_of_option_loop values target 0 (len values) (none i32))) + +(fn contains_loop ((values (vec string)) (target string) (position i32) (values_len i32)) -> bool + (if (< position values_len) + (if (= (at values position) target) + true + (contains_loop values target (+ position 1) values_len)) + false)) + +(fn contains ((values (vec string)) (target string)) -> bool + (contains_loop values target 0 (len values))) + +(fn count_of_loop ((values (vec string)) (target string) (position i32) (values_len i32) (hits i32)) -> i32 + (if (< position values_len) + (count_of_loop values target (+ position 1) values_len (+ hits (if (= (at values position) target) + 1 + 0))) + hits)) + +(fn count_of ((values (vec string)) (target string)) -> i32 + (count_of_loop values target 0 (len values) 0)) + +(fn concat_loop ((built (vec string)) (right (vec string)) (position i32) (right_len i32)) -> (vec string) + (if (< position right_len) + (concat_loop (append built (at right position)) right (+ position 1) right_len) + built)) + +(fn concat ((left (vec string)) (right (vec string))) -> (vec string) + (let right_len i32 (len right)) + (concat_loop left right 0 right_len)) + +(fn take_loop ((values (vec string)) (position i32) (limit i32) (built (vec string))) -> (vec string) + (if (< position limit) + (take_loop values (+ position 1) limit (append built (at values position))) + built)) + +(fn take ((values (vec string)) (count i32)) -> (vec string) + (let values_len i32 (len values)) + (if (< count 0) + (empty) + (if (< count values_len) + (take_loop values 0 count (empty)) + values))) + +(fn starts_with ((values (vec string)) (prefix (vec string))) -> bool + (let prefix_len i32 (len prefix)) + (if (< (len values) prefix_len) + false + (= (take values prefix_len) prefix))) + +(fn without_prefix ((values (vec string)) (prefix (vec string))) -> (vec string) + (if (starts_with values prefix) + (drop values (len prefix)) + values)) + +(fn ends_with ((values (vec string)) (suffix (vec string))) -> bool + (let values_len i32 (len values)) + (let suffix_len i32 (len suffix)) + (if (< values_len suffix_len) + false + (= (drop values (- values_len suffix_len)) suffix))) + +(fn without_suffix ((values (vec string)) (suffix (vec string))) -> (vec string) + (if (ends_with values suffix) + (take values (- (len values) (len suffix))) + values)) + +(fn drop_loop ((values (vec string)) (position i32) (values_len i32) (built (vec string))) -> (vec string) + (if (< position values_len) + (drop_loop values (+ position 1) values_len (append built (at values position))) + built)) + +(fn drop ((values (vec string)) (count i32)) -> (vec string) + (let values_len i32 (len values)) + (if (< count 0) + values + (if (< count values_len) + (drop_loop values count values_len (empty)) + (empty)))) + +(fn reverse_loop ((values (vec string)) (position i32) (built (vec string))) -> (vec string) + (if (< position 0) + built + (reverse_loop values (- position 1) (append built (at values position))))) + +(fn reverse ((values (vec string))) -> (vec string) + (reverse_loop values (- (len values) 1) (empty))) + +(fn subvec ((values (vec string)) (start i32) (end_exclusive i32)) -> (vec string) + (if (< start 0) + (empty) + (if (< start end_exclusive) + (take (drop values start) (- end_exclusive start)) + (empty)))) + +(fn insert_at ((values (vec string)) (position i32) (value string)) -> (vec string) + (let values_len i32 (len values)) + (if (< position 0) + values + (if (< position values_len) + (concat (append (take values position) value) (drop values position)) + (if (= position values_len) + (append values value) + values)))) + +(fn insert_range ((values (vec string)) (position i32) (inserted (vec string))) -> (vec string) + (let values_len i32 (len values)) + (if (< position 0) + values + (if (< position values_len) + (concat (concat (take values position) inserted) (drop values position)) + (if (= position values_len) + (concat values inserted) + values)))) + +(fn replace_at ((values (vec string)) (position i32) (replacement string)) -> (vec string) + (if (< position 0) + values + (if (< position (len values)) + (concat (append (take values position) replacement) (drop values (+ position 1))) + values))) + +(fn replace_range ((values (vec string)) (start i32) (end_exclusive i32) (replacement (vec string))) -> (vec string) + (let values_len i32 (len values)) + (if (< start 0) + values + (if (< start values_len) + (if (< start end_exclusive) + (concat (concat (take values start) replacement) (drop values end_exclusive)) + values) + values))) + +(fn remove_at ((values (vec string)) (position i32)) -> (vec string) + (if (< position 0) + values + (if (< position (len values)) + (concat (take values position) (drop values (+ position 1))) + values))) + +(fn remove_range ((values (vec string)) (start i32) (end_exclusive i32)) -> (vec string) + (let values_len i32 (len values)) + (if (< start 0) + values + (if (< start values_len) + (if (< start end_exclusive) + (concat (take values start) (drop values end_exclusive)) + values) + values))) diff --git a/runtime/runtime.c b/runtime/runtime.c new file mode 100644 index 0000000..bddbd1e --- /dev/null +++ b/runtime/runtime.c @@ -0,0 +1,1114 @@ +#define _POSIX_C_SOURCE 200809L + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef struct { + int32_t len; + int32_t *data; +} __glagol_vec_i32; + +typedef struct { + int32_t len; + int64_t *data; +} __glagol_vec_i64; + +typedef struct { + int32_t len; + double *data; +} __glagol_vec_f64; + +typedef struct { + int32_t len; + bool *data; +} __glagol_vec_bool; + +typedef struct { + int32_t len; + char **data; +} __glagol_vec_string; + +static int32_t __glagol_process_argc_value = 0; +static char **__glagol_process_argv_value = NULL; +static bool __glagol_time_base_initialized = false; +static struct timespec __glagol_time_base; + +void print_i32(int32_t value) { + printf("%d\n", value); +} + +void print_i64(int64_t value) { + printf("%" PRId64 "\n", value); +} + +void print_u32(uint32_t value) { + printf("%" PRIu32 "\n", value); +} + +void print_u64(uint64_t value) { + printf("%" PRIu64 "\n", value); +} + +void print_f64(double value) { + printf("%.17g\n", value); +} + +void print_string(const char *value) { + puts(value); +} + +void print_bool(bool value) { + puts(value ? "true" : "false"); +} + +int32_t string_len(const char *value) { + return (int32_t)strlen(value); +} + +void __glagol_io_eprint(const char *value) { + fputs(value, stderr); +} + +char *__glagol_io_read_stdin_result(void) { + size_t capacity = 1024; + size_t length = 0; + char *value = malloc(capacity); + if (value == NULL) { + return NULL; + } + + for (;;) { + if (length == capacity) { + if (capacity > SIZE_MAX / 2) { + free(value); + return NULL; + } + + size_t next_capacity = capacity * 2; + char *next = realloc(value, next_capacity); + if (next == NULL) { + free(value); + return NULL; + } + + value = next; + capacity = next_capacity; + } + + size_t available = capacity - length; + size_t read_count = fread(value + length, 1, available, stdin); + length += read_count; + + if (read_count < available) { + if (ferror(stdin)) { + free(value); + return NULL; + } + break; + } + } + + if (length == capacity) { + if (capacity == SIZE_MAX) { + free(value); + return NULL; + } + + char *next = realloc(value, capacity + 1); + if (next == NULL) { + free(value); + return NULL; + } + value = next; + } + + value[length] = '\0'; + return value; +} + +void __glagol_process_init(int32_t argc, char **argv) { + __glagol_process_argc_value = argc; + __glagol_process_argv_value = argv; +} + +int32_t __glagol_process_argc(void) { + return __glagol_process_argc_value; +} + +static void __glagol_process_arg_trap(void) { + fputs("slovo runtime error: process argument index out of bounds\n", stderr); + exit(1); +} + +char *__glagol_process_arg(int32_t index) { + if (index < 0 || index >= __glagol_process_argc_value || __glagol_process_argv_value == NULL) { + __glagol_process_arg_trap(); + } + return __glagol_process_argv_value[index]; +} + +char *__glagol_process_arg_result(int32_t index) { + if (index < 0 || index >= __glagol_process_argc_value || __glagol_process_argv_value == NULL) { + return NULL; + } + return __glagol_process_argv_value[index]; +} + +char *__glagol_env_get(const char *name) { + char *value = getenv(name); + return value == NULL ? "" : value; +} + +char *__glagol_env_get_result(const char *name) { + return getenv(name); +} + +static void __glagol_fs_read_trap(void) { + fputs("slovo runtime error: file read failed\n", stderr); + exit(1); +} + +char *__glagol_fs_read_text(const char *path) { + FILE *file = fopen(path, "rb"); + if (file == NULL) { + __glagol_fs_read_trap(); + } + + if (fseek(file, 0, SEEK_END) != 0) { + fclose(file); + __glagol_fs_read_trap(); + } + + long length = ftell(file); + if (length < 0) { + fclose(file); + __glagol_fs_read_trap(); + } + + if (fseek(file, 0, SEEK_SET) != 0) { + fclose(file); + __glagol_fs_read_trap(); + } + + size_t size = (size_t)length; + char *value = malloc(size + 1); + if (value == NULL) { + fclose(file); + __glagol_fs_read_trap(); + } + + if (size > 0 && fread(value, 1, size, file) != size) { + free(value); + fclose(file); + __glagol_fs_read_trap(); + } + + if (fclose(file) != 0) { + free(value); + __glagol_fs_read_trap(); + } + + value[size] = '\0'; + return value; +} + +char *__glagol_fs_read_text_result(const char *path) { + FILE *file = fopen(path, "rb"); + if (file == NULL) { + return NULL; + } + + if (fseek(file, 0, SEEK_END) != 0) { + fclose(file); + return NULL; + } + + long length = ftell(file); + if (length < 0) { + fclose(file); + return NULL; + } + + if (fseek(file, 0, SEEK_SET) != 0) { + fclose(file); + return NULL; + } + + size_t size = (size_t)length; + char *value = malloc(size + 1); + if (value == NULL) { + fclose(file); + return NULL; + } + + if (size > 0 && fread(value, 1, size, file) != size) { + free(value); + fclose(file); + return NULL; + } + + if (fclose(file) != 0) { + free(value); + return NULL; + } + + value[size] = '\0'; + return value; +} + +int32_t __glagol_fs_write_text(const char *path, const char *text) { + FILE *file = fopen(path, "wb"); + if (file == NULL) { + return 1; + } + + size_t len = strlen(text); + if (len > 0 && fwrite(text, 1, len, file) != len) { + fclose(file); + return 1; + } + + if (fclose(file) != 0) { + return 1; + } + + return 0; +} + +int32_t __glagol_fs_write_text_result(const char *path, const char *text) { + return __glagol_fs_write_text(path, text); +} + +static void __glagol_allocation_trap(void) { + fputs("slovo runtime error: string allocation failed\n", stderr); + exit(1); +} + +char *__glagol_string_concat(const char *left, const char *right) { + size_t left_len = strlen(left); + size_t right_len = strlen(right); + + if (left_len > SIZE_MAX - right_len - 1) { + __glagol_allocation_trap(); + } + + char *value = malloc(left_len + right_len + 1); + if (value == NULL) { + __glagol_allocation_trap(); + } + + memcpy(value, left, left_len); + memcpy(value + left_len, right, right_len + 1); + return value; +} + +static char *__glagol_num_u64_to_string_impl(uint64_t magnitude, bool negative) { + char reversed[20]; + size_t digit_count = 0; + + do { + reversed[digit_count] = (char)(0x30u + (magnitude % 10u)); + digit_count++; + magnitude /= 10u; + } while (magnitude != 0u); + + size_t sign_count = negative ? 1u : 0u; + char *value = malloc(sign_count + digit_count + 1u); + if (value == NULL) { + __glagol_allocation_trap(); + } + + size_t cursor = 0; + if (negative) { + value[cursor] = (char)0x2du; + cursor++; + } + + for (size_t i = 0; i < digit_count; i++) { + value[cursor + i] = reversed[digit_count - i - 1u]; + } + value[cursor + digit_count] = '\0'; + return value; +} + +static char *__glagol_num_i64_to_string_impl(int64_t value) { + bool negative = value < 0; + uint64_t magnitude = negative + ? (uint64_t)(-(value + 1)) + 1u + : (uint64_t)value; + return __glagol_num_u64_to_string_impl(magnitude, negative); +} + +char *__glagol_num_i32_to_string(int32_t value) { + return __glagol_num_i64_to_string_impl((int64_t)value); +} + +char *__glagol_num_u32_to_string(uint32_t value) { + return __glagol_num_u64_to_string_impl((uint64_t)value, false); +} + +char *__glagol_num_i64_to_string(int64_t value) { + return __glagol_num_i64_to_string_impl(value); +} + +char *__glagol_num_u64_to_string(uint64_t value) { + return __glagol_num_u64_to_string_impl(value, false); +} + +char *__glagol_num_f64_to_string(double value) { + int length = snprintf(NULL, 0, "%.17f", value); + if (length < 0) { + __glagol_allocation_trap(); + } + + char *text = malloc((size_t)length + 1u); + if (text == NULL) { + __glagol_allocation_trap(); + } + + int written = snprintf(text, (size_t)length + 1u, "%.17f", value); + if (written != length) { + free(text); + __glagol_allocation_trap(); + } + + char *dot = strchr(text, '.'); + if (dot != NULL) { + char *last = text + strlen(text) - 1u; + while (last > dot + 1 && *last == '0') { + *last = '\0'; + last--; + } + } + return text; +} + +bool __glagol_string_eq(const char *left, const char *right) { + return strcmp(left, right) == 0; +} + +static int64_t __glagol_string_parse_i32_result_encode(uint32_t status, int32_t payload) { + uint64_t encoded = ((uint64_t)status << 32) | (uint32_t)payload; + return (int64_t)encoded; +} + +static int64_t __glagol_string_parse_i32_result_err(void) { + return __glagol_string_parse_i32_result_encode(1, 1); +} + +int64_t __glagol_string_parse_i32_result(const char *text) { + if (text == NULL || text[0] == '\0') { + return __glagol_string_parse_i32_result_err(); + } + + const unsigned char *cursor = (const unsigned char *)text; + bool negative = false; + if (*cursor == '-') { + negative = true; + cursor++; + if (*cursor == '\0') { + return __glagol_string_parse_i32_result_err(); + } + } + + int64_t limit = negative ? 2147483648LL : 2147483647LL; + int64_t value = 0; + while (*cursor != '\0') { + unsigned char byte = *cursor; + if (byte < '0' || byte > '9') { + return __glagol_string_parse_i32_result_err(); + } + + int64_t digit = (int64_t)(byte - '0'); + if (value > (limit - digit) / 10) { + return __glagol_string_parse_i32_result_err(); + } + + value = value * 10 + digit; + cursor++; + } + + int32_t parsed; + if (negative) { + parsed = value == 2147483648LL ? INT32_MIN : (int32_t)-value; + } else { + parsed = (int32_t)value; + } + return __glagol_string_parse_i32_result_encode(0, parsed); +} + +static int64_t __glagol_string_parse_u32_result_err(void) { + return __glagol_string_parse_i32_result_encode(1, 1); +} + +int64_t __glagol_string_parse_u32_result(const char *text) { + if (text == NULL || text[0] == '\0') { + return __glagol_string_parse_u32_result_err(); + } + + const unsigned char *cursor = (const unsigned char *)text; + uint64_t value = 0; + while (*cursor != '\0') { + unsigned char byte = *cursor; + if (byte < '0' || byte > '9') { + return __glagol_string_parse_u32_result_err(); + } + + uint64_t digit = (uint64_t)(byte - '0'); + if (value > (((uint64_t)UINT32_MAX) - digit) / 10u) { + return __glagol_string_parse_u32_result_err(); + } + + value = value * 10u + digit; + cursor++; + } + + return __glagol_string_parse_i32_result_encode(0, (int32_t)(uint32_t)value); +} + +int32_t __glagol_string_parse_i64_result(const char *text, int64_t *out) { + if (out == NULL || text == NULL || text[0] == '\0') { + return 1; + } + + const unsigned char *cursor = (const unsigned char *)text; + bool negative = false; + if (*cursor == '-') { + negative = true; + cursor++; + if (*cursor == '\0') { + return 1; + } + } + + uint64_t limit = negative ? ((uint64_t)INT64_MAX + 1u) : (uint64_t)INT64_MAX; + uint64_t value = 0; + while (*cursor != '\0') { + unsigned char byte = *cursor; + if (byte < '0' || byte > '9') { + return 1; + } + + uint64_t digit = (uint64_t)(byte - '0'); + if (value > (limit - digit) / 10u) { + return 1; + } + + value = value * 10u + digit; + cursor++; + } + + if (negative) { + *out = value == ((uint64_t)INT64_MAX + 1u) ? INT64_MIN : -(int64_t)value; + } else { + *out = (int64_t)value; + } + return 0; +} + +int32_t __glagol_string_parse_u64_result(const char *text, uint64_t *out) { + if (out == NULL || text == NULL || text[0] == '\0') { + return 1; + } + + const unsigned char *cursor = (const unsigned char *)text; + uint64_t value = 0; + while (*cursor != '\0') { + unsigned char byte = *cursor; + if (byte < '0' || byte > '9') { + return 1; + } + + uint64_t digit = (uint64_t)(byte - '0'); + if (value > (UINT64_MAX - digit) / 10u) { + return 1; + } + + value = value * 10u + digit; + cursor++; + } + + *out = value; + return 0; +} + +static size_t __glagol_consume_ascii_digits(const unsigned char *text, size_t index) { + while (text[index] >= '0' && text[index] <= '9') { + index++; + } + return index; +} + +static bool __glagol_string_is_ascii_decimal_f64(const char *text) { + if (text == NULL || text[0] == '\0') { + return false; + } + + const unsigned char *bytes = (const unsigned char *)text; + size_t index = 0; + if (bytes[index] == '-') { + index++; + if (bytes[index] == '\0') { + return false; + } + } + + size_t whole_start = index; + index = __glagol_consume_ascii_digits(bytes, index); + size_t whole_digits = index - whole_start; + + if (whole_digits == 0 || bytes[index] != '.') { + return false; + } + + index++; + size_t fractional_start = index; + index = __glagol_consume_ascii_digits(bytes, index); + size_t fractional_digits = index - fractional_start; + if (fractional_digits == 0) { + return false; + } + + return bytes[index] == '\0'; +} + +int32_t __glagol_string_parse_f64_result(const char *text, double *out) { + if (out == NULL || !__glagol_string_is_ascii_decimal_f64(text)) { + return 1; + } + + errno = 0; + char *end = NULL; + double value = strtod(text, &end); + if (errno == ERANGE || end == text || end == NULL || *end != '\0' || !isfinite(value)) { + return 1; + } + + *out = value; + return 0; +} + +int32_t __glagol_string_parse_bool_result(const char *text, bool *out) { + if (out == NULL || text == NULL) { + return 1; + } + + if (strcmp(text, "true") == 0) { + *out = true; + return 0; + } + + if (strcmp(text, "false") == 0) { + *out = false; + return 0; + } + + return 1; +} + +static void __glagol_vec_i32_allocation_trap(void) { + fputs("slovo runtime error: vector allocation failed\n", stderr); + exit(1); +} + +static void __glagol_vec_i32_index_trap(void) { + fputs("slovo runtime error: vector index out of bounds\n", stderr); + exit(1); +} + +__glagol_vec_i32 *__glagol_vec_i32_empty(void) { + __glagol_vec_i32 *value = malloc(sizeof(__glagol_vec_i32)); + if (value == NULL) { + __glagol_vec_i32_allocation_trap(); + } + + value->len = 0; + value->data = NULL; + return value; +} + +__glagol_vec_i32 *__glagol_vec_i32_append(const __glagol_vec_i32 *source, int32_t element) { + if (source->len < 0 || source->len == INT32_MAX) { + __glagol_vec_i32_allocation_trap(); + } + + size_t old_len = (size_t)source->len; + size_t new_len = old_len + 1; + if (new_len > SIZE_MAX / sizeof(int32_t)) { + __glagol_vec_i32_allocation_trap(); + } + + __glagol_vec_i32 *value = malloc(sizeof(__glagol_vec_i32)); + if (value == NULL) { + __glagol_vec_i32_allocation_trap(); + } + + int32_t *data = malloc(new_len * sizeof(int32_t)); + if (data == NULL) { + free(value); + __glagol_vec_i32_allocation_trap(); + } + + if (old_len > 0) { + memcpy(data, source->data, old_len * sizeof(int32_t)); + } + data[old_len] = element; + + value->len = (int32_t)new_len; + value->data = data; + return value; +} + +int32_t __glagol_vec_i32_len(const __glagol_vec_i32 *value) { + return value->len; +} + +int32_t __glagol_vec_i32_index(const __glagol_vec_i32 *value, int32_t index) { + if (index < 0 || index >= value->len) { + __glagol_vec_i32_index_trap(); + } + return value->data[index]; +} + +bool __glagol_vec_i32_eq(const __glagol_vec_i32 *left, const __glagol_vec_i32 *right) { + if (left->len != right->len) { + return false; + } + if (left->len == 0) { + return true; + } + return memcmp(left->data, right->data, (size_t)left->len * sizeof(int32_t)) == 0; +} + +static void __glagol_vec_i64_allocation_trap(void) { + fputs("slovo runtime error: vector allocation failed\n", stderr); + exit(1); +} + +static void __glagol_vec_i64_index_trap(void) { + fputs("slovo runtime error: vector index out of bounds\n", stderr); + exit(1); +} + +__glagol_vec_i64 *__glagol_vec_i64_empty(void) { + __glagol_vec_i64 *value = malloc(sizeof(__glagol_vec_i64)); + if (value == NULL) { + __glagol_vec_i64_allocation_trap(); + } + + value->len = 0; + value->data = NULL; + return value; +} + +__glagol_vec_i64 *__glagol_vec_i64_append(const __glagol_vec_i64 *source, int64_t element) { + if (source->len < 0 || source->len == INT32_MAX) { + __glagol_vec_i64_allocation_trap(); + } + + size_t old_len = (size_t)source->len; + size_t new_len = old_len + 1; + if (new_len > SIZE_MAX / sizeof(int64_t)) { + __glagol_vec_i64_allocation_trap(); + } + + __glagol_vec_i64 *value = malloc(sizeof(__glagol_vec_i64)); + if (value == NULL) { + __glagol_vec_i64_allocation_trap(); + } + + int64_t *data = malloc(new_len * sizeof(int64_t)); + if (data == NULL) { + free(value); + __glagol_vec_i64_allocation_trap(); + } + + if (old_len > 0) { + memcpy(data, source->data, old_len * sizeof(int64_t)); + } + data[old_len] = element; + + value->len = (int32_t)new_len; + value->data = data; + return value; +} + +int32_t __glagol_vec_i64_len(const __glagol_vec_i64 *value) { + return value->len; +} + +int64_t __glagol_vec_i64_index(const __glagol_vec_i64 *value, int32_t index) { + if (index < 0 || index >= value->len) { + __glagol_vec_i64_index_trap(); + } + return value->data[index]; +} + +bool __glagol_vec_i64_eq(const __glagol_vec_i64 *left, const __glagol_vec_i64 *right) { + if (left->len != right->len) { + return false; + } + if (left->len == 0) { + return true; + } + return memcmp(left->data, right->data, (size_t)left->len * sizeof(int64_t)) == 0; +} + +static void __glagol_vec_f64_allocation_trap(void) { + fputs("slovo runtime error: vector allocation failed\n", stderr); + exit(1); +} + +static void __glagol_vec_f64_index_trap(void) { + fputs("slovo runtime error: vector index out of bounds\n", stderr); + exit(1); +} + +__glagol_vec_f64 *__glagol_vec_f64_empty(void) { + __glagol_vec_f64 *value = malloc(sizeof(__glagol_vec_f64)); + if (value == NULL) { + __glagol_vec_f64_allocation_trap(); + } + + value->len = 0; + value->data = NULL; + return value; +} + +__glagol_vec_f64 *__glagol_vec_f64_append(const __glagol_vec_f64 *source, double element) { + if (source->len < 0 || source->len == INT32_MAX) { + __glagol_vec_f64_allocation_trap(); + } + + size_t old_len = (size_t)source->len; + size_t new_len = old_len + 1; + if (new_len > SIZE_MAX / sizeof(double)) { + __glagol_vec_f64_allocation_trap(); + } + + __glagol_vec_f64 *value = malloc(sizeof(__glagol_vec_f64)); + if (value == NULL) { + __glagol_vec_f64_allocation_trap(); + } + + double *data = malloc(new_len * sizeof(double)); + if (data == NULL) { + free(value); + __glagol_vec_f64_allocation_trap(); + } + + if (old_len > 0) { + memcpy(data, source->data, old_len * sizeof(double)); + } + data[old_len] = element; + + value->len = (int32_t)new_len; + value->data = data; + return value; +} + +int32_t __glagol_vec_f64_len(const __glagol_vec_f64 *value) { + return value->len; +} + +double __glagol_vec_f64_index(const __glagol_vec_f64 *value, int32_t index) { + if (index < 0 || index >= value->len) { + __glagol_vec_f64_index_trap(); + } + return value->data[index]; +} + +bool __glagol_vec_f64_eq(const __glagol_vec_f64 *left, const __glagol_vec_f64 *right) { + if (left->len != right->len) { + return false; + } + for (int32_t index = 0; index < left->len; index++) { + if (left->data[index] != right->data[index]) { + return false; + } + } + return true; +} + +static void __glagol_vec_bool_allocation_trap(void) { + fputs("slovo runtime error: vector allocation failed\n", stderr); + exit(1); +} + +static void __glagol_vec_bool_index_trap(void) { + fputs("slovo runtime error: vector index out of bounds\n", stderr); + exit(1); +} + +__glagol_vec_bool *__glagol_vec_bool_empty(void) { + __glagol_vec_bool *value = malloc(sizeof(__glagol_vec_bool)); + if (value == NULL) { + __glagol_vec_bool_allocation_trap(); + } + + value->len = 0; + value->data = NULL; + return value; +} + +__glagol_vec_bool *__glagol_vec_bool_append(const __glagol_vec_bool *source, bool element) { + if (source->len < 0 || source->len == INT32_MAX) { + __glagol_vec_bool_allocation_trap(); + } + + size_t old_len = (size_t)source->len; + size_t new_len = old_len + 1; + if (new_len > SIZE_MAX / sizeof(bool)) { + __glagol_vec_bool_allocation_trap(); + } + + __glagol_vec_bool *value = malloc(sizeof(__glagol_vec_bool)); + if (value == NULL) { + __glagol_vec_bool_allocation_trap(); + } + + bool *data = malloc(new_len * sizeof(bool)); + if (data == NULL) { + free(value); + __glagol_vec_bool_allocation_trap(); + } + + if (old_len > 0) { + memcpy(data, source->data, old_len * sizeof(bool)); + } + data[old_len] = element; + + value->len = (int32_t)new_len; + value->data = data; + return value; +} + +int32_t __glagol_vec_bool_len(const __glagol_vec_bool *value) { + return value->len; +} + +bool __glagol_vec_bool_index(const __glagol_vec_bool *value, int32_t index) { + if (index < 0 || index >= value->len) { + __glagol_vec_bool_index_trap(); + } + return value->data[index]; +} + +bool __glagol_vec_bool_eq(const __glagol_vec_bool *left, const __glagol_vec_bool *right) { + if (left->len != right->len) { + return false; + } + if (left->len == 0) { + return true; + } + return memcmp(left->data, right->data, (size_t)left->len * sizeof(bool)) == 0; +} + +static void __glagol_vec_string_allocation_trap(void) { + fputs("slovo runtime error: vector allocation failed\n", stderr); + exit(1); +} + +static void __glagol_vec_string_index_trap(void) { + fputs("slovo runtime error: vector index out of bounds\n", stderr); + exit(1); +} + +__glagol_vec_string *__glagol_vec_string_empty(void) { + __glagol_vec_string *value = malloc(sizeof(__glagol_vec_string)); + if (value == NULL) { + __glagol_vec_string_allocation_trap(); + } + + value->len = 0; + value->data = NULL; + return value; +} + +__glagol_vec_string *__glagol_vec_string_append( + const __glagol_vec_string *source, + const char *element +) { + if (source->len < 0 || source->len == INT32_MAX) { + __glagol_vec_string_allocation_trap(); + } + + size_t old_len = (size_t)source->len; + size_t new_len = old_len + 1; + if (new_len > SIZE_MAX / sizeof(char *)) { + __glagol_vec_string_allocation_trap(); + } + + __glagol_vec_string *value = malloc(sizeof(__glagol_vec_string)); + if (value == NULL) { + __glagol_vec_string_allocation_trap(); + } + + char **data = malloc(new_len * sizeof(char *)); + if (data == NULL) { + free(value); + __glagol_vec_string_allocation_trap(); + } + + if (old_len > 0) { + memcpy(data, source->data, old_len * sizeof(char *)); + } + data[old_len] = (char *)element; + + value->len = (int32_t)new_len; + value->data = data; + return value; +} + +int32_t __glagol_vec_string_len(const __glagol_vec_string *value) { + return value->len; +} + +char *__glagol_vec_string_index(const __glagol_vec_string *value, int32_t index) { + if (index < 0 || index >= value->len) { + __glagol_vec_string_index_trap(); + } + return value->data[index]; +} + +bool __glagol_vec_string_eq(const __glagol_vec_string *left, const __glagol_vec_string *right) { + if (left->len != right->len) { + return false; + } + for (int32_t index = 0; index < left->len; index++) { + if (strcmp(left->data[index], right->data[index]) != 0) { + return false; + } + } + return true; +} + +static void __glagol_time_monotonic_unavailable_trap(void) { + fputs("slovo runtime error: monotonic time unavailable\n", stderr); + exit(1); +} + +static void __glagol_time_sleep_negative_trap(void) { + fputs("slovo runtime error: sleep_ms negative duration\n", stderr); + exit(1); +} + +static void __glagol_time_sleep_failed_trap(void) { + fputs("slovo runtime error: sleep failed\n", stderr); + exit(1); +} + +int32_t __glagol_time_monotonic_ms(void) { + struct timespec now; + if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) { + __glagol_time_monotonic_unavailable_trap(); + } + + if (!__glagol_time_base_initialized) { + __glagol_time_base = now; + __glagol_time_base_initialized = true; + return 0; + } + + time_t seconds = now.tv_sec - __glagol_time_base.tv_sec; + long nanoseconds = now.tv_nsec - __glagol_time_base.tv_nsec; + if (nanoseconds < 0) { + seconds -= 1; + nanoseconds += 1000000000L; + } + if (seconds < 0) { + return 0; + } + if (seconds > INT32_MAX / 1000) { + return INT32_MAX; + } + + int64_t milliseconds = (int64_t)seconds * 1000 + nanoseconds / 1000000L; + if (milliseconds > INT32_MAX) { + return INT32_MAX; + } + return (int32_t)milliseconds; +} + +void __glagol_time_sleep_ms(int32_t ms) { + if (ms < 0) { + __glagol_time_sleep_negative_trap(); + } + + struct timespec remaining; + remaining.tv_sec = ms / 1000; + remaining.tv_nsec = (long)(ms % 1000) * 1000000L; + + while (nanosleep(&remaining, &remaining) != 0) { + if (errno != EINTR) { + __glagol_time_sleep_failed_trap(); + } + } +} + +static void __glagol_random_i32_unavailable_trap(void) { + fputs("slovo runtime error: random i32 unavailable\n", stderr); + exit(1); +} + +int32_t __glagol_random_i32(void) { + int fd = open("/dev/urandom", O_RDONLY); + if (fd < 0) { + __glagol_random_i32_unavailable_trap(); + } + + uint32_t value = 0; + unsigned char *cursor = (unsigned char *)&value; + size_t remaining = sizeof(value); + while (remaining > 0) { + ssize_t n = read(fd, cursor, remaining); + if (n < 0) { + if (errno == EINTR) { + continue; + } + close(fd); + __glagol_random_i32_unavailable_trap(); + } + if (n == 0) { + close(fd); + __glagol_random_i32_unavailable_trap(); + } + cursor += n; + remaining -= (size_t)n; + } + + if (close(fd) != 0) { + __glagol_random_i32_unavailable_trap(); + } + return (int32_t)(value & INT32_MAX); +} + +void __glagol_array_bounds_trap(void) { + fputs("slovo runtime error: array index out of bounds\n", stderr); + exit(1); +} + +void __glagol_unwrap_some_trap(void) { + fputs("slovo runtime error: unwrap_some on none\n", stderr); + exit(1); +} + +void __glagol_unwrap_ok_trap(void) { + fputs("slovo runtime error: unwrap_ok on err\n", stderr); + exit(1); +} + +void __glagol_unwrap_err_trap(void) { + fputs("slovo runtime error: unwrap_err on ok\n", stderr); + exit(1); +} diff --git a/scripts/conformance-gate.sh b/scripts/conformance-gate.sh new file mode 100755 index 0000000..31924b0 --- /dev/null +++ b/scripts/conformance-gate.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -euo pipefail + +script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +repo_root="$(cd -- "${script_dir}/.." && pwd)" +compiler_dir="${repo_root}/compiler" + +# Focused standard-runtime conformance gate. scripts/release-gate.sh remains the full +# release gate and is not replaced by this script. +git -C "${repo_root}" diff --check + +cd "${compiler_dir}" +cargo fmt --check +cargo test --test standard_runtime_conformance_alignment +cargo test --test standard_time +cargo test --test promotion_gate promotion_gate_artifacts_are_aligned diff --git a/scripts/release-gate.sh b/scripts/release-gate.sh new file mode 100755 index 0000000..cb65ecd --- /dev/null +++ b/scripts/release-gate.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -euo pipefail + +script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +repo_root="$(cd -- "${script_dir}/.." && pwd)" +compiler_dir="${repo_root}/compiler" + +cd "${repo_root}" +git diff --check +"${repo_root}/scripts/render-doc-pdfs.sh" +git diff --check +git diff --exit-code -- docs/papers + +cd "${compiler_dir}" +cargo fmt --check +# Full cargo test includes unignored integration gates such as dx_v1_7, +# beta_v2_0_0_beta_1, and beta_1_0_0. +cargo test +cargo test --test promotion_gate -- --ignored +cargo test --test binary_smoke -- --ignored +cargo test --test llvm_smoke -- --ignored diff --git a/scripts/render-doc-pdfs.js b/scripts/render-doc-pdfs.js new file mode 100755 index 0000000..6e614d6 --- /dev/null +++ b/scripts/render-doc-pdfs.js @@ -0,0 +1,159 @@ +#!/usr/bin/env node +const fs = require("fs"); +const path = require("path"); +const { execFileSync } = require("child_process"); + +const scriptDir = __dirname; +const repoRoot = path.resolve(scriptDir, ".."); +const docsDir = path.join(repoRoot, "docs"); +const homeDir = process.env.HOME || ""; + +function firstLine(text) { + return ( + text + .split(/\r?\n/) + .map((line) => line.trim()) + .find(Boolean) || "" + ); +} + +function findOne(root, matchArgs) { + if (!root || !fs.existsSync(root)) { + return ""; + } + + try { + return firstLine( + execFileSync("find", [root, ...matchArgs], { + encoding: "utf8", + }), + ); + } catch { + return ""; + } +} + +function resolveMdToPdfPackage() { + const explicit = process.env.MD_TO_PDF_PACKAGE; + if (explicit && fs.existsSync(explicit)) { + return explicit; + } + + const found = findOne(path.join(homeDir, ".npm", "_npx"), [ + "-path", + "*/node_modules/md-to-pdf", + "-type", + "d", + ]); + if (found) { + return found; + } + + throw new Error( + "unable to find md-to-pdf package; set MD_TO_PDF_PACKAGE to a node_modules/md-to-pdf path", + ); +} + +function resolveChromePath() { + const envPaths = [ + process.env.MD_TO_PDF_CHROME, + process.env.CHROME_HEADLESS_SHELL, + ]; + + for (const candidate of envPaths) { + if (candidate && fs.existsSync(candidate)) { + return candidate; + } + } + + return findOne(path.join(homeDir, ".cache", "puppeteer"), [ + "-path", + "*/chrome-headless-shell", + "-type", + "f", + ]); +} + +function loadCssWithEmbeddedFont() { + const cssPath = path.join(docsDir, "pdf.css"); + const fontPath = path.join( + docsDir, + "assets", + "fonts", + "NotoSansGlagolitic-Regular.ttf", + ); + const fontBase64 = fs.readFileSync(fontPath).toString("base64"); + const css = fs.readFileSync(cssPath, "utf8"); + + return css.replace( + 'url("assets/fonts/NotoSansGlagolitic-Regular.ttf")', + `url("data:font/truetype;base64,${fontBase64}")`, + ); +} + +async function main() { + const mdToPdfPackage = resolveMdToPdfPackage(); + const { mdToPdf } = require(mdToPdfPackage); + const chromePath = resolveChromePath(); + const baseOptions = { + basedir: repoRoot, + css: loadCssWithEmbeddedFont(), + launch_options: { + args: ["--no-sandbox", "--disable-setuid-sandbox"], + }, + pdf_options: { + format: "A4", + printBackground: true, + margin: { + top: "18mm", + right: "16mm", + bottom: "18mm", + left: "16mm", + }, + }, + }; + + if (chromePath) { + baseOptions.launch_options.executablePath = chromePath; + } + + const jobs = [ + { + source: "docs/papers/SLOVO_WHITEPAPER.md", + dest: "docs/papers/SLOVO_WHITEPAPER.pdf", + }, + { + source: "docs/language/MANIFEST.md", + dest: "docs/papers/SLOVO_MANIFEST.pdf", + }, + { + source: "docs/papers/GLAGOL_WHITEPAPER.md", + dest: "docs/papers/GLAGOL_WHITEPAPER.pdf", + }, + { + source: "docs/compiler/GLAGOL_COMPILER_MANIFEST.md", + dest: "docs/papers/GLAGOL_COMPILER_MANIFEST.pdf", + }, + ]; + + for (const job of jobs) { + const result = await mdToPdf( + { + path: path.join(repoRoot, job.source), + }, + { + ...baseOptions, + dest: path.join(repoRoot, job.dest), + }, + ); + if (!result) { + throw new Error(`failed to render ${job.source}`); + } + console.log(job.dest); + } +} + +main().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/scripts/render-doc-pdfs.sh b/scripts/render-doc-pdfs.sh new file mode 100755 index 0000000..9a87ab1 --- /dev/null +++ b/scripts/render-doc-pdfs.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -euo pipefail + +script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" + +node "${script_dir}/render-doc-pdfs.js" diff --git a/tests/add.checked.lower b/tests/add.checked.lower new file mode 100644 index 0000000..3881ca7 --- /dev/null +++ b/tests/add.checked.lower @@ -0,0 +1,11 @@ +program main + fn add(a: i32, b: i32) -> i32 + binary + : i32 + var a : i32 + var b : i32 + fn main() -> i32 + call print_i32 : unit + call add : i32 + int 20 : i32 + int 22 : i32 + int 0 : i32 diff --git a/tests/add.expected.ll b/tests/add.expected.ll new file mode 100644 index 0000000..f01e30d --- /dev/null +++ b/tests/add.expected.ll @@ -0,0 +1,17 @@ +; Approximate expected shape. +; Exact temporary register names may differ. + +declare void @print_i32(i32) + +define i32 @add(i32 %a, i32 %b) { +entry: + %0 = add i32 %a, %b + ret i32 %0 +} + +define i32 @main() { +entry: + %0 = call i32 @add(i32 20, i32 22) + call void @print_i32(i32 %0) + ret i32 0 +} diff --git a/tests/add.sexpr b/tests/add.sexpr new file mode 100644 index 0000000..4f5eac3 --- /dev/null +++ b/tests/add.sexpr @@ -0,0 +1,32 @@ +list + ident module + ident main +list + ident fn + ident add + list + list + ident a + ident i32 + list + ident b + ident i32 + arrow -> + ident i32 + list + ident + + ident a + ident b +list + ident fn + ident main + list + arrow -> + ident i32 + list + ident print_i32 + list + ident add + int 20 + int 22 + int 0 diff --git a/tests/add.surface.lower b/tests/add.surface.lower new file mode 100644 index 0000000..b7955f4 --- /dev/null +++ b/tests/add.surface.lower @@ -0,0 +1,11 @@ +program main + fn add(a: i32, b: i32) -> i32 + binary + + var a + var b + fn main() -> i32 + call print_i32 + call add + int 20 + int 22 + int 0 diff --git a/tests/arity-mismatch.diag b/tests/arity-mismatch.diag new file mode 100644 index 0000000..f6cc5a2 --- /dev/null +++ b/tests/arity-mismatch.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ArityMismatch) + (message "function `add` called with wrong number of arguments") + (file "") + (span + (bytes 82 89) + (range 8 3 8 10) + ) + (expected "2") + (found "1") +) diff --git a/tests/array-argument-type-mismatch.diag b/tests/array-argument-type-mismatch.diag new file mode 100644 index 0000000..5bd58a7 --- /dev/null +++ b/tests/array-argument-type-mismatch.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code TypeMismatch) + (message "cannot call `first` with argument of wrong type") + (file "") + (span + (bytes 107 120) + (range 8 10 8 23) + ) + (expected "(array i32 2)") + (found "(array i32 1)") +) diff --git a/tests/array-direct-scalars-value-flow.checked.lower b/tests/array-direct-scalars-value-flow.checked.lower new file mode 100644 index 0000000..8982496 --- /dev/null +++ b/tests/array-direct-scalars-value-flow.checked.lower @@ -0,0 +1,105 @@ +program main + fn make_i32_values(base: i32) -> (array i32 3) + array : (array i32 3) + var base : i32 + binary + : i32 + var base : i32 + int 1 : i32 + binary + : i32 + var base : i32 + int 2 : i32 + fn make_i64_values(base: i64) -> (array i64 3) + array : (array i64 3) + var base : i64 + binary + : i64 + var base : i64 + i64 1 : i64 + binary + : i64 + var base : i64 + i64 2 : i64 + fn make_f64_values(base: f64) -> (array f64 3) + array : (array f64 3) + var base : f64 + binary + : f64 + var base : f64 + float 1 : f64 + binary + : f64 + var base : f64 + float 2 : f64 + fn make_flags(first: bool) -> (array bool 3) + array : (array bool 3) + var first : bool + bool false : bool + bool true : bool + fn i32_at(values: (array i32 3), i: i32) -> i32 + index : i32 + var values : (array i32 3) + var i : i32 + fn i64_at(values: (array i64 3), i: i32) -> i64 + index : i64 + var values : (array i64 3) + var i : i32 + fn f64_at(values: (array f64 3), i: i32) -> f64 + index : f64 + var values : (array f64 3) + var i : i32 + fn bool_at(values: (array bool 3), i: i32) -> bool + index : bool + var values : (array bool 3) + var i : i32 + fn i64_local_array_flow(i: i32) -> i64 + local let values : unit + call make_i64_values : (array i64 3) + i64 40 : i64 + call i64_at : i64 + var values : (array i64 3) + var i : i32 + fn f64_parameter_local_copy(values: (array f64 3), i: i32) -> f64 + local let copy : unit + var values : (array f64 3) + index : f64 + var copy : (array f64 3) + var i : i32 + fn echo_flags(values: (array bool 3)) -> (array bool 3) + var values : (array bool 3) + fn bool_call_return_index(i: i32) -> bool + index : bool + call make_flags : (array bool 3) + bool true : bool + var i : i32 + fn main() -> i32 + if : i32 + call bool_call_return_index : bool + int 0 : i32 + int 0 : i32 + int 1 : i32 + test "i32 array parameter value flow" + binary = : bool + call i32_at : i32 + call make_i32_values : (array i32 3) + int 7 : i32 + int 0 : i32 + int 7 : i32 + test "i64 array local call value flow" + binary = : bool + call i64_local_array_flow : i64 + int 2 : i32 + i64 42 : i64 + test "f64 array parameter local copy" + binary = : bool + call f64_parameter_local_copy : f64 + call make_f64_values : (array f64 3) + float 4 : f64 + int 1 : i32 + float 5 : f64 + test "bool array dynamic index" + call bool_at : bool + call make_flags : (array bool 3) + bool true : bool + int 2 : i32 + test "bool array return call value flow" + index : bool + call echo_flags : (array bool 3) + call make_flags : (array bool 3) + bool false : bool + int 2 : i32 diff --git a/tests/array-direct-scalars-value-flow.slo b/tests/array-direct-scalars-value-flow.slo new file mode 100644 index 0000000..fcfae37 --- /dev/null +++ b/tests/array-direct-scalars-value-flow.slo @@ -0,0 +1,59 @@ +(module main) + +(fn make_i32_values ((base i32)) -> (array i32 3) + (array i32 base (+ base 1) (+ base 2))) + +(fn make_i64_values ((base i64)) -> (array i64 3) + (array i64 base (+ base 1i64) (+ base 2i64))) + +(fn make_f64_values ((base f64)) -> (array f64 3) + (array f64 base (+ base 1.0) (+ base 2.0))) + +(fn make_flags ((first bool)) -> (array bool 3) + (array bool first false true)) + +(fn i32_at ((values (array i32 3)) (i i32)) -> i32 + (index values i)) + +(fn i64_at ((values (array i64 3)) (i i32)) -> i64 + (index values i)) + +(fn f64_at ((values (array f64 3)) (i i32)) -> f64 + (index values i)) + +(fn bool_at ((values (array bool 3)) (i i32)) -> bool + (index values i)) + +(fn i64_local_array_flow ((i i32)) -> i64 + (let values (array i64 3) (make_i64_values 40i64)) + (i64_at values i)) + +(fn f64_parameter_local_copy ((values (array f64 3)) (i i32)) -> f64 + (let copy (array f64 3) values) + (index copy i)) + +(fn echo_flags ((values (array bool 3))) -> (array bool 3) + values) + +(fn bool_call_return_index ((i i32)) -> bool + (index (make_flags true) i)) + +(test "i32 array parameter value flow" + (= (i32_at (make_i32_values 7) 0) 7)) + +(test "i64 array local call value flow" + (= (i64_local_array_flow 2) 42i64)) + +(test "f64 array parameter local copy" + (= (f64_parameter_local_copy (make_f64_values 4.0) 1) 5.0)) + +(test "bool array dynamic index" + (bool_at (make_flags true) 2)) + +(test "bool array return call value flow" + (index (echo_flags (make_flags false)) 2)) + +(fn main () -> i32 + (if (bool_call_return_index 0) + 0 + 1)) diff --git a/tests/array-direct-scalars-value-flow.surface.lower b/tests/array-direct-scalars-value-flow.surface.lower new file mode 100644 index 0000000..110a8dd --- /dev/null +++ b/tests/array-direct-scalars-value-flow.surface.lower @@ -0,0 +1,105 @@ +program main + fn make_i32_values(base: i32) -> (array i32 3) + array i32 + var base + binary + + var base + int 1 + binary + + var base + int 2 + fn make_i64_values(base: i64) -> (array i64 3) + array i64 + var base + binary + + var base + i64 1 + binary + + var base + i64 2 + fn make_f64_values(base: f64) -> (array f64 3) + array f64 + var base + binary + + var base + float 1 + binary + + var base + float 2 + fn make_flags(first: bool) -> (array bool 3) + array bool + var first + bool false + bool true + fn i32_at(values: (array i32 3), i: i32) -> i32 + index + var values + var i + fn i64_at(values: (array i64 3), i: i32) -> i64 + index + var values + var i + fn f64_at(values: (array f64 3), i: i32) -> f64 + index + var values + var i + fn bool_at(values: (array bool 3), i: i32) -> bool + index + var values + var i + fn i64_local_array_flow(i: i32) -> i64 + local let values: (array i64 3) + call make_i64_values + i64 40 + call i64_at + var values + var i + fn f64_parameter_local_copy(values: (array f64 3), i: i32) -> f64 + local let copy: (array f64 3) + var values + index + var copy + var i + fn echo_flags(values: (array bool 3)) -> (array bool 3) + var values + fn bool_call_return_index(i: i32) -> bool + index + call make_flags + bool true + var i + fn main() -> i32 + if + call bool_call_return_index + int 0 + int 0 + int 1 + test "i32 array parameter value flow" + binary = + call i32_at + call make_i32_values + int 7 + int 0 + int 7 + test "i64 array local call value flow" + binary = + call i64_local_array_flow + int 2 + i64 42 + test "f64 array parameter local copy" + binary = + call f64_parameter_local_copy + call make_f64_values + float 4 + int 1 + float 5 + test "bool array dynamic index" + call bool_at + call make_flags + bool true + int 2 + test "bool array return call value flow" + index + call echo_flags + call make_flags + bool false + int 2 diff --git a/tests/array-direct-scalars.checked.lower b/tests/array-direct-scalars.checked.lower new file mode 100644 index 0000000..e6270ef --- /dev/null +++ b/tests/array-direct-scalars.checked.lower @@ -0,0 +1,52 @@ +program main + fn i32_second() -> i32 + index : i32 + array : (array i32 3) + int 10 : i32 + int 20 : i32 + int 30 : i32 + int 1 : i32 + fn i64_local_pick() -> i64 + local let values : unit + array : (array i64 3) + i64 4 : i64 + i64 5 : i64 + i64 6 : i64 + index : i64 + var values : (array i64 3) + int 2 : i32 + fn f64_third() -> f64 + index : f64 + array : (array f64 3) + float 1.5 : f64 + float 2.5 : f64 + float 3.5 : f64 + int 2 : i32 + fn bool_local_pick() -> bool + local let flags : unit + array : (array bool 3) + bool false : bool + bool true : bool + bool false : bool + index : bool + var flags : (array bool 3) + int 1 : i32 + fn main() -> i32 + if : i32 + call bool_local_pick : bool + call i32_second : i32 + int 0 : i32 + test "i32 direct scalar array index" + binary = : bool + call i32_second : i32 + int 20 : i32 + test "i64 local direct scalar array index" + binary = : bool + call i64_local_pick : i64 + i64 6 : i64 + test "f64 direct scalar array index" + binary = : bool + call f64_third : f64 + float 3.5 : f64 + test "bool local direct scalar array index" + call bool_local_pick : bool diff --git a/tests/array-direct-scalars.slo b/tests/array-direct-scalars.slo new file mode 100644 index 0000000..cb2a494 --- /dev/null +++ b/tests/array-direct-scalars.slo @@ -0,0 +1,32 @@ +(module main) + +(fn i32_second () -> i32 + (index (array i32 10 20 30) 1)) + +(fn i64_local_pick () -> i64 + (let values (array i64 3) (array i64 4i64 5i64 6i64)) + (index values 2)) + +(fn f64_third () -> f64 + (index (array f64 1.5 2.5 3.5) 2)) + +(fn bool_local_pick () -> bool + (let flags (array bool 3) (array bool false true false)) + (index flags 1)) + +(test "i32 direct scalar array index" + (= (i32_second) 20)) + +(test "i64 local direct scalar array index" + (= (i64_local_pick) 6i64)) + +(test "f64 direct scalar array index" + (= (f64_third) 3.5)) + +(test "bool local direct scalar array index" + (bool_local_pick)) + +(fn main () -> i32 + (if (bool_local_pick) + (i32_second) + 0)) diff --git a/tests/array-direct-scalars.surface.lower b/tests/array-direct-scalars.surface.lower new file mode 100644 index 0000000..e836803 --- /dev/null +++ b/tests/array-direct-scalars.surface.lower @@ -0,0 +1,52 @@ +program main + fn i32_second() -> i32 + index + array i32 + int 10 + int 20 + int 30 + int 1 + fn i64_local_pick() -> i64 + local let values: (array i64 3) + array i64 + i64 4 + i64 5 + i64 6 + index + var values + int 2 + fn f64_third() -> f64 + index + array f64 + float 1.5 + float 2.5 + float 3.5 + int 2 + fn bool_local_pick() -> bool + local let flags: (array bool 3) + array bool + bool false + bool true + bool false + index + var flags + int 1 + fn main() -> i32 + if + call bool_local_pick + call i32_second + int 0 + test "i32 direct scalar array index" + binary = + call i32_second + int 20 + test "i64 local direct scalar array index" + binary = + call i64_local_pick + i64 6 + test "f64 direct scalar array index" + binary = + call f64_third + float 3.5 + test "bool local direct scalar array index" + call bool_local_pick diff --git a/tests/array-enum.checked.lower b/tests/array-enum.checked.lower new file mode 100644 index 0000000..20bbbd0 --- /dev/null +++ b/tests/array-enum.checked.lower @@ -0,0 +1,54 @@ +program main + enum Color + variant Red + variant Blue + variant Green + fn make_palette() -> (array Color 3) + array : (array Color 3) + enum-variant Color.Red #0 : Color + enum-variant Color.Blue #1 : Color + enum-variant Color.Green #2 : Color + fn echo_palette(colors: (array Color 3)) -> (array Color 3) + var colors : (array Color 3) + fn at(colors: (array Color 3), i: i32) -> Color + index : Color + var colors : (array Color 3) + var i : i32 + fn local_pick() -> Color + local let colors : unit + call make_palette : (array Color 3) + index : Color + var colors : (array Color 3) + int 1 : i32 + fn main() -> i32 + match : i32 + subject + call at : Color + call make_palette : (array Color 3) + int 1 : i32 + arm Color.Blue + int 0 : i32 + arm Color.Red + int 1 : i32 + arm Color.Green + int 1 : i32 + test "enum array immediate index" + binary = : bool + index : Color + array : (array Color 3) + enum-variant Color.Red #0 : Color + enum-variant Color.Blue #1 : Color + enum-variant Color.Green #2 : Color + int 2 : i32 + enum-variant Color.Green #2 : Color + test "enum array local index" + binary = : bool + call local_pick : Color + enum-variant Color.Blue #1 : Color + test "enum array param return dynamic index" + binary = : bool + call at : Color + call echo_palette : (array Color 3) + call make_palette : (array Color 3) + int 0 : i32 + enum-variant Color.Red #0 : Color diff --git a/tests/array-enum.slo b/tests/array-enum.slo new file mode 100644 index 0000000..3444aa6 --- /dev/null +++ b/tests/array-enum.slo @@ -0,0 +1,34 @@ +(module main) + +(enum Color Red Blue Green) + +(fn make_palette () -> (array Color 3) + (array Color (Color.Red) (Color.Blue) (Color.Green))) + +(fn echo_palette ((colors (array Color 3))) -> (array Color 3) + colors) + +(fn at ((colors (array Color 3)) (i i32)) -> Color + (index colors i)) + +(fn local_pick () -> Color + (let colors (array Color 3) (make_palette)) + (index colors 1)) + +(test "enum array immediate index" + (= (index (array Color (Color.Red) (Color.Blue) (Color.Green)) 2) (Color.Green))) + +(test "enum array local index" + (= (local_pick) (Color.Blue))) + +(test "enum array param return dynamic index" + (= (at (echo_palette (make_palette)) 0) (Color.Red))) + +(fn main () -> i32 + (match (at (make_palette) 1) + ((Color.Blue) + 0) + ((Color.Red) + 1) + ((Color.Green) + 1))) diff --git a/tests/array-enum.surface.lower b/tests/array-enum.surface.lower new file mode 100644 index 0000000..905875e --- /dev/null +++ b/tests/array-enum.surface.lower @@ -0,0 +1,54 @@ +program main + enum Color + variant Red + variant Blue + variant Green + fn make_palette() -> (array Color 3) + array Color + enum-variant Color.Red + enum-variant Color.Blue + enum-variant Color.Green + fn echo_palette(colors: (array Color 3)) -> (array Color 3) + var colors + fn at(colors: (array Color 3), i: i32) -> Color + index + var colors + var i + fn local_pick() -> Color + local let colors: (array Color 3) + call make_palette + index + var colors + int 1 + fn main() -> i32 + match + subject + call at + call make_palette + int 1 + arm Color.Blue + int 0 + arm Color.Red + int 1 + arm Color.Green + int 1 + test "enum array immediate index" + binary = + index + array Color + enum-variant Color.Red + enum-variant Color.Blue + enum-variant Color.Green + int 2 + enum-variant Color.Green + test "enum array local index" + binary = + call local_pick + enum-variant Color.Blue + test "enum array param return dynamic index" + binary = + call at + call echo_palette + call make_palette + int 0 + enum-variant Color.Red diff --git a/tests/array-index-not-i32.diag b/tests/array-index-not-i32.diag new file mode 100644 index 0000000..3c161e3 --- /dev/null +++ b/tests/array-index-not-i32.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ArrayIndexNotI32) + (message "`index` offset must be i32") + (file "") + (span + (bytes 58 62) + (range 5 24 5 28) + ) + (expected "i32") + (found "bool") +) diff --git a/tests/array-index-out-of-bounds.diag b/tests/array-index-out-of-bounds.diag new file mode 100644 index 0000000..7a3467b --- /dev/null +++ b/tests/array-index-out-of-bounds.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ArrayIndexOutOfBounds) + (message "array index is outside the fixed array bounds") + (file "") + (span + (bytes 60 61) + (range 5 26 5 27) + ) + (expected "0..1") + (found "2") +) diff --git a/tests/array-local-initializer-type-mismatch.diag b/tests/array-local-initializer-type-mismatch.diag new file mode 100644 index 0000000..8f073a7 --- /dev/null +++ b/tests/array-local-initializer-type-mismatch.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code TypeMismatch) + (message "local `values` initializer has the wrong type") + (file "") + (span + (bytes 63 76) + (range 5 29 5 42) + ) + (expected "(array i32 2)") + (found "(array i32 1)") +) diff --git a/tests/array-return-type-mismatch.diag b/tests/array-return-type-mismatch.diag new file mode 100644 index 0000000..6bb32d8 --- /dev/null +++ b/tests/array-return-type-mismatch.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ReturnTypeMismatch) + (message "function `bad` returns wrong type") + (file "") + (span + (bytes 16 60) + (range 4 1 5 17) + ) + (expected "(array i32 2)") + (found "(array i32 1)") +) diff --git a/tests/array-string-value-flow.checked.lower b/tests/array-string-value-flow.checked.lower new file mode 100644 index 0000000..38c8e73 --- /dev/null +++ b/tests/array-string-value-flow.checked.lower @@ -0,0 +1,72 @@ +program main + fn make_words(head: string) -> (array string 3) + array : (array string 3) + var head : string + string "middle" : string + string "tail" : string + fn at(values: (array string 3), i: i32) -> string + index : string + var values : (array string 3) + var i : i32 + fn echo(values: (array string 3)) -> (array string 3) + var values : (array string 3) + fn call_return_index(i: i32) -> string + index : string + call make_words : (array string 3) + string "call" : string + var i : i32 + fn local_array_flow(i: i32) -> string + local let words : unit + call make_words : (array string 3) + string "local" : string + call at : string + var words : (array string 3) + var i : i32 + fn parameter_local_copy(values: (array string 3), i: i32) -> string + local let copy : unit + var values : (array string 3) + index : string + var copy : (array string 3) + var i : i32 + fn main() -> i32 + if : i32 + binary = : bool + call call_return_index : string + int 1 : i32 + string "middle" : string + int 0 : i32 + int 1 : i32 + test "string array parameter value flow" + binary = : bool + call at : string + call make_words : (array string 3) + string "alpha" : string + int 0 : i32 + string "alpha" : string + test "string array dynamic index" + binary = : bool + call at : string + call make_words : (array string 3) + string "alpha" : string + int 2 : i32 + string "tail" : string + test "string array local call value flow" + binary = : bool + call local_array_flow : string + int 1 : i32 + string "middle" : string + test "string array parameter local copy" + binary = : bool + call parameter_local_copy : string + call make_words : (array string 3) + string "omega" : string + int 0 : i32 + string "omega" : string + test "string array return call value flow" + binary = : bool + index : string + call echo : (array string 3) + call make_words : (array string 3) + string "zeta" : string + int 2 : i32 + string "tail" : string diff --git a/tests/array-string-value-flow.slo b/tests/array-string-value-flow.slo new file mode 100644 index 0000000..75ea05c --- /dev/null +++ b/tests/array-string-value-flow.slo @@ -0,0 +1,41 @@ +(module main) + +(fn make_words ((head string)) -> (array string 3) + (array string head "middle" "tail")) + +(fn at ((values (array string 3)) (i i32)) -> string + (index values i)) + +(fn echo ((values (array string 3))) -> (array string 3) + values) + +(fn call_return_index ((i i32)) -> string + (index (make_words "call") i)) + +(fn local_array_flow ((i i32)) -> string + (let words (array string 3) (make_words "local")) + (at words i)) + +(fn parameter_local_copy ((values (array string 3)) (i i32)) -> string + (let copy (array string 3) values) + (index copy i)) + +(test "string array parameter value flow" + (= (at (make_words "alpha") 0) "alpha")) + +(test "string array dynamic index" + (= (at (make_words "alpha") 2) "tail")) + +(test "string array local call value flow" + (= (local_array_flow 1) "middle")) + +(test "string array parameter local copy" + (= (parameter_local_copy (make_words "omega") 0) "omega")) + +(test "string array return call value flow" + (= (index (echo (make_words "zeta")) 2) "tail")) + +(fn main () -> i32 + (if (= (call_return_index 1) "middle") + 0 + 1)) diff --git a/tests/array-string-value-flow.surface.lower b/tests/array-string-value-flow.surface.lower new file mode 100644 index 0000000..5accf16 --- /dev/null +++ b/tests/array-string-value-flow.surface.lower @@ -0,0 +1,72 @@ +program main + fn make_words(head: string) -> (array string 3) + array string + var head + string "middle" + string "tail" + fn at(values: (array string 3), i: i32) -> string + index + var values + var i + fn echo(values: (array string 3)) -> (array string 3) + var values + fn call_return_index(i: i32) -> string + index + call make_words + string "call" + var i + fn local_array_flow(i: i32) -> string + local let words: (array string 3) + call make_words + string "local" + call at + var words + var i + fn parameter_local_copy(values: (array string 3), i: i32) -> string + local let copy: (array string 3) + var values + index + var copy + var i + fn main() -> i32 + if + binary = + call call_return_index + int 1 + string "middle" + int 0 + int 1 + test "string array parameter value flow" + binary = + call at + call make_words + string "alpha" + int 0 + string "alpha" + test "string array dynamic index" + binary = + call at + call make_words + string "alpha" + int 2 + string "tail" + test "string array local call value flow" + binary = + call local_array_flow + int 1 + string "middle" + test "string array parameter local copy" + binary = + call parameter_local_copy + call make_words + string "omega" + int 0 + string "omega" + test "string array return call value flow" + binary = + index + call echo + call make_words + string "zeta" + int 2 + string "tail" diff --git a/tests/array-string.checked.lower b/tests/array-string.checked.lower new file mode 100644 index 0000000..ba417d2 --- /dev/null +++ b/tests/array-string.checked.lower @@ -0,0 +1,32 @@ +program main + fn immediate_second() -> string + index : string + array : (array string 3) + string "sun" : string + string "moon" : string + string "star" : string + int 1 : i32 + fn local_pick() -> string + local let words : unit + array : (array string 3) + string "red" : string + string "green" : string + string "blue" : string + index : string + var words : (array string 3) + int 2 : i32 + fn main() -> i32 + if : i32 + binary = : bool + call local_pick : string + string "blue" : string + int 0 : i32 + int 1 : i32 + test "string immediate array index" + binary = : bool + call immediate_second : string + string "moon" : string + test "string local array index" + binary = : bool + call local_pick : string + string "blue" : string diff --git a/tests/array-string.slo b/tests/array-string.slo new file mode 100644 index 0000000..cbefcb3 --- /dev/null +++ b/tests/array-string.slo @@ -0,0 +1,19 @@ +(module main) + +(fn immediate_second () -> string + (index (array string "sun" "moon" "star") 1)) + +(fn local_pick () -> string + (let words (array string 3) (array string "red" "green" "blue")) + (index words 2)) + +(test "string immediate array index" + (= (immediate_second) "moon")) + +(test "string local array index" + (= (local_pick) "blue")) + +(fn main () -> i32 + (if (= (local_pick) "blue") + 0 + 1)) diff --git a/tests/array-string.surface.lower b/tests/array-string.surface.lower new file mode 100644 index 0000000..e38f386 --- /dev/null +++ b/tests/array-string.surface.lower @@ -0,0 +1,32 @@ +program main + fn immediate_second() -> string + index + array string + string "sun" + string "moon" + string "star" + int 1 + fn local_pick() -> string + local let words: (array string 3) + array string + string "red" + string "green" + string "blue" + index + var words + int 2 + fn main() -> i32 + if + binary = + call local_pick + string "blue" + int 0 + int 1 + test "string immediate array index" + binary = + call immediate_second + string "moon" + test "string local array index" + binary = + call local_pick + string "blue" diff --git a/tests/array-struct-elements.checked.lower b/tests/array-struct-elements.checked.lower new file mode 100644 index 0000000..029ea3e --- /dev/null +++ b/tests/array-struct-elements.checked.lower @@ -0,0 +1,81 @@ +program main + enum Color + variant Red + variant Blue + variant Green + struct Pixel + field x: i32 + field label: string + field color: Color + fn make_pixels(head: string) -> (array Pixel 3) + array : (array Pixel 3) + construct Pixel : Pixel + field x + int 1 : i32 + field label + var head : string + field color + enum-variant Color.Red #0 : Color + construct Pixel : Pixel + field x + int 2 : i32 + field label + string "mid" : string + field color + enum-variant Color.Blue #1 : Color + construct Pixel : Pixel + field x + int 3 : i32 + field label + string "tail" : string + field color + enum-variant Color.Green #2 : Color + fn echo_pixels(pixels: (array Pixel 3)) -> (array Pixel 3) + var pixels : (array Pixel 3) + fn at(pixels: (array Pixel 3), i: i32) -> Pixel + index : Pixel + var pixels : (array Pixel 3) + var i : i32 + fn first_label(head: string) -> string + field-access label : string + call at : Pixel + call make_pixels : (array Pixel 3) + var head : string + int 0 : i32 + fn last_color(head: string) -> Color + field-access color : Color + index : Pixel + call echo_pixels : (array Pixel 3) + call make_pixels : (array Pixel 3) + var head : string + int 2 : i32 + fn main() -> i32 + match : i32 + subject + call last_color : Color + string "head" : string + arm Color.Green + int 0 : i32 + arm Color.Red + int 1 : i32 + arm Color.Blue + int 1 : i32 + test "struct array string field access" + binary = : bool + call first_label : string + string "head" : string + string "head" : string + test "struct array nested enum field access" + binary = : bool + call last_color : Color + string "head" : string + enum-variant Color.Green #2 : Color + test "struct array local param return call flow" + binary = : bool + field-access x : i32 + call at : Pixel + call echo_pixels : (array Pixel 3) + call make_pixels : (array Pixel 3) + string "sun" : string + int 1 : i32 + int 2 : i32 diff --git a/tests/array-struct-elements.slo b/tests/array-struct-elements.slo new file mode 100644 index 0000000..2f20f49 --- /dev/null +++ b/tests/array-struct-elements.slo @@ -0,0 +1,41 @@ +(module main) + +(enum Color Red Blue Green) + +(struct Pixel + (x i32) + (label string) + (color Color)) + +(fn make_pixels ((head string)) -> (array Pixel 3) + (array Pixel (Pixel (x 1) (label head) (color (Color.Red))) (Pixel (x 2) (label "mid") (color (Color.Blue))) (Pixel (x 3) (label "tail") (color (Color.Green))))) + +(fn echo_pixels ((pixels (array Pixel 3))) -> (array Pixel 3) + pixels) + +(fn at ((pixels (array Pixel 3)) (i i32)) -> Pixel + (index pixels i)) + +(fn first_label ((head string)) -> string + (. (at (make_pixels head) 0) label)) + +(fn last_color ((head string)) -> Color + (. (index (echo_pixels (make_pixels head)) 2) color)) + +(test "struct array string field access" + (= (first_label "head") "head")) + +(test "struct array nested enum field access" + (= (last_color "head") (Color.Green))) + +(test "struct array local param return call flow" + (= (. (at (echo_pixels (make_pixels "sun")) 1) x) 2)) + +(fn main () -> i32 + (match (last_color "head") + ((Color.Green) + 0) + ((Color.Red) + 1) + ((Color.Blue) + 1))) diff --git a/tests/array-struct-elements.surface.lower b/tests/array-struct-elements.surface.lower new file mode 100644 index 0000000..c122660 --- /dev/null +++ b/tests/array-struct-elements.surface.lower @@ -0,0 +1,81 @@ +program main + enum Color + variant Red + variant Blue + variant Green + struct Pixel + field x: i32 + field label: string + field color: Color + fn make_pixels(head: string) -> (array Pixel 3) + array Pixel + construct Pixel + field x + int 1 + field label + var head + field color + enum-variant Color.Red + construct Pixel + field x + int 2 + field label + string "mid" + field color + enum-variant Color.Blue + construct Pixel + field x + int 3 + field label + string "tail" + field color + enum-variant Color.Green + fn echo_pixels(pixels: (array Pixel 3)) -> (array Pixel 3) + var pixels + fn at(pixels: (array Pixel 3), i: i32) -> Pixel + index + var pixels + var i + fn first_label(head: string) -> string + field-access label + call at + call make_pixels + var head + int 0 + fn last_color(head: string) -> Color + field-access color + index + call echo_pixels + call make_pixels + var head + int 2 + fn main() -> i32 + match + subject + call last_color + string "head" + arm Color.Green + int 0 + arm Color.Red + int 1 + arm Color.Blue + int 1 + test "struct array string field access" + binary = + call first_label + string "head" + string "head" + test "struct array nested enum field access" + binary = + call last_color + string "head" + enum-variant Color.Green + test "struct array local param return call flow" + binary = + field-access x + call at + call echo_pixels + call make_pixels + string "sun" + int 1 + int 2 diff --git a/tests/array-struct-fields.checked.lower b/tests/array-struct-fields.checked.lower new file mode 100644 index 0000000..037572b --- /dev/null +++ b/tests/array-struct-fields.checked.lower @@ -0,0 +1,124 @@ +program main + struct ArrayRecord + field ints: (array i32 3) + field wides: (array i64 2) + field ratios: (array f64 3) + field flags: (array bool 3) + field words: (array string 3) + fn make_record(base: i32, head: string) -> ArrayRecord + construct ArrayRecord : ArrayRecord + field ints + array : (array i32 3) + var base : i32 + binary + : i32 + var base : i32 + int 1 : i32 + binary + : i32 + var base : i32 + int 2 : i32 + field wides + array : (array i64 2) + i64 40 : i64 + i64 41 : i64 + field ratios + array : (array f64 3) + float 1.5 : f64 + float 2.5 : f64 + float 3.5 : f64 + field flags + array : (array bool 3) + bool false : bool + bool true : bool + bool true : bool + field words + array : (array string 3) + var head : string + string "moon" : string + string "star" : string + fn echo_record(record: ArrayRecord) -> ArrayRecord + var record : ArrayRecord + fn local_record(head: string) -> ArrayRecord + local let record : unit + call make_record : ArrayRecord + int 7 : i32 + var head : string + call echo_record : ArrayRecord + var record : ArrayRecord + fn int_at(record: ArrayRecord, i: i32) -> i32 + index : i32 + field-access ints : (array i32 3) + var record : ArrayRecord + var i : i32 + fn wide_at(record: ArrayRecord, i: i32) -> i64 + index : i64 + field-access wides : (array i64 2) + var record : ArrayRecord + var i : i32 + fn ratio_at(record: ArrayRecord, i: i32) -> f64 + index : f64 + field-access ratios : (array f64 3) + var record : ArrayRecord + var i : i32 + fn flag_at(record: ArrayRecord, i: i32) -> bool + index : bool + field-access flags : (array bool 3) + var record : ArrayRecord + var i : i32 + fn word_at(record: ArrayRecord, i: i32) -> string + index : string + field-access words : (array string 3) + var record : ArrayRecord + var i : i32 + fn main() -> i32 + if : i32 + call flag_at : bool + call local_record : ArrayRecord + string "sun" : string + int 2 : i32 + int 0 : i32 + int 1 : i32 + test "struct array i32 field access" + binary = : bool + call int_at : i32 + call make_record : ArrayRecord + int 7 : i32 + string "sun" : string + int 2 : i32 + int 9 : i32 + test "struct array i64 field access" + binary = : bool + call wide_at : i64 + call make_record : ArrayRecord + int 7 : i32 + string "sun" : string + int 1 : i32 + i64 41 : i64 + test "struct array f64 field access" + binary = : bool + call ratio_at : f64 + call make_record : ArrayRecord + int 7 : i32 + string "sun" : string + int 2 : i32 + float 3.5 : f64 + test "struct array bool field access" + call flag_at : bool + call make_record : ArrayRecord + int 7 : i32 + string "sun" : string + int 1 : i32 + test "struct array string field access" + binary = : bool + call word_at : string + call make_record : ArrayRecord + int 7 : i32 + string "sun" : string + int 1 : i32 + string "moon" : string + test "struct array local param return call flow" + binary = : bool + call word_at : string + call local_record : ArrayRecord + string "sun" : string + int 0 : i32 + string "sun" : string diff --git a/tests/array-struct-fields.slo b/tests/array-struct-fields.slo new file mode 100644 index 0000000..b275d6d --- /dev/null +++ b/tests/array-struct-fields.slo @@ -0,0 +1,56 @@ +(module main) + +(struct ArrayRecord + (ints (array i32 3)) + (wides (array i64 2)) + (ratios (array f64 3)) + (flags (array bool 3)) + (words (array string 3))) + +(fn make_record ((base i32) (head string)) -> ArrayRecord + (ArrayRecord (ints (array i32 base (+ base 1) (+ base 2))) (wides (array i64 40i64 41i64)) (ratios (array f64 1.5 2.5 3.5)) (flags (array bool false true true)) (words (array string head "moon" "star")))) + +(fn echo_record ((record ArrayRecord)) -> ArrayRecord + record) + +(fn local_record ((head string)) -> ArrayRecord + (let record ArrayRecord (make_record 7 head)) + (echo_record record)) + +(fn int_at ((record ArrayRecord) (i i32)) -> i32 + (index (. record ints) i)) + +(fn wide_at ((record ArrayRecord) (i i32)) -> i64 + (index (. record wides) i)) + +(fn ratio_at ((record ArrayRecord) (i i32)) -> f64 + (index (. record ratios) i)) + +(fn flag_at ((record ArrayRecord) (i i32)) -> bool + (index (. record flags) i)) + +(fn word_at ((record ArrayRecord) (i i32)) -> string + (index (. record words) i)) + +(test "struct array i32 field access" + (= (int_at (make_record 7 "sun") 2) 9)) + +(test "struct array i64 field access" + (= (wide_at (make_record 7 "sun") 1) 41i64)) + +(test "struct array f64 field access" + (= (ratio_at (make_record 7 "sun") 2) 3.5)) + +(test "struct array bool field access" + (flag_at (make_record 7 "sun") 1)) + +(test "struct array string field access" + (= (word_at (make_record 7 "sun") 1) "moon")) + +(test "struct array local param return call flow" + (= (word_at (local_record "sun") 0) "sun")) + +(fn main () -> i32 + (if (flag_at (local_record "sun") 2) + 0 + 1)) diff --git a/tests/array-struct-fields.surface.lower b/tests/array-struct-fields.surface.lower new file mode 100644 index 0000000..dc1e631 --- /dev/null +++ b/tests/array-struct-fields.surface.lower @@ -0,0 +1,124 @@ +program main + struct ArrayRecord + field ints: (array i32 3) + field wides: (array i64 2) + field ratios: (array f64 3) + field flags: (array bool 3) + field words: (array string 3) + fn make_record(base: i32, head: string) -> ArrayRecord + construct ArrayRecord + field ints + array i32 + var base + binary + + var base + int 1 + binary + + var base + int 2 + field wides + array i64 + i64 40 + i64 41 + field ratios + array f64 + float 1.5 + float 2.5 + float 3.5 + field flags + array bool + bool false + bool true + bool true + field words + array string + var head + string "moon" + string "star" + fn echo_record(record: ArrayRecord) -> ArrayRecord + var record + fn local_record(head: string) -> ArrayRecord + local let record: ArrayRecord + call make_record + int 7 + var head + call echo_record + var record + fn int_at(record: ArrayRecord, i: i32) -> i32 + index + field-access ints + var record + var i + fn wide_at(record: ArrayRecord, i: i32) -> i64 + index + field-access wides + var record + var i + fn ratio_at(record: ArrayRecord, i: i32) -> f64 + index + field-access ratios + var record + var i + fn flag_at(record: ArrayRecord, i: i32) -> bool + index + field-access flags + var record + var i + fn word_at(record: ArrayRecord, i: i32) -> string + index + field-access words + var record + var i + fn main() -> i32 + if + call flag_at + call local_record + string "sun" + int 2 + int 0 + int 1 + test "struct array i32 field access" + binary = + call int_at + call make_record + int 7 + string "sun" + int 2 + int 9 + test "struct array i64 field access" + binary = + call wide_at + call make_record + int 7 + string "sun" + int 1 + i64 41 + test "struct array f64 field access" + binary = + call ratio_at + call make_record + int 7 + string "sun" + int 2 + float 3.5 + test "struct array bool field access" + call flag_at + call make_record + int 7 + string "sun" + int 1 + test "struct array string field access" + binary = + call word_at + call make_record + int 7 + string "sun" + int 1 + string "moon" + test "struct array local param return call flow" + binary = + call word_at + call local_record + string "sun" + int 0 + string "sun" diff --git a/tests/array-value-flow.checked.lower b/tests/array-value-flow.checked.lower new file mode 100644 index 0000000..eaa1bb7 --- /dev/null +++ b/tests/array-value-flow.checked.lower @@ -0,0 +1,74 @@ +program main + fn make_values(base: i32) -> (array i32 3) + array : (array i32 3) + var base : i32 + binary + : i32 + var base : i32 + int 1 : i32 + binary + : i32 + var base : i32 + int 2 : i32 + fn first(values: (array i32 3)) -> i32 + index : i32 + var values : (array i32 3) + int 0 : i32 + fn at(values: (array i32 3), i: i32) -> i32 + index : i32 + var values : (array i32 3) + var i : i32 + fn echo(values: (array i32 3)) -> (array i32 3) + var values : (array i32 3) + fn call_return_index(i: i32) -> i32 + index : i32 + call make_values : (array i32 3) + int 10 : i32 + var i : i32 + fn local_array_flow(i: i32) -> i32 + local let values : unit + call make_values : (array i32 3) + int 20 : i32 + call at : i32 + var values : (array i32 3) + var i : i32 + fn parameter_local_copy(values: (array i32 3), i: i32) -> i32 + local let copy : unit + var values : (array i32 3) + index : i32 + var copy : (array i32 3) + var i : i32 + fn main() -> i32 + call call_return_index : i32 + int 1 : i32 + test "array parameter value flow" + binary = : bool + call first : i32 + call make_values : (array i32 3) + int 7 : i32 + int 7 : i32 + test "array dynamic index" + binary = : bool + call at : i32 + call make_values : (array i32 3) + int 4 : i32 + int 2 : i32 + int 6 : i32 + test "array local call value flow" + binary = : bool + call local_array_flow : i32 + int 1 : i32 + int 21 : i32 + test "array parameter local copy" + binary = : bool + call parameter_local_copy : i32 + call make_values : (array i32 3) + int 40 : i32 + int 2 : i32 + int 42 : i32 + test "array return call value flow" + binary = : bool + index : i32 + call echo : (array i32 3) + call make_values : (array i32 3) + int 30 : i32 + int 2 : i32 + int 32 : i32 diff --git a/tests/array-value-flow.slo b/tests/array-value-flow.slo new file mode 100644 index 0000000..7774902 --- /dev/null +++ b/tests/array-value-flow.slo @@ -0,0 +1,42 @@ +(module main) + +(fn make_values ((base i32)) -> (array i32 3) + (array i32 base (+ base 1) (+ base 2))) + +(fn first ((values (array i32 3))) -> i32 + (index values 0)) + +(fn at ((values (array i32 3)) (i i32)) -> i32 + (index values i)) + +(fn echo ((values (array i32 3))) -> (array i32 3) + values) + +(fn call_return_index ((i i32)) -> i32 + (index (make_values 10) i)) + +(fn local_array_flow ((i i32)) -> i32 + (let values (array i32 3) (make_values 20)) + (at values i)) + +(fn parameter_local_copy ((values (array i32 3)) (i i32)) -> i32 + (let copy (array i32 3) values) + (index copy i)) + +(test "array parameter value flow" + (= (first (make_values 7)) 7)) + +(test "array dynamic index" + (= (at (make_values 4) 2) 6)) + +(test "array local call value flow" + (= (local_array_flow 1) 21)) + +(test "array parameter local copy" + (= (parameter_local_copy (make_values 40) 2) 42)) + +(test "array return call value flow" + (= (index (echo (make_values 30)) 2) 32)) + +(fn main () -> i32 + (call_return_index 1)) diff --git a/tests/array-value-flow.surface.lower b/tests/array-value-flow.surface.lower new file mode 100644 index 0000000..113b6f9 --- /dev/null +++ b/tests/array-value-flow.surface.lower @@ -0,0 +1,74 @@ +program main + fn make_values(base: i32) -> (array i32 3) + array i32 + var base + binary + + var base + int 1 + binary + + var base + int 2 + fn first(values: (array i32 3)) -> i32 + index + var values + int 0 + fn at(values: (array i32 3), i: i32) -> i32 + index + var values + var i + fn echo(values: (array i32 3)) -> (array i32 3) + var values + fn call_return_index(i: i32) -> i32 + index + call make_values + int 10 + var i + fn local_array_flow(i: i32) -> i32 + local let values: (array i32 3) + call make_values + int 20 + call at + var values + var i + fn parameter_local_copy(values: (array i32 3), i: i32) -> i32 + local let copy: (array i32 3) + var values + index + var copy + var i + fn main() -> i32 + call call_return_index + int 1 + test "array parameter value flow" + binary = + call first + call make_values + int 7 + int 7 + test "array dynamic index" + binary = + call at + call make_values + int 4 + int 2 + int 6 + test "array local call value flow" + binary = + call local_array_flow + int 1 + int 21 + test "array parameter local copy" + binary = + call parameter_local_copy + call make_values + int 40 + int 2 + int 42 + test "array return call value flow" + binary = + index + call echo + call make_values + int 30 + int 2 + int 32 diff --git a/tests/array.checked.lower b/tests/array.checked.lower new file mode 100644 index 0000000..77b6dd6 --- /dev/null +++ b/tests/array.checked.lower @@ -0,0 +1,40 @@ +program main + fn immediate_second() -> i32 + index : i32 + array : (array i32 3) + int 10 : i32 + int 20 : i32 + int 30 : i32 + int 1 : i32 + fn local_sum() -> i32 + local let values : unit + array : (array i32 3) + int 4 : i32 + int 5 : i32 + int 6 : i32 + binary + : i32 + index : i32 + var values : (array i32 3) + int 0 : i32 + index : i32 + var values : (array i32 3) + int 2 : i32 + fn main() -> i32 + binary + : i32 + call immediate_second : i32 + call local_sum : i32 + test "immediate array index" + binary = : bool + call immediate_second : i32 + int 20 : i32 + test "array local index" + local let values : unit + array : (array i32 3) + int 7 : i32 + int 8 : i32 + int 9 : i32 + binary = : bool + index : i32 + var values : (array i32 3) + int 2 : i32 + int 9 : i32 diff --git a/tests/array.slo b/tests/array.slo new file mode 100644 index 0000000..9ca3674 --- /dev/null +++ b/tests/array.slo @@ -0,0 +1,18 @@ +(module main) + +(fn immediate_second () -> i32 + (index (array i32 10 20 30) 1)) + +(fn local_sum () -> i32 + (let values (array i32 3) (array i32 4 5 6)) + (+ (index values 0) (index values 2))) + +(test "immediate array index" + (= (immediate_second) 20)) + +(test "array local index" + (let values (array i32 3) (array i32 7 8 9)) + (= (index values 2) 9)) + +(fn main () -> i32 + (+ (immediate_second) (local_sum))) diff --git a/tests/array.surface.lower b/tests/array.surface.lower new file mode 100644 index 0000000..d254eab --- /dev/null +++ b/tests/array.surface.lower @@ -0,0 +1,40 @@ +program main + fn immediate_second() -> i32 + index + array i32 + int 10 + int 20 + int 30 + int 1 + fn local_sum() -> i32 + local let values: (array i32 3) + array i32 + int 4 + int 5 + int 6 + binary + + index + var values + int 0 + index + var values + int 2 + fn main() -> i32 + binary + + call immediate_second + call local_sum + test "immediate array index" + binary = + call immediate_second + int 20 + test "array local index" + local let values: (array i32 3) + array i32 + int 7 + int 8 + int 9 + binary = + index + var values + int 2 + int 9 diff --git a/tests/boolean-logic.slo b/tests/boolean-logic.slo new file mode 100644 index 0000000..c9dac75 --- /dev/null +++ b/tests/boolean-logic.slo @@ -0,0 +1,58 @@ +(module main) + +(fn and_true () -> bool + (and true true)) + +(fn and_false () -> bool + (and true false)) + +(fn or_true () -> bool + (or false true)) + +(fn not_false () -> bool + (not false)) + +(fn and_short_circuits () -> bool + (not (and false (= (/ 1 0) 0)))) + +(fn or_short_circuits () -> bool + (or true (= (/ 1 0) 0))) + +(fn logic_ok () -> bool + (if (and_true) + (if (not (and_false)) + (if (or_true) + (if (not_false) + (if (and_short_circuits) + (or_short_circuits) + false) + false) + false) + false) + false)) + +(fn main () -> i32 + (if (logic_ok) + 42 + 1)) + +(test "and true" + (and_true)) + +(test "and false" + (not (and_false))) + +(test "or true" + (or_true)) + +(test "not false" + (not_false)) + +(test "and short circuit" + (and_short_circuits)) + +(test "or short circuit" + (or_short_circuits)) + +(test "boolean logic summary" + (logic_ok)) diff --git a/tests/canonical.fmt b/tests/canonical.fmt new file mode 100644 index 0000000..def0173 --- /dev/null +++ b/tests/canonical.fmt @@ -0,0 +1,11 @@ +; status: formatter-canonical +; Scope: current strict supported syntax only. + +(module main) + +(fn add ((a i32) (b i32)) -> i32 + (+ a b)) + +(fn main () -> i32 + (print_i32 (add 20 22)) + 0) diff --git a/tests/checked-i64-to-i32-conversion.checked.lower b/tests/checked-i64-to-i32-conversion.checked.lower new file mode 100644 index 0000000..1193bbe --- /dev/null +++ b/tests/checked-i64-to-i32-conversion.checked.lower @@ -0,0 +1,89 @@ +program main + fn narrow(value: i64) -> (result i32 i32) + call std.num.i64_to_i32_result : (result i32 i32) + var value : i64 + fn low_ok() -> (result i32 i32) + call narrow : (result i32 i32) + i64 -2147483648 : i64 + fn high_ok() -> (result i32 i32) + call narrow : (result i32 i32) + i64 2147483647 : i64 + fn negative_ok() -> (result i32 i32) + call narrow : (result i32 i32) + i64 -7 : i64 + fn below_low_err() -> (result i32 i32) + call narrow : (result i32 i32) + i64 -2147483649 : i64 + fn above_high_err() -> (result i32 i32) + call narrow : (result i32 i32) + i64 2147483648 : i64 + fn main() -> i32 + local let value : unit + call negative_ok : (result i32 i32) + if : i32 + std.result.is_ok : bool + var value : (result i32 i32) + if : i32 + binary = : bool + std.result.unwrap_ok : i32 + var value : (result i32 i32) + int -7 : i32 + int 0 : i32 + int 1 : i32 + std.result.unwrap_err : i32 + var value : (result i32 i32) + test "i64 low bound narrows to i32" + local let value : unit + call low_ok : (result i32 i32) + if : bool + std.result.is_ok : bool + var value : (result i32 i32) + binary = : bool + std.result.unwrap_ok : i32 + var value : (result i32 i32) + int -2147483648 : i32 + bool false : bool + test "i64 high bound narrows to i32" + local let value : unit + call high_ok : (result i32 i32) + if : bool + std.result.is_ok : bool + var value : (result i32 i32) + binary = : bool + std.result.unwrap_ok : i32 + var value : (result i32 i32) + int 2147483647 : i32 + bool false : bool + test "negative i64 narrows to i32" + local let value : unit + call negative_ok : (result i32 i32) + if : bool + std.result.is_ok : bool + var value : (result i32 i32) + binary = : bool + std.result.unwrap_ok : i32 + var value : (result i32 i32) + int -7 : i32 + bool false : bool + test "below i32 range returns err" + local let value : unit + call below_low_err : (result i32 i32) + if : bool + std.result.is_err : bool + var value : (result i32 i32) + binary = : bool + std.result.unwrap_err : i32 + var value : (result i32 i32) + int 1 : i32 + bool false : bool + test "above i32 range returns err" + local let value : unit + call above_high_err : (result i32 i32) + if : bool + std.result.is_err : bool + var value : (result i32 i32) + binary = : bool + std.result.unwrap_err : i32 + var value : (result i32 i32) + int 1 : i32 + bool false : bool diff --git a/tests/checked-i64-to-i32-conversion.slo b/tests/checked-i64-to-i32-conversion.slo new file mode 100644 index 0000000..e0905f7 --- /dev/null +++ b/tests/checked-i64-to-i32-conversion.slo @@ -0,0 +1,57 @@ +(module main) + +(fn narrow ((value i64)) -> (result i32 i32) + (std.num.i64_to_i32_result value)) + +(fn low_ok () -> (result i32 i32) + (narrow -2147483648i64)) + +(fn high_ok () -> (result i32 i32) + (narrow 2147483647i64)) + +(fn negative_ok () -> (result i32 i32) + (narrow -7i64)) + +(fn below_low_err () -> (result i32 i32) + (narrow -2147483649i64)) + +(fn above_high_err () -> (result i32 i32) + (narrow 2147483648i64)) + +(test "i64 low bound narrows to i32" + (let value (result i32 i32) (low_ok)) + (if (std.result.is_ok value) + (= (std.result.unwrap_ok value) -2147483648) + false)) + +(test "i64 high bound narrows to i32" + (let value (result i32 i32) (high_ok)) + (if (std.result.is_ok value) + (= (std.result.unwrap_ok value) 2147483647) + false)) + +(test "negative i64 narrows to i32" + (let value (result i32 i32) (negative_ok)) + (if (std.result.is_ok value) + (= (std.result.unwrap_ok value) -7) + false)) + +(test "below i32 range returns err" + (let value (result i32 i32) (below_low_err)) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "above i32 range returns err" + (let value (result i32 i32) (above_high_err)) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(fn main () -> i32 + (let value (result i32 i32) (negative_ok)) + (if (std.result.is_ok value) + (if (= (std.result.unwrap_ok value) -7) + 0 + 1) + (std.result.unwrap_err value))) diff --git a/tests/checked-i64-to-i32-conversion.surface.lower b/tests/checked-i64-to-i32-conversion.surface.lower new file mode 100644 index 0000000..8bae033 --- /dev/null +++ b/tests/checked-i64-to-i32-conversion.surface.lower @@ -0,0 +1,89 @@ +program main + fn narrow(value: i64) -> (result i32 i32) + call std.num.i64_to_i32_result + var value + fn low_ok() -> (result i32 i32) + call narrow + i64 -2147483648 + fn high_ok() -> (result i32 i32) + call narrow + i64 2147483647 + fn negative_ok() -> (result i32 i32) + call narrow + i64 -7 + fn below_low_err() -> (result i32 i32) + call narrow + i64 -2147483649 + fn above_high_err() -> (result i32 i32) + call narrow + i64 2147483648 + fn main() -> i32 + local let value: (result i32 i32) + call negative_ok + if + std.result.is_ok + var value + if + binary = + std.result.unwrap_ok + var value + int -7 + int 0 + int 1 + std.result.unwrap_err + var value + test "i64 low bound narrows to i32" + local let value: (result i32 i32) + call low_ok + if + std.result.is_ok + var value + binary = + std.result.unwrap_ok + var value + int -2147483648 + bool false + test "i64 high bound narrows to i32" + local let value: (result i32 i32) + call high_ok + if + std.result.is_ok + var value + binary = + std.result.unwrap_ok + var value + int 2147483647 + bool false + test "negative i64 narrows to i32" + local let value: (result i32 i32) + call negative_ok + if + std.result.is_ok + var value + binary = + std.result.unwrap_ok + var value + int -7 + bool false + test "below i32 range returns err" + local let value: (result i32 i32) + call below_low_err + if + std.result.is_err + var value + binary = + std.result.unwrap_err + var value + int 1 + bool false + test "above i32 range returns err" + local let value: (result i32 i32) + call above_high_err + if + std.result.is_err + var value + binary = + std.result.unwrap_err + var value + int 1 + bool false diff --git a/tests/comments.checked.lower b/tests/comments.checked.lower new file mode 100644 index 0000000..09d8cc6 --- /dev/null +++ b/tests/comments.checked.lower @@ -0,0 +1,17 @@ +program main + fn add_one(value: i32) -> i32 + local let one : unit + int 1 : i32 + binary + : i32 + var value : i32 + var one : i32 + fn main() -> i32 + call add_one : i32 + int 41 : i32 + test "comments stay attached" + local let observed : unit + call add_one : i32 + int 4 : i32 + binary = : bool + var observed : i32 + int 5 : i32 diff --git a/tests/comments.slo b/tests/comments.slo new file mode 100644 index 0000000..27596b3 --- /dev/null +++ b/tests/comments.slo @@ -0,0 +1,35 @@ +; status: formatter-canonical +; Scope: v1 full-line comment stability positions. +; comment before module form + +(module main) + +; comment before helper function + +(fn add_one ((value i32)) -> i32 + ; comment before first function body expression + (let one i32 1) + ; comment before final function body expression + (+ value one) + ; comment after final function body expression +) + +; comment before top-level test + +(test "comments stay attached" + ; comment before non-final test body expression + (let observed i32 (add_one 4)) + ; comment before final test body expression + (= observed 5) + ; comment after final test body expression +) + +; comment before main function + +(fn main () -> i32 + ; comment before only function body expression + (add_one 41) + ; comment after final function body expression +) + +; comment after last top-level form diff --git a/tests/comments.surface.lower b/tests/comments.surface.lower new file mode 100644 index 0000000..344f642 --- /dev/null +++ b/tests/comments.surface.lower @@ -0,0 +1,17 @@ +program main + fn add_one(value: i32) -> i32 + local let one: i32 + int 1 + binary + + var value + var one + fn main() -> i32 + call add_one + int 41 + test "comments stay attached" + local let observed: i32 + call add_one + int 4 + binary = + var observed + int 5 diff --git a/tests/composite-locals.checked.lower b/tests/composite-locals.checked.lower new file mode 100644 index 0000000..9a68b6b --- /dev/null +++ b/tests/composite-locals.checked.lower @@ -0,0 +1,467 @@ +program main + enum Signal + variant Red + variant Yellow + variant Green + enum Reading + variant Missing + variant Value i32 + struct Point + field x: i32 + field y: i32 + struct PrimitiveRecord + field id: i32 + field active: bool + field label: string + struct NumericRecord + field wide: i64 + field ratio: f64 + struct TaggedReading + field status: Signal + field reading: Reading + fn make_point(x: i32, y: i32) -> Point + construct Point : Point + field x + var x : i32 + field y + var y : i32 + fn make_primitive_record(id: i32, label: string) -> PrimitiveRecord + construct PrimitiveRecord : PrimitiveRecord + field id + var id : i32 + field active + binary = : bool + var id : i32 + int 7 : i32 + field label + var label : string + fn make_numeric_record(wide: i64, ratio: f64) -> NumericRecord + construct NumericRecord : NumericRecord + field wide + var wide : i64 + field ratio + var ratio : f64 + fn make_tagged(status: Signal, reading: Reading) -> TaggedReading + construct TaggedReading : TaggedReading + field status + var status : Signal + field reading + var reading : Reading + fn vec_i32_pair(base: i32) -> (vec i32) + local let values : unit + call std.vec.i32.empty : (vec i32) + local let first : unit + call std.vec.i32.append : (vec i32) + var values : (vec i32) + var base : i32 + call std.vec.i32.append : (vec i32) + var first : (vec i32) + binary + : i32 + var base : i32 + int 1 : i32 + fn vec_i64_pair(base: i64) -> (vec i64) + local let values : unit + call std.vec.i64.empty : (vec i64) + local let first : unit + call std.vec.i64.append : (vec i64) + var values : (vec i64) + var base : i64 + call std.vec.i64.append : (vec i64) + var first : (vec i64) + binary + : i64 + var base : i64 + i64 1 : i64 + fn vec_f64_pair(base: f64) -> (vec f64) + local let values : unit + call std.vec.f64.empty : (vec f64) + local let first : unit + call std.vec.f64.append : (vec f64) + var values : (vec f64) + var base : f64 + call std.vec.f64.append : (vec f64) + var first : (vec f64) + binary + : f64 + var base : f64 + float 1 : f64 + fn vec_bool_pair(first: bool, second: bool) -> (vec bool) + local let values : unit + call std.vec.bool.empty : (vec bool) + local let left : unit + call std.vec.bool.append : (vec bool) + var values : (vec bool) + var first : bool + call std.vec.bool.append : (vec bool) + var left : (vec bool) + var second : bool + fn vec_string_pair(first: string, second: string) -> (vec string) + local let values : unit + call std.vec.string.empty : (vec string) + local let left : unit + call std.vec.string.append : (vec string) + var values : (vec string) + var first : string + call std.vec.string.append : (vec string) + var left : (vec string) + var second : string + fn swap_string(first: string, second: string) -> string + local var current : unit + var first : string + set current : unit + var second : string + var current : string + fn vec_i32_local_value() -> i32 + local var current : unit + call vec_i32_pair : (vec i32) + int 10 : i32 + set current : unit + call vec_i32_pair : (vec i32) + int 40 : i32 + call std.vec.i32.index : i32 + var current : (vec i32) + int 1 : i32 + fn vec_i64_local_value() -> i64 + local var current : unit + call vec_i64_pair : (vec i64) + i64 10 : i64 + set current : unit + call vec_i64_pair : (vec i64) + i64 40 : i64 + call std.vec.i64.index : i64 + var current : (vec i64) + int 1 : i32 + fn vec_f64_local_value() -> f64 + local var current : unit + call vec_f64_pair : (vec f64) + float 10 : f64 + set current : unit + call vec_f64_pair : (vec f64) + float 40 : f64 + call std.vec.f64.index : f64 + var current : (vec f64) + int 1 : i32 + fn vec_bool_local_value() -> bool + local var current : unit + call vec_bool_pair : (vec bool) + bool true : bool + bool false : bool + set current : unit + call vec_bool_pair : (vec bool) + bool false : bool + bool true : bool + call std.vec.bool.index : bool + var current : (vec bool) + int 1 : i32 + fn vec_string_local_value() -> string + local var current : unit + call vec_string_pair : (vec string) + string "oak" : string + string "elm" : string + set current : unit + call vec_string_pair : (vec string) + string "pine" : string + string "slovo" : string + call std.vec.string.index : string + var current : (vec string) + int 1 : i32 + fn option_i32_local_value() -> i32 + local var current : unit + none : (option i32) + set current : unit + some : (option i32) + int 42 : i32 + match : i32 + subject + var current : (option i32) + arm some payload + var payload : i32 + arm none + int 0 : i32 + fn option_i64_local_value() -> i64 + local var current : unit + none : (option i64) + set current : unit + some : (option i64) + i64 42000000000 : i64 + match : i64 + subject + var current : (option i64) + arm some payload + var payload : i64 + arm none + i64 0 : i64 + fn option_f64_local_value() -> f64 + local var current : unit + none : (option f64) + set current : unit + some : (option f64) + float 42.5 : f64 + match : f64 + subject + var current : (option f64) + arm some payload + var payload : f64 + arm none + float 0 : f64 + fn option_bool_local_value() -> bool + local var current : unit + none : (option bool) + set current : unit + some : (option bool) + bool true : bool + match : bool + subject + var current : (option bool) + arm some payload + var payload : bool + arm none + bool false : bool + fn option_string_local_value() -> string + local var current : unit + none : (option string) + set current : unit + some : (option string) + string "branch" : string + match : string + subject + var current : (option string) + arm some payload + var payload : string + arm none + string "fallback" : string + fn result_i32_local_value() -> i32 + local var current : unit + err : (result i32 i32) + int 7 : i32 + set current : unit + ok : (result i32 i32) + int 42 : i32 + match : i32 + subject + var current : (result i32 i32) + arm ok payload + var payload : i32 + arm err code + var code : i32 + fn result_i64_local_value() -> i64 + local var current : unit + err : (result i64 i32) + int 7 : i32 + set current : unit + ok : (result i64 i32) + i64 42000000000 : i64 + match : i64 + subject + var current : (result i64 i32) + arm ok payload + var payload : i64 + arm err code + i64 0 : i64 + fn result_f64_local_value() -> f64 + local var current : unit + err : (result f64 i32) + int 7 : i32 + set current : unit + ok : (result f64 i32) + float 42.5 : f64 + match : f64 + subject + var current : (result f64 i32) + arm ok payload + var payload : f64 + arm err code + float 0 : f64 + fn result_bool_local_value() -> bool + local var current : unit + err : (result bool i32) + int 7 : i32 + set current : unit + ok : (result bool i32) + bool true : bool + match : bool + subject + var current : (result bool i32) + arm ok payload + var payload : bool + arm err code + bool false : bool + fn result_string_local_value() -> string + local var current : unit + err : (result string i32) + int 7 : i32 + set current : unit + ok : (result string i32) + string "slovo" : string + match : string + subject + var current : (result string i32) + arm ok payload + var payload : string + arm err code + string "fallback" : string + fn point_local_sum() -> i32 + local var current : unit + call make_point : Point + int 1 : i32 + int 2 : i32 + set current : unit + call make_point : Point + int 20 : i32 + int 22 : i32 + binary + : i32 + field-access x : i32 + var current : Point + field-access y : i32 + var current : Point + fn primitive_record_local_label() -> string + local var current : unit + call make_primitive_record : PrimitiveRecord + int 3 : i32 + string "alpha" : string + set current : unit + call make_primitive_record : PrimitiveRecord + int 7 : i32 + string "beta" : string + field-access label : string + var current : PrimitiveRecord + fn numeric_record_local_ratio() -> f64 + local var current : unit + call make_numeric_record : NumericRecord + i64 10 : i64 + float 1 : f64 + set current : unit + call make_numeric_record : NumericRecord + i64 20 : i64 + float 3.5 : f64 + field-access ratio : f64 + var current : NumericRecord + fn signal_local_code() -> i32 + local var current : unit + enum-variant Signal.Red #0 : Signal + set current : unit + enum-variant Signal.Green #2 : Signal + match : i32 + subject + var current : Signal + arm Signal.Red + int 1 : i32 + arm Signal.Yellow + int 2 : i32 + arm Signal.Green + int 3 : i32 + fn reading_local_code() -> i32 + local var current : unit + enum-variant Reading.Missing #0 : Reading + set current : unit + enum-variant Reading.Value #1 payload : Reading + int 42 : i32 + match : i32 + subject + var current : Reading + arm Reading.Missing + int 0 : i32 + arm Reading.Value payload + var payload : i32 + fn tagged_local_code() -> i32 + local var current : unit + call make_tagged : TaggedReading + enum-variant Signal.Yellow #1 : Signal + enum-variant Reading.Missing #0 : Reading + set current : unit + call make_tagged : TaggedReading + enum-variant Signal.Green #2 : Signal + enum-variant Reading.Value #1 payload : Reading + int 42 : i32 + match : i32 + subject + field-access reading : Reading + var current : TaggedReading + arm Reading.Missing + int 0 : i32 + arm Reading.Value payload + var payload : i32 + fn main() -> i32 + call tagged_local_code : i32 + test "mutable string local set works" + binary = : bool + call swap_string : string + string "oak" : string + string "slovo" : string + string "slovo" : string + test "mutable vec i32 local set works" + binary = : bool + call vec_i32_local_value : i32 + int 41 : i32 + test "mutable vec i64 local set works" + binary = : bool + call vec_i64_local_value : i64 + i64 41 : i64 + test "mutable vec f64 local set works" + binary = : bool + call vec_f64_local_value : f64 + float 41 : f64 + test "mutable vec bool local set works" + call vec_bool_local_value : bool + test "mutable vec string local set works" + binary = : bool + call vec_string_local_value : string + string "slovo" : string + test "mutable option i32 local set works" + binary = : bool + call option_i32_local_value : i32 + int 42 : i32 + test "mutable option i64 local set works" + binary = : bool + call option_i64_local_value : i64 + i64 42000000000 : i64 + test "mutable option f64 local set works" + binary = : bool + call option_f64_local_value : f64 + float 42.5 : f64 + test "mutable option bool local set works" + call option_bool_local_value : bool + test "mutable option string local set works" + binary = : bool + call option_string_local_value : string + string "branch" : string + test "mutable result i32 local set works" + binary = : bool + call result_i32_local_value : i32 + int 42 : i32 + test "mutable result i64 local set works" + binary = : bool + call result_i64_local_value : i64 + i64 42000000000 : i64 + test "mutable result f64 local set works" + binary = : bool + call result_f64_local_value : f64 + float 42.5 : f64 + test "mutable result bool local set works" + call result_bool_local_value : bool + test "mutable result string local set works" + binary = : bool + call result_string_local_value : string + string "slovo" : string + test "mutable plain struct local set works" + binary = : bool + call point_local_sum : i32 + int 42 : i32 + test "mutable primitive struct local set works" + binary = : bool + call primitive_record_local_label : string + string "beta" : string + test "mutable numeric struct local set works" + binary = : bool + call numeric_record_local_ratio : f64 + float 3.5 : f64 + test "mutable payloadless enum local set works" + binary = : bool + call signal_local_code : i32 + int 3 : i32 + test "mutable payload enum local set works" + binary = : bool + call reading_local_code : i32 + int 42 : i32 + test "mutable enum struct local set works" + binary = : bool + call tagged_local_code : i32 + int 42 : i32 diff --git a/tests/composite-locals.slo b/tests/composite-locals.slo new file mode 100644 index 0000000..41f00c3 --- /dev/null +++ b/tests/composite-locals.slo @@ -0,0 +1,294 @@ +(module main) + +(struct Point + (x i32) + (y i32)) + +(struct PrimitiveRecord + (id i32) + (active bool) + (label string)) + +(struct NumericRecord + (wide i64) + (ratio f64)) + +(enum Signal Red Yellow Green) + +(enum Reading + Missing + (Value i32)) + +(struct TaggedReading + (status Signal) + (reading Reading)) + +(fn make_point ((x i32) (y i32)) -> Point + (Point (x x) (y y))) + +(fn make_primitive_record ((id i32) (label string)) -> PrimitiveRecord + (PrimitiveRecord (id id) (active (= id 7)) (label label))) + +(fn make_numeric_record ((wide i64) (ratio f64)) -> NumericRecord + (NumericRecord (wide wide) (ratio ratio))) + +(fn make_tagged ((status Signal) (reading Reading)) -> TaggedReading + (TaggedReading (status status) (reading reading))) + +(fn vec_i32_pair ((base i32)) -> (vec i32) + (let values (vec i32) (std.vec.i32.empty)) + (let first (vec i32) (std.vec.i32.append values base)) + (std.vec.i32.append first (+ base 1))) + +(fn vec_i64_pair ((base i64)) -> (vec i64) + (let values (vec i64) (std.vec.i64.empty)) + (let first (vec i64) (std.vec.i64.append values base)) + (std.vec.i64.append first (+ base 1i64))) + +(fn vec_f64_pair ((base f64)) -> (vec f64) + (let values (vec f64) (std.vec.f64.empty)) + (let first (vec f64) (std.vec.f64.append values base)) + (std.vec.f64.append first (+ base 1.0))) + +(fn vec_bool_pair ((first bool) (second bool)) -> (vec bool) + (let values (vec bool) (std.vec.bool.empty)) + (let left (vec bool) (std.vec.bool.append values first)) + (std.vec.bool.append left second)) + +(fn vec_string_pair ((first string) (second string)) -> (vec string) + (let values (vec string) (std.vec.string.empty)) + (let left (vec string) (std.vec.string.append values first)) + (std.vec.string.append left second)) + +(fn swap_string ((first string) (second string)) -> string + (var current string first) + (set current second) + current) + +(fn vec_i32_local_value () -> i32 + (var current (vec i32) (vec_i32_pair 10)) + (set current (vec_i32_pair 40)) + (std.vec.i32.index current 1)) + +(fn vec_i64_local_value () -> i64 + (var current (vec i64) (vec_i64_pair 10i64)) + (set current (vec_i64_pair 40i64)) + (std.vec.i64.index current 1)) + +(fn vec_f64_local_value () -> f64 + (var current (vec f64) (vec_f64_pair 10.0)) + (set current (vec_f64_pair 40.0)) + (std.vec.f64.index current 1)) + +(fn vec_bool_local_value () -> bool + (var current (vec bool) (vec_bool_pair true false)) + (set current (vec_bool_pair false true)) + (std.vec.bool.index current 1)) + +(fn vec_string_local_value () -> string + (var current (vec string) (vec_string_pair "oak" "elm")) + (set current (vec_string_pair "pine" "slovo")) + (std.vec.string.index current 1)) + +(fn option_i32_local_value () -> i32 + (var current (option i32) (none i32)) + (set current (some i32 42)) + (match current + ((some payload) + payload) + ((none) + 0))) + +(fn option_i64_local_value () -> i64 + (var current (option i64) (none i64)) + (set current (some i64 42000000000i64)) + (match current + ((some payload) + payload) + ((none) + 0i64))) + +(fn option_f64_local_value () -> f64 + (var current (option f64) (none f64)) + (set current (some f64 42.5)) + (match current + ((some payload) + payload) + ((none) + 0.0))) + +(fn option_bool_local_value () -> bool + (var current (option bool) (none bool)) + (set current (some bool true)) + (match current + ((some payload) + payload) + ((none) + false))) + +(fn option_string_local_value () -> string + (var current (option string) (none string)) + (set current (some string "branch")) + (match current + ((some payload) + payload) + ((none) + "fallback"))) + +(fn result_i32_local_value () -> i32 + (var current (result i32 i32) (err i32 i32 7)) + (set current (ok i32 i32 42)) + (match current + ((ok payload) + payload) + ((err code) + code))) + +(fn result_i64_local_value () -> i64 + (var current (result i64 i32) (err i64 i32 7)) + (set current (ok i64 i32 42000000000i64)) + (match current + ((ok payload) + payload) + ((err code) + 0i64))) + +(fn result_f64_local_value () -> f64 + (var current (result f64 i32) (err f64 i32 7)) + (set current (ok f64 i32 42.5)) + (match current + ((ok payload) + payload) + ((err code) + 0.0))) + +(fn result_bool_local_value () -> bool + (var current (result bool i32) (err bool i32 7)) + (set current (ok bool i32 true)) + (match current + ((ok payload) + payload) + ((err code) + false))) + +(fn result_string_local_value () -> string + (var current (result string i32) (err string i32 7)) + (set current (ok string i32 "slovo")) + (match current + ((ok payload) + payload) + ((err code) + "fallback"))) + +(fn point_local_sum () -> i32 + (var current Point (make_point 1 2)) + (set current (make_point 20 22)) + (+ (. current x) (. current y))) + +(fn primitive_record_local_label () -> string + (var current PrimitiveRecord (make_primitive_record 3 "alpha")) + (set current (make_primitive_record 7 "beta")) + (. current label)) + +(fn numeric_record_local_ratio () -> f64 + (var current NumericRecord (make_numeric_record 10i64 1.0)) + (set current (make_numeric_record 20i64 3.5)) + (. current ratio)) + +(fn signal_local_code () -> i32 + (var current Signal (Signal.Red)) + (set current (Signal.Green)) + (match current + ((Signal.Red) + 1) + ((Signal.Yellow) + 2) + ((Signal.Green) + 3))) + +(fn reading_local_code () -> i32 + (var current Reading (Reading.Missing)) + (set current (Reading.Value 42)) + (match current + ((Reading.Missing) + 0) + ((Reading.Value payload) + payload))) + +(fn tagged_local_code () -> i32 + (var current TaggedReading (make_tagged (Signal.Yellow) (Reading.Missing))) + (set current (make_tagged (Signal.Green) (Reading.Value 42))) + (match (. current reading) + ((Reading.Missing) + 0) + ((Reading.Value payload) + payload))) + +(test "mutable string local set works" + (= (swap_string "oak" "slovo") "slovo")) + +(test "mutable vec i32 local set works" + (= (vec_i32_local_value) 41)) + +(test "mutable vec i64 local set works" + (= (vec_i64_local_value) 41i64)) + +(test "mutable vec f64 local set works" + (= (vec_f64_local_value) 41.0)) + +(test "mutable vec bool local set works" + (vec_bool_local_value)) + +(test "mutable vec string local set works" + (= (vec_string_local_value) "slovo")) + +(test "mutable option i32 local set works" + (= (option_i32_local_value) 42)) + +(test "mutable option i64 local set works" + (= (option_i64_local_value) 42000000000i64)) + +(test "mutable option f64 local set works" + (= (option_f64_local_value) 42.5)) + +(test "mutable option bool local set works" + (option_bool_local_value)) + +(test "mutable option string local set works" + (= (option_string_local_value) "branch")) + +(test "mutable result i32 local set works" + (= (result_i32_local_value) 42)) + +(test "mutable result i64 local set works" + (= (result_i64_local_value) 42000000000i64)) + +(test "mutable result f64 local set works" + (= (result_f64_local_value) 42.5)) + +(test "mutable result bool local set works" + (result_bool_local_value)) + +(test "mutable result string local set works" + (= (result_string_local_value) "slovo")) + +(test "mutable plain struct local set works" + (= (point_local_sum) 42)) + +(test "mutable primitive struct local set works" + (= (primitive_record_local_label) "beta")) + +(test "mutable numeric struct local set works" + (= (numeric_record_local_ratio) 3.5)) + +(test "mutable payloadless enum local set works" + (= (signal_local_code) 3)) + +(test "mutable payload enum local set works" + (= (reading_local_code) 42)) + +(test "mutable enum struct local set works" + (= (tagged_local_code) 42)) + +(fn main () -> i32 + (tagged_local_code)) diff --git a/tests/composite-locals.surface.lower b/tests/composite-locals.surface.lower new file mode 100644 index 0000000..5172167 --- /dev/null +++ b/tests/composite-locals.surface.lower @@ -0,0 +1,467 @@ +program main + enum Signal + variant Red + variant Yellow + variant Green + enum Reading + variant Missing + variant Value i32 + struct Point + field x: i32 + field y: i32 + struct PrimitiveRecord + field id: i32 + field active: bool + field label: string + struct NumericRecord + field wide: i64 + field ratio: f64 + struct TaggedReading + field status: Signal + field reading: Reading + fn make_point(x: i32, y: i32) -> Point + construct Point + field x + var x + field y + var y + fn make_primitive_record(id: i32, label: string) -> PrimitiveRecord + construct PrimitiveRecord + field id + var id + field active + binary = + var id + int 7 + field label + var label + fn make_numeric_record(wide: i64, ratio: f64) -> NumericRecord + construct NumericRecord + field wide + var wide + field ratio + var ratio + fn make_tagged(status: Signal, reading: Reading) -> TaggedReading + construct TaggedReading + field status + var status + field reading + var reading + fn vec_i32_pair(base: i32) -> (vec i32) + local let values: (vec i32) + call std.vec.i32.empty + local let first: (vec i32) + call std.vec.i32.append + var values + var base + call std.vec.i32.append + var first + binary + + var base + int 1 + fn vec_i64_pair(base: i64) -> (vec i64) + local let values: (vec i64) + call std.vec.i64.empty + local let first: (vec i64) + call std.vec.i64.append + var values + var base + call std.vec.i64.append + var first + binary + + var base + i64 1 + fn vec_f64_pair(base: f64) -> (vec f64) + local let values: (vec f64) + call std.vec.f64.empty + local let first: (vec f64) + call std.vec.f64.append + var values + var base + call std.vec.f64.append + var first + binary + + var base + float 1 + fn vec_bool_pair(first: bool, second: bool) -> (vec bool) + local let values: (vec bool) + call std.vec.bool.empty + local let left: (vec bool) + call std.vec.bool.append + var values + var first + call std.vec.bool.append + var left + var second + fn vec_string_pair(first: string, second: string) -> (vec string) + local let values: (vec string) + call std.vec.string.empty + local let left: (vec string) + call std.vec.string.append + var values + var first + call std.vec.string.append + var left + var second + fn swap_string(first: string, second: string) -> string + local var current: string + var first + set current + var second + var current + fn vec_i32_local_value() -> i32 + local var current: (vec i32) + call vec_i32_pair + int 10 + set current + call vec_i32_pair + int 40 + call std.vec.i32.index + var current + int 1 + fn vec_i64_local_value() -> i64 + local var current: (vec i64) + call vec_i64_pair + i64 10 + set current + call vec_i64_pair + i64 40 + call std.vec.i64.index + var current + int 1 + fn vec_f64_local_value() -> f64 + local var current: (vec f64) + call vec_f64_pair + float 10 + set current + call vec_f64_pair + float 40 + call std.vec.f64.index + var current + int 1 + fn vec_bool_local_value() -> bool + local var current: (vec bool) + call vec_bool_pair + bool true + bool false + set current + call vec_bool_pair + bool false + bool true + call std.vec.bool.index + var current + int 1 + fn vec_string_local_value() -> string + local var current: (vec string) + call vec_string_pair + string "oak" + string "elm" + set current + call vec_string_pair + string "pine" + string "slovo" + call std.vec.string.index + var current + int 1 + fn option_i32_local_value() -> i32 + local var current: (option i32) + none i32 + set current + some i32 + int 42 + match + subject + var current + arm some payload + var payload + arm none + int 0 + fn option_i64_local_value() -> i64 + local var current: (option i64) + none i64 + set current + some i64 + i64 42000000000 + match + subject + var current + arm some payload + var payload + arm none + i64 0 + fn option_f64_local_value() -> f64 + local var current: (option f64) + none f64 + set current + some f64 + float 42.5 + match + subject + var current + arm some payload + var payload + arm none + float 0 + fn option_bool_local_value() -> bool + local var current: (option bool) + none bool + set current + some bool + bool true + match + subject + var current + arm some payload + var payload + arm none + bool false + fn option_string_local_value() -> string + local var current: (option string) + none string + set current + some string + string "branch" + match + subject + var current + arm some payload + var payload + arm none + string "fallback" + fn result_i32_local_value() -> i32 + local var current: (result i32 i32) + err i32 i32 + int 7 + set current + ok i32 i32 + int 42 + match + subject + var current + arm ok payload + var payload + arm err code + var code + fn result_i64_local_value() -> i64 + local var current: (result i64 i32) + err i64 i32 + int 7 + set current + ok i64 i32 + i64 42000000000 + match + subject + var current + arm ok payload + var payload + arm err code + i64 0 + fn result_f64_local_value() -> f64 + local var current: (result f64 i32) + err f64 i32 + int 7 + set current + ok f64 i32 + float 42.5 + match + subject + var current + arm ok payload + var payload + arm err code + float 0 + fn result_bool_local_value() -> bool + local var current: (result bool i32) + err bool i32 + int 7 + set current + ok bool i32 + bool true + match + subject + var current + arm ok payload + var payload + arm err code + bool false + fn result_string_local_value() -> string + local var current: (result string i32) + err string i32 + int 7 + set current + ok string i32 + string "slovo" + match + subject + var current + arm ok payload + var payload + arm err code + string "fallback" + fn point_local_sum() -> i32 + local var current: Point + call make_point + int 1 + int 2 + set current + call make_point + int 20 + int 22 + binary + + field-access x + var current + field-access y + var current + fn primitive_record_local_label() -> string + local var current: PrimitiveRecord + call make_primitive_record + int 3 + string "alpha" + set current + call make_primitive_record + int 7 + string "beta" + field-access label + var current + fn numeric_record_local_ratio() -> f64 + local var current: NumericRecord + call make_numeric_record + i64 10 + float 1 + set current + call make_numeric_record + i64 20 + float 3.5 + field-access ratio + var current + fn signal_local_code() -> i32 + local var current: Signal + enum-variant Signal.Red + set current + enum-variant Signal.Green + match + subject + var current + arm Signal.Red + int 1 + arm Signal.Yellow + int 2 + arm Signal.Green + int 3 + fn reading_local_code() -> i32 + local var current: Reading + enum-variant Reading.Missing + set current + enum-variant Reading.Value + int 42 + match + subject + var current + arm Reading.Missing + int 0 + arm Reading.Value payload + var payload + fn tagged_local_code() -> i32 + local var current: TaggedReading + call make_tagged + enum-variant Signal.Yellow + enum-variant Reading.Missing + set current + call make_tagged + enum-variant Signal.Green + enum-variant Reading.Value + int 42 + match + subject + field-access reading + var current + arm Reading.Missing + int 0 + arm Reading.Value payload + var payload + fn main() -> i32 + call tagged_local_code + test "mutable string local set works" + binary = + call swap_string + string "oak" + string "slovo" + string "slovo" + test "mutable vec i32 local set works" + binary = + call vec_i32_local_value + int 41 + test "mutable vec i64 local set works" + binary = + call vec_i64_local_value + i64 41 + test "mutable vec f64 local set works" + binary = + call vec_f64_local_value + float 41 + test "mutable vec bool local set works" + call vec_bool_local_value + test "mutable vec string local set works" + binary = + call vec_string_local_value + string "slovo" + test "mutable option i32 local set works" + binary = + call option_i32_local_value + int 42 + test "mutable option i64 local set works" + binary = + call option_i64_local_value + i64 42000000000 + test "mutable option f64 local set works" + binary = + call option_f64_local_value + float 42.5 + test "mutable option bool local set works" + call option_bool_local_value + test "mutable option string local set works" + binary = + call option_string_local_value + string "branch" + test "mutable result i32 local set works" + binary = + call result_i32_local_value + int 42 + test "mutable result i64 local set works" + binary = + call result_i64_local_value + i64 42000000000 + test "mutable result f64 local set works" + binary = + call result_f64_local_value + float 42.5 + test "mutable result bool local set works" + call result_bool_local_value + test "mutable result string local set works" + binary = + call result_string_local_value + string "slovo" + test "mutable plain struct local set works" + binary = + call point_local_sum + int 42 + test "mutable primitive struct local set works" + binary = + call primitive_record_local_label + string "beta" + test "mutable numeric struct local set works" + binary = + call numeric_record_local_ratio + float 3.5 + test "mutable payloadless enum local set works" + binary = + call signal_local_code + int 3 + test "mutable payload enum local set works" + binary = + call reading_local_code + int 42 + test "mutable enum struct local set works" + binary = + call tagged_local_code + int 42 diff --git a/tests/composite-struct-fields.checked.lower b/tests/composite-struct-fields.checked.lower new file mode 100644 index 0000000..f4b10f3 --- /dev/null +++ b/tests/composite-struct-fields.checked.lower @@ -0,0 +1,362 @@ +program main + enum Status + variant Ready + variant Blocked + struct Pair + field left: i32 + field right: i32 + struct CompositeRecord + field ints: (vec i32) + field wides: (vec i64) + field ratios: (vec f64) + field flags: (vec bool) + field labels: (vec string) + field maybe_count: (option i32) + field maybe_wide: (option i64) + field maybe_ratio: (option f64) + field maybe_flag: (option bool) + field maybe_label: (option string) + field count_result: (result i32 i32) + field wide_result: (result i64 i32) + field ratio_result: (result f64 i32) + field flag_result: (result bool i32) + field label_result: (result string i32) + field pair: Pair + field status: Status + fn make_pair(left: i32, right: i32) -> Pair + construct Pair : Pair + field left + var left : i32 + field right + var right : i32 + fn vec_i32_pair(base: i32) -> (vec i32) + local let values : unit + call std.vec.i32.empty : (vec i32) + local let first : unit + call std.vec.i32.append : (vec i32) + var values : (vec i32) + var base : i32 + call std.vec.i32.append : (vec i32) + var first : (vec i32) + binary + : i32 + var base : i32 + int 1 : i32 + fn vec_i64_pair(base: i64) -> (vec i64) + local let values : unit + call std.vec.i64.empty : (vec i64) + local let first : unit + call std.vec.i64.append : (vec i64) + var values : (vec i64) + var base : i64 + call std.vec.i64.append : (vec i64) + var first : (vec i64) + binary + : i64 + var base : i64 + i64 1 : i64 + fn vec_f64_pair(base: f64) -> (vec f64) + local let values : unit + call std.vec.f64.empty : (vec f64) + local let first : unit + call std.vec.f64.append : (vec f64) + var values : (vec f64) + var base : f64 + call std.vec.f64.append : (vec f64) + var first : (vec f64) + binary + : f64 + var base : f64 + float 1 : f64 + fn vec_bool_pair(first: bool, second: bool) -> (vec bool) + local let values : unit + call std.vec.bool.empty : (vec bool) + local let left : unit + call std.vec.bool.append : (vec bool) + var values : (vec bool) + var first : bool + call std.vec.bool.append : (vec bool) + var left : (vec bool) + var second : bool + fn vec_string_pair(first: string, second: string) -> (vec string) + local let values : unit + call std.vec.string.empty : (vec string) + local let left : unit + call std.vec.string.append : (vec string) + var values : (vec string) + var first : string + call std.vec.string.append : (vec string) + var left : (vec string) + var second : string + fn make_record() -> CompositeRecord + construct CompositeRecord : CompositeRecord + field ints + call vec_i32_pair : (vec i32) + int 10 : i32 + field wides + call vec_i64_pair : (vec i64) + i64 100 : i64 + field ratios + call vec_f64_pair : (vec f64) + float 3.5 : f64 + field flags + call vec_bool_pair : (vec bool) + bool false : bool + bool true : bool + field labels + call vec_string_pair : (vec string) + string "oak" : string + string "elm" : string + field maybe_count + some : (option i32) + int 7 : i32 + field maybe_wide + some : (option i64) + i64 7000000000 : i64 + field maybe_ratio + some : (option f64) + float 7.5 : f64 + field maybe_flag + some : (option bool) + bool true : bool + field maybe_label + some : (option string) + string "alpha" : string + field count_result + ok : (result i32 i32) + int 9 : i32 + field wide_result + ok : (result i64 i32) + i64 9000000000 : i64 + field ratio_result + ok : (result f64 i32) + float 9.25 : f64 + field flag_result + ok : (result bool i32) + bool true : bool + field label_result + ok : (result string i32) + string "beta" : string + field pair + call make_pair : Pair + int 20 : i32 + int 22 : i32 + field status + enum-variant Status.Ready #0 : Status + fn echo_record(record: CompositeRecord) -> CompositeRecord + var record : CompositeRecord + fn local_record() -> CompositeRecord + local let record : unit + call make_record : CompositeRecord + call echo_record : CompositeRecord + var record : CompositeRecord + fn ints_second(record: CompositeRecord) -> i32 + call std.vec.i32.index : i32 + field-access ints : (vec i32) + var record : CompositeRecord + int 1 : i32 + fn wides_second(record: CompositeRecord) -> i64 + call std.vec.i64.index : i64 + field-access wides : (vec i64) + var record : CompositeRecord + int 1 : i32 + fn ratios_second(record: CompositeRecord) -> f64 + call std.vec.f64.index : f64 + field-access ratios : (vec f64) + var record : CompositeRecord + int 1 : i32 + fn flags_second(record: CompositeRecord) -> bool + call std.vec.bool.index : bool + field-access flags : (vec bool) + var record : CompositeRecord + int 1 : i32 + fn labels_second(record: CompositeRecord) -> string + call std.vec.string.index : string + field-access labels : (vec string) + var record : CompositeRecord + int 1 : i32 + fn maybe_count_value(record: CompositeRecord) -> i32 + match : i32 + subject + field-access maybe_count : (option i32) + var record : CompositeRecord + arm some payload + var payload : i32 + arm none + int 0 : i32 + fn maybe_wide_value(record: CompositeRecord) -> i64 + match : i64 + subject + field-access maybe_wide : (option i64) + var record : CompositeRecord + arm some payload + var payload : i64 + arm none + i64 0 : i64 + fn maybe_ratio_value(record: CompositeRecord) -> f64 + match : f64 + subject + field-access maybe_ratio : (option f64) + var record : CompositeRecord + arm some payload + var payload : f64 + arm none + float 0 : f64 + fn maybe_flag_value(record: CompositeRecord) -> bool + match : bool + subject + field-access maybe_flag : (option bool) + var record : CompositeRecord + arm some payload + var payload : bool + arm none + bool false : bool + fn maybe_label_value(record: CompositeRecord) -> string + match : string + subject + field-access maybe_label : (option string) + var record : CompositeRecord + arm some payload + var payload : string + arm none + string "missing" : string + fn count_result_value(record: CompositeRecord) -> i32 + match : i32 + subject + field-access count_result : (result i32 i32) + var record : CompositeRecord + arm ok payload + var payload : i32 + arm err code + var code : i32 + fn wide_result_value(record: CompositeRecord) -> i64 + match : i64 + subject + field-access wide_result : (result i64 i32) + var record : CompositeRecord + arm ok payload + var payload : i64 + arm err code + i64 0 : i64 + fn ratio_result_value(record: CompositeRecord) -> f64 + match : f64 + subject + field-access ratio_result : (result f64 i32) + var record : CompositeRecord + arm ok payload + var payload : f64 + arm err code + float 0 : f64 + fn flag_result_value(record: CompositeRecord) -> bool + match : bool + subject + field-access flag_result : (result bool i32) + var record : CompositeRecord + arm ok payload + var payload : bool + arm err code + bool false : bool + fn label_result_value(record: CompositeRecord) -> string + match : string + subject + field-access label_result : (result string i32) + var record : CompositeRecord + arm ok payload + var payload : string + arm err code + string "missing" : string + fn pair_total(record: CompositeRecord) -> i32 + binary + : i32 + field-access left : i32 + field-access pair : Pair + var record : CompositeRecord + field-access right : i32 + field-access pair : Pair + var record : CompositeRecord + fn is_ready(record: CompositeRecord) -> bool + binary = : bool + field-access status : Status + var record : CompositeRecord + enum-variant Status.Ready #0 : Status + fn main() -> i32 + call pair_total : i32 + call local_record : CompositeRecord + test "struct vec i32 field access" + binary = : bool + call ints_second : i32 + call make_record : CompositeRecord + int 11 : i32 + test "struct vec i64 field access" + binary = : bool + call wides_second : i64 + call make_record : CompositeRecord + i64 101 : i64 + test "struct vec f64 field access" + binary = : bool + call ratios_second : f64 + call make_record : CompositeRecord + float 4.5 : f64 + test "struct vec bool field access" + call flags_second : bool + call make_record : CompositeRecord + test "struct vec string field access" + binary = : bool + call labels_second : string + call make_record : CompositeRecord + string "elm" : string + test "struct option i32 field access" + binary = : bool + call maybe_count_value : i32 + call make_record : CompositeRecord + int 7 : i32 + test "struct option i64 field access" + binary = : bool + call maybe_wide_value : i64 + call make_record : CompositeRecord + i64 7000000000 : i64 + test "struct option f64 field access" + binary = : bool + call maybe_ratio_value : f64 + call make_record : CompositeRecord + float 7.5 : f64 + test "struct option bool field access" + call maybe_flag_value : bool + call make_record : CompositeRecord + test "struct option string field access" + binary = : bool + call maybe_label_value : string + call make_record : CompositeRecord + string "alpha" : string + test "struct result i32 field access" + binary = : bool + call count_result_value : i32 + call make_record : CompositeRecord + int 9 : i32 + test "struct result i64 field access" + binary = : bool + call wide_result_value : i64 + call make_record : CompositeRecord + i64 9000000000 : i64 + test "struct result f64 field access" + binary = : bool + call ratio_result_value : f64 + call make_record : CompositeRecord + float 9.25 : f64 + test "struct result bool field access" + call flag_result_value : bool + call make_record : CompositeRecord + test "struct result string field access" + binary = : bool + call label_result_value : string + call make_record : CompositeRecord + string "beta" : string + test "nested struct field access" + binary = : bool + call pair_total : i32 + call make_record : CompositeRecord + int 42 : i32 + test "nested struct local param return call flow" + binary = : bool + call pair_total : i32 + call local_record : CompositeRecord + int 42 : i32 + test "direct enum struct field equality" + call is_ready : bool + call make_record : CompositeRecord diff --git a/tests/composite-struct-fields.slo b/tests/composite-struct-fields.slo new file mode 100644 index 0000000..fd3028a --- /dev/null +++ b/tests/composite-struct-fields.slo @@ -0,0 +1,212 @@ +(module main) + +(enum Status Ready Blocked) + +(struct Pair + (left i32) + (right i32)) + +(struct CompositeRecord + (ints (vec i32)) + (wides (vec i64)) + (ratios (vec f64)) + (flags (vec bool)) + (labels (vec string)) + (maybe_count (option i32)) + (maybe_wide (option i64)) + (maybe_ratio (option f64)) + (maybe_flag (option bool)) + (maybe_label (option string)) + (count_result (result i32 i32)) + (wide_result (result i64 i32)) + (ratio_result (result f64 i32)) + (flag_result (result bool i32)) + (label_result (result string i32)) + (pair Pair) + (status Status)) + +(fn make_pair ((left i32) (right i32)) -> Pair + (Pair (left left) (right right))) + +(fn vec_i32_pair ((base i32)) -> (vec i32) + (let values (vec i32) (std.vec.i32.empty)) + (let first (vec i32) (std.vec.i32.append values base)) + (std.vec.i32.append first (+ base 1))) + +(fn vec_i64_pair ((base i64)) -> (vec i64) + (let values (vec i64) (std.vec.i64.empty)) + (let first (vec i64) (std.vec.i64.append values base)) + (std.vec.i64.append first (+ base 1i64))) + +(fn vec_f64_pair ((base f64)) -> (vec f64) + (let values (vec f64) (std.vec.f64.empty)) + (let first (vec f64) (std.vec.f64.append values base)) + (std.vec.f64.append first (+ base 1.0))) + +(fn vec_bool_pair ((first bool) (second bool)) -> (vec bool) + (let values (vec bool) (std.vec.bool.empty)) + (let left (vec bool) (std.vec.bool.append values first)) + (std.vec.bool.append left second)) + +(fn vec_string_pair ((first string) (second string)) -> (vec string) + (let values (vec string) (std.vec.string.empty)) + (let left (vec string) (std.vec.string.append values first)) + (std.vec.string.append left second)) + +(fn make_record () -> CompositeRecord + (CompositeRecord (ints (vec_i32_pair 10)) (wides (vec_i64_pair 100i64)) (ratios (vec_f64_pair 3.5)) (flags (vec_bool_pair false true)) (labels (vec_string_pair "oak" "elm")) (maybe_count (some i32 7)) (maybe_wide (some i64 7000000000i64)) (maybe_ratio (some f64 7.5)) (maybe_flag (some bool true)) (maybe_label (some string "alpha")) (count_result (ok i32 i32 9)) (wide_result (ok i64 i32 9000000000i64)) (ratio_result (ok f64 i32 9.25)) (flag_result (ok bool i32 true)) (label_result (ok string i32 "beta")) (pair (make_pair 20 22)) (status (Status.Ready)))) + +(fn echo_record ((record CompositeRecord)) -> CompositeRecord + record) + +(fn local_record () -> CompositeRecord + (let record CompositeRecord (make_record)) + (echo_record record)) + +(fn ints_second ((record CompositeRecord)) -> i32 + (std.vec.i32.index (. record ints) 1)) + +(fn wides_second ((record CompositeRecord)) -> i64 + (std.vec.i64.index (. record wides) 1)) + +(fn ratios_second ((record CompositeRecord)) -> f64 + (std.vec.f64.index (. record ratios) 1)) + +(fn flags_second ((record CompositeRecord)) -> bool + (std.vec.bool.index (. record flags) 1)) + +(fn labels_second ((record CompositeRecord)) -> string + (std.vec.string.index (. record labels) 1)) + +(fn maybe_count_value ((record CompositeRecord)) -> i32 + (match (. record maybe_count) + ((some payload) + payload) + ((none) + 0))) + +(fn maybe_wide_value ((record CompositeRecord)) -> i64 + (match (. record maybe_wide) + ((some payload) + payload) + ((none) + 0i64))) + +(fn maybe_ratio_value ((record CompositeRecord)) -> f64 + (match (. record maybe_ratio) + ((some payload) + payload) + ((none) + 0.0))) + +(fn maybe_flag_value ((record CompositeRecord)) -> bool + (match (. record maybe_flag) + ((some payload) + payload) + ((none) + false))) + +(fn maybe_label_value ((record CompositeRecord)) -> string + (match (. record maybe_label) + ((some payload) + payload) + ((none) + "missing"))) + +(fn count_result_value ((record CompositeRecord)) -> i32 + (match (. record count_result) + ((ok payload) + payload) + ((err code) + code))) + +(fn wide_result_value ((record CompositeRecord)) -> i64 + (match (. record wide_result) + ((ok payload) + payload) + ((err code) + 0i64))) + +(fn ratio_result_value ((record CompositeRecord)) -> f64 + (match (. record ratio_result) + ((ok payload) + payload) + ((err code) + 0.0))) + +(fn flag_result_value ((record CompositeRecord)) -> bool + (match (. record flag_result) + ((ok payload) + payload) + ((err code) + false))) + +(fn label_result_value ((record CompositeRecord)) -> string + (match (. record label_result) + ((ok payload) + payload) + ((err code) + "missing"))) + +(fn pair_total ((record CompositeRecord)) -> i32 + (+ (. (. record pair) left) (. (. record pair) right))) + +(fn is_ready ((record CompositeRecord)) -> bool + (= (. record status) (Status.Ready))) + +(test "struct vec i32 field access" + (= (ints_second (make_record)) 11)) + +(test "struct vec i64 field access" + (= (wides_second (make_record)) 101i64)) + +(test "struct vec f64 field access" + (= (ratios_second (make_record)) 4.5)) + +(test "struct vec bool field access" + (flags_second (make_record))) + +(test "struct vec string field access" + (= (labels_second (make_record)) "elm")) + +(test "struct option i32 field access" + (= (maybe_count_value (make_record)) 7)) + +(test "struct option i64 field access" + (= (maybe_wide_value (make_record)) 7000000000i64)) + +(test "struct option f64 field access" + (= (maybe_ratio_value (make_record)) 7.5)) + +(test "struct option bool field access" + (maybe_flag_value (make_record))) + +(test "struct option string field access" + (= (maybe_label_value (make_record)) "alpha")) + +(test "struct result i32 field access" + (= (count_result_value (make_record)) 9)) + +(test "struct result i64 field access" + (= (wide_result_value (make_record)) 9000000000i64)) + +(test "struct result f64 field access" + (= (ratio_result_value (make_record)) 9.25)) + +(test "struct result bool field access" + (flag_result_value (make_record))) + +(test "struct result string field access" + (= (label_result_value (make_record)) "beta")) + +(test "nested struct field access" + (= (pair_total (make_record)) 42)) + +(test "nested struct local param return call flow" + (= (pair_total (local_record)) 42)) + +(test "direct enum struct field equality" + (is_ready (make_record))) + +(fn main () -> i32 + (pair_total (local_record))) diff --git a/tests/composite-struct-fields.surface.lower b/tests/composite-struct-fields.surface.lower new file mode 100644 index 0000000..ffde0f1 --- /dev/null +++ b/tests/composite-struct-fields.surface.lower @@ -0,0 +1,362 @@ +program main + enum Status + variant Ready + variant Blocked + struct Pair + field left: i32 + field right: i32 + struct CompositeRecord + field ints: (vec i32) + field wides: (vec i64) + field ratios: (vec f64) + field flags: (vec bool) + field labels: (vec string) + field maybe_count: (option i32) + field maybe_wide: (option i64) + field maybe_ratio: (option f64) + field maybe_flag: (option bool) + field maybe_label: (option string) + field count_result: (result i32 i32) + field wide_result: (result i64 i32) + field ratio_result: (result f64 i32) + field flag_result: (result bool i32) + field label_result: (result string i32) + field pair: Pair + field status: Status + fn make_pair(left: i32, right: i32) -> Pair + construct Pair + field left + var left + field right + var right + fn vec_i32_pair(base: i32) -> (vec i32) + local let values: (vec i32) + call std.vec.i32.empty + local let first: (vec i32) + call std.vec.i32.append + var values + var base + call std.vec.i32.append + var first + binary + + var base + int 1 + fn vec_i64_pair(base: i64) -> (vec i64) + local let values: (vec i64) + call std.vec.i64.empty + local let first: (vec i64) + call std.vec.i64.append + var values + var base + call std.vec.i64.append + var first + binary + + var base + i64 1 + fn vec_f64_pair(base: f64) -> (vec f64) + local let values: (vec f64) + call std.vec.f64.empty + local let first: (vec f64) + call std.vec.f64.append + var values + var base + call std.vec.f64.append + var first + binary + + var base + float 1 + fn vec_bool_pair(first: bool, second: bool) -> (vec bool) + local let values: (vec bool) + call std.vec.bool.empty + local let left: (vec bool) + call std.vec.bool.append + var values + var first + call std.vec.bool.append + var left + var second + fn vec_string_pair(first: string, second: string) -> (vec string) + local let values: (vec string) + call std.vec.string.empty + local let left: (vec string) + call std.vec.string.append + var values + var first + call std.vec.string.append + var left + var second + fn make_record() -> CompositeRecord + construct CompositeRecord + field ints + call vec_i32_pair + int 10 + field wides + call vec_i64_pair + i64 100 + field ratios + call vec_f64_pair + float 3.5 + field flags + call vec_bool_pair + bool false + bool true + field labels + call vec_string_pair + string "oak" + string "elm" + field maybe_count + some i32 + int 7 + field maybe_wide + some i64 + i64 7000000000 + field maybe_ratio + some f64 + float 7.5 + field maybe_flag + some bool + bool true + field maybe_label + some string + string "alpha" + field count_result + ok i32 i32 + int 9 + field wide_result + ok i64 i32 + i64 9000000000 + field ratio_result + ok f64 i32 + float 9.25 + field flag_result + ok bool i32 + bool true + field label_result + ok string i32 + string "beta" + field pair + call make_pair + int 20 + int 22 + field status + enum-variant Status.Ready + fn echo_record(record: CompositeRecord) -> CompositeRecord + var record + fn local_record() -> CompositeRecord + local let record: CompositeRecord + call make_record + call echo_record + var record + fn ints_second(record: CompositeRecord) -> i32 + call std.vec.i32.index + field-access ints + var record + int 1 + fn wides_second(record: CompositeRecord) -> i64 + call std.vec.i64.index + field-access wides + var record + int 1 + fn ratios_second(record: CompositeRecord) -> f64 + call std.vec.f64.index + field-access ratios + var record + int 1 + fn flags_second(record: CompositeRecord) -> bool + call std.vec.bool.index + field-access flags + var record + int 1 + fn labels_second(record: CompositeRecord) -> string + call std.vec.string.index + field-access labels + var record + int 1 + fn maybe_count_value(record: CompositeRecord) -> i32 + match + subject + field-access maybe_count + var record + arm some payload + var payload + arm none + int 0 + fn maybe_wide_value(record: CompositeRecord) -> i64 + match + subject + field-access maybe_wide + var record + arm some payload + var payload + arm none + i64 0 + fn maybe_ratio_value(record: CompositeRecord) -> f64 + match + subject + field-access maybe_ratio + var record + arm some payload + var payload + arm none + float 0 + fn maybe_flag_value(record: CompositeRecord) -> bool + match + subject + field-access maybe_flag + var record + arm some payload + var payload + arm none + bool false + fn maybe_label_value(record: CompositeRecord) -> string + match + subject + field-access maybe_label + var record + arm some payload + var payload + arm none + string "missing" + fn count_result_value(record: CompositeRecord) -> i32 + match + subject + field-access count_result + var record + arm ok payload + var payload + arm err code + var code + fn wide_result_value(record: CompositeRecord) -> i64 + match + subject + field-access wide_result + var record + arm ok payload + var payload + arm err code + i64 0 + fn ratio_result_value(record: CompositeRecord) -> f64 + match + subject + field-access ratio_result + var record + arm ok payload + var payload + arm err code + float 0 + fn flag_result_value(record: CompositeRecord) -> bool + match + subject + field-access flag_result + var record + arm ok payload + var payload + arm err code + bool false + fn label_result_value(record: CompositeRecord) -> string + match + subject + field-access label_result + var record + arm ok payload + var payload + arm err code + string "missing" + fn pair_total(record: CompositeRecord) -> i32 + binary + + field-access left + field-access pair + var record + field-access right + field-access pair + var record + fn is_ready(record: CompositeRecord) -> bool + binary = + field-access status + var record + enum-variant Status.Ready + fn main() -> i32 + call pair_total + call local_record + test "struct vec i32 field access" + binary = + call ints_second + call make_record + int 11 + test "struct vec i64 field access" + binary = + call wides_second + call make_record + i64 101 + test "struct vec f64 field access" + binary = + call ratios_second + call make_record + float 4.5 + test "struct vec bool field access" + call flags_second + call make_record + test "struct vec string field access" + binary = + call labels_second + call make_record + string "elm" + test "struct option i32 field access" + binary = + call maybe_count_value + call make_record + int 7 + test "struct option i64 field access" + binary = + call maybe_wide_value + call make_record + i64 7000000000 + test "struct option f64 field access" + binary = + call maybe_ratio_value + call make_record + float 7.5 + test "struct option bool field access" + call maybe_flag_value + call make_record + test "struct option string field access" + binary = + call maybe_label_value + call make_record + string "alpha" + test "struct result i32 field access" + binary = + call count_result_value + call make_record + int 9 + test "struct result i64 field access" + binary = + call wide_result_value + call make_record + i64 9000000000 + test "struct result f64 field access" + binary = + call ratio_result_value + call make_record + float 9.25 + test "struct result bool field access" + call flag_result_value + call make_record + test "struct result string field access" + binary = + call label_result_value + call make_record + string "beta" + test "nested struct field access" + binary = + call pair_total + call make_record + int 42 + test "nested struct local param return call flow" + binary = + call pair_total + call local_record + int 42 + test "direct enum struct field equality" + call is_ready + call make_record diff --git a/tests/cyclic-struct-fields.diag b/tests/cyclic-struct-fields.diag new file mode 100644 index 0000000..e2d6bcd --- /dev/null +++ b/tests/cyclic-struct-fields.diag @@ -0,0 +1,21 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code RecursiveStructFieldUnsupported) + (message "recursive struct fields are not supported (`Left -> Right -> Left`)") + (file "") + (span + (bytes 69 73) + (range 8 9 8 13) + ) + (hint "flatten the struct graph or remove the recursive field") + (related + (span + (file "") + (bytes 24 28) + (range 4 9 4 13) + (message "recursive struct declaration") + ) + ) +) diff --git a/tests/duplicate-local.diag b/tests/duplicate-local.diag new file mode 100644 index 0000000..c6bc5ee --- /dev/null +++ b/tests/duplicate-local.diag @@ -0,0 +1,20 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code DuplicateLocal) + (message "duplicate local declaration `x`") + (file "") + (span + (bytes 58 59) + (range 6 8 6 9) + ) + (related + (span + (file "") + (bytes 42 43) + (range 5 8 5 9) + (message "original local declaration") + ) + ) +) diff --git a/tests/duplicate-match-arm.diag b/tests/duplicate-match-arm.diag new file mode 100644 index 0000000..a057f20 --- /dev/null +++ b/tests/duplicate-match-arm.diag @@ -0,0 +1,20 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code DuplicateMatchArm) + (message "duplicate `some` match arm") + (file "") + (span + (bytes 110 122) + (range 8 6 8 18) + ) + (related + (span + (file "") + (bytes 75 89) + (range 6 6 6 20) + (message "original match arm") + ) + ) +) diff --git a/tests/duplicate-struct-constructor-field.diag b/tests/duplicate-struct-constructor-field.diag new file mode 100644 index 0000000..39f52f1 --- /dev/null +++ b/tests/duplicate-struct-constructor-field.diag @@ -0,0 +1,20 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code DuplicateStructConstructorField) + (message "constructor for `Point` repeats field `x`") + (file "") + (span + (bytes 80 81) + (range 8 20 8 21) + ) + (related + (span + (file "") + (bytes 74 75) + (range 8 14 8 15) + (message "original constructor field") + ) + ) +) diff --git a/tests/duplicate-struct-field.diag b/tests/duplicate-struct-field.diag new file mode 100644 index 0000000..89ee413 --- /dev/null +++ b/tests/duplicate-struct-field.diag @@ -0,0 +1,20 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code DuplicateStructField) + (message "duplicate field `x` in struct `Point`") + (file "") + (span + (bytes 43 44) + (range 6 4 6 5) + ) + (related + (span + (file "") + (bytes 33 34) + (range 5 4 5 5) + (message "original field declaration") + ) + ) +) diff --git a/tests/duplicate-struct.diag b/tests/duplicate-struct.diag new file mode 100644 index 0000000..83c84bb --- /dev/null +++ b/tests/duplicate-struct.diag @@ -0,0 +1,20 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code DuplicateStruct) + (message "duplicate struct `Point`") + (file "") + (span + (bytes 50 55) + (range 7 9 7 14) + ) + (related + (span + (file "") + (bytes 24 29) + (range 4 9 4 14) + (message "original struct declaration") + ) + ) +) diff --git a/tests/empty-array.diag b/tests/empty-array.diag new file mode 100644 index 0000000..ef3c8d0 --- /dev/null +++ b/tests/empty-array.diag @@ -0,0 +1,13 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code EmptyArrayUnsupported) + (message "first-pass arrays must contain at least one element") + (file "") + (span + (bytes 44 55) + (range 5 10 5 21) + ) + (hint "provide one or more supported direct scalar, string, enum, or struct values") +) diff --git a/tests/empty-struct.diag b/tests/empty-struct.diag new file mode 100644 index 0000000..1e25841 --- /dev/null +++ b/tests/empty-struct.diag @@ -0,0 +1,13 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code EmptyStructUnsupported) + (message "first-pass structs must declare at least one field") + (file "") + (span + (bytes 16 30) + (range 4 1 4 15) + ) + (hint "declare at least one `i32` field") +) diff --git a/tests/empty-while-body.diag b/tests/empty-while-body.diag new file mode 100644 index 0000000..199d417 --- /dev/null +++ b/tests/empty-while-body.diag @@ -0,0 +1,13 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code EmptyWhileBody) + (message "`while` must have at least one body form") + (file "") + (span + (bytes 37 49) + (range 5 3 5 15) + ) + (hint "provide at least one unit-producing loop body form") +) diff --git a/tests/enum-basic.checked.lower b/tests/enum-basic.checked.lower new file mode 100644 index 0000000..11be6ad --- /dev/null +++ b/tests/enum-basic.checked.lower @@ -0,0 +1,64 @@ +program main + enum Signal + variant Red + variant Yellow + variant Green + fn red() -> Signal + enum-variant Signal.Red #0 : Signal + fn yellow() -> Signal + enum-variant Signal.Yellow #1 : Signal + fn echo(signal: Signal) -> Signal + var signal : Signal + fn local_signal() -> Signal + local let signal : unit + enum-variant Signal.Green #2 : Signal + var signal : Signal + fn same_signal(left: Signal, right: Signal) -> bool + binary = : bool + var left : Signal + var right : Signal + fn signal_code(signal: Signal) -> i32 + match : i32 + subject + var signal : Signal + arm Signal.Red + int 1 : i32 + arm Signal.Yellow + local let code : unit + int 2 : i32 + var code : i32 + arm Signal.Green + int 3 : i32 + fn call_flow() -> Signal + call echo : Signal + call local_signal : Signal + fn main() -> i32 + call signal_code : i32 + call call_flow : Signal + test "enum constructor equality" + binary = : bool + enum-variant Signal.Red #0 : Signal + call red : Signal + test "enum local return call flow" + binary = : bool + call call_flow : Signal + enum-variant Signal.Green #2 : Signal + test "enum parameter equality" + call same_signal : bool + call yellow : Signal + enum-variant Signal.Yellow #1 : Signal + test "enum match red" + binary = : bool + call signal_code : i32 + enum-variant Signal.Red #0 : Signal + int 1 : i32 + test "enum match multi expression arm" + binary = : bool + call signal_code : i32 + enum-variant Signal.Yellow #1 : Signal + int 2 : i32 + test "enum match green" + binary = : bool + call signal_code : i32 + call call_flow : Signal + int 3 : i32 diff --git a/tests/enum-basic.slo b/tests/enum-basic.slo new file mode 100644 index 0000000..b4e3799 --- /dev/null +++ b/tests/enum-basic.slo @@ -0,0 +1,53 @@ +(module main) + +(enum Signal Red Yellow Green) + +(fn red () -> Signal + (Signal.Red)) + +(fn yellow () -> Signal + (Signal.Yellow)) + +(fn echo ((signal Signal)) -> Signal + signal) + +(fn local_signal () -> Signal + (let signal Signal (Signal.Green)) + signal) + +(fn same_signal ((left Signal) (right Signal)) -> bool + (= left right)) + +(fn signal_code ((signal Signal)) -> i32 + (match signal + ((Signal.Red) + 1) + ((Signal.Yellow) + (let code i32 2) + code) + ((Signal.Green) + 3))) + +(fn call_flow () -> Signal + (echo (local_signal))) + +(test "enum constructor equality" + (= (Signal.Red) (red))) + +(test "enum local return call flow" + (= (call_flow) (Signal.Green))) + +(test "enum parameter equality" + (same_signal (yellow) (Signal.Yellow))) + +(test "enum match red" + (= (signal_code (Signal.Red)) 1)) + +(test "enum match multi expression arm" + (= (signal_code (Signal.Yellow)) 2)) + +(test "enum match green" + (= (signal_code (call_flow)) 3)) + +(fn main () -> i32 + (signal_code (call_flow))) diff --git a/tests/enum-basic.surface.lower b/tests/enum-basic.surface.lower new file mode 100644 index 0000000..bdb2abb --- /dev/null +++ b/tests/enum-basic.surface.lower @@ -0,0 +1,64 @@ +program main + enum Signal + variant Red + variant Yellow + variant Green + fn red() -> Signal + enum-variant Signal.Red + fn yellow() -> Signal + enum-variant Signal.Yellow + fn echo(signal: Signal) -> Signal + var signal + fn local_signal() -> Signal + local let signal: Signal + enum-variant Signal.Green + var signal + fn same_signal(left: Signal, right: Signal) -> bool + binary = + var left + var right + fn signal_code(signal: Signal) -> i32 + match + subject + var signal + arm Signal.Red + int 1 + arm Signal.Yellow + local let code: i32 + int 2 + var code + arm Signal.Green + int 3 + fn call_flow() -> Signal + call echo + call local_signal + fn main() -> i32 + call signal_code + call call_flow + test "enum constructor equality" + binary = + enum-variant Signal.Red + call red + test "enum local return call flow" + binary = + call call_flow + enum-variant Signal.Green + test "enum parameter equality" + call same_signal + call yellow + enum-variant Signal.Yellow + test "enum match red" + binary = + call signal_code + enum-variant Signal.Red + int 1 + test "enum match multi expression arm" + binary = + call signal_code + enum-variant Signal.Yellow + int 2 + test "enum match green" + binary = + call signal_code + call call_flow + int 3 diff --git a/tests/enum-constructor-arity.diag b/tests/enum-constructor-arity.diag new file mode 100644 index 0000000..b00df7e --- /dev/null +++ b/tests/enum-constructor-arity.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code VariantConstructorArity) + (message "enum constructor `Color.Red` takes no arguments") + (file "") + (span + (bytes 57 70) + (range 7 3 7 16) + ) + (expected "0") + (found "1") +) diff --git a/tests/enum-container-values.diag b/tests/enum-container-values.diag new file mode 100644 index 0000000..700a3e7 --- /dev/null +++ b/tests/enum-container-values.diag @@ -0,0 +1,28 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedOptionPayloadType) + (message "first-pass options support only `i32`, `i64`, `u32`, `u64`, `f64`, `bool`, and `string` payloads") + (file "") + (span + (bytes 144 149) + (range 8 35 8 40) + ) + (expected "i32, i64, u32, u64, f64, bool, or string") + (found "Color") +) + +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedEnumContainer) + (message "enum values in option/result containers are not supported") + (file "") + (span + (bytes 117 122) + (range 8 8 8 13) + ) + (hint "store enum values directly in immutable locals") +) diff --git a/tests/enum-duplicate-variant.diag b/tests/enum-duplicate-variant.diag new file mode 100644 index 0000000..d126c44 --- /dev/null +++ b/tests/enum-duplicate-variant.diag @@ -0,0 +1,20 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code DuplicateEnumVariant) + (message "duplicate variant `Red` in enum `Color`") + (file "") + (span + (bytes 32 35) + (range 4 17 4 20) + ) + (related + (span + (file "") + (bytes 28 31) + (range 4 13 4 16) + (message "original variant declaration") + ) + ) +) diff --git a/tests/enum-duplicate.diag b/tests/enum-duplicate.diag new file mode 100644 index 0000000..f3f0580 --- /dev/null +++ b/tests/enum-duplicate.diag @@ -0,0 +1,20 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code DuplicateEnum) + (message "duplicate enum `Color`") + (file "") + (span + (bytes 39 44) + (range 5 7 5 12) + ) + (related + (span + (file "") + (bytes 22 27) + (range 4 7 4 12) + (message "original enum declaration") + ) + ) +) diff --git a/tests/enum-empty.diag b/tests/enum-empty.diag new file mode 100644 index 0000000..85ebf4d --- /dev/null +++ b/tests/enum-empty.diag @@ -0,0 +1,13 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code EmptyEnumUnsupported) + (message "enum declarations must contain at least one variant") + (file "") + (span + (bytes 16 28) + (range 4 1 4 13) + ) + (hint "declare at least one payloadless or unary direct scalar/string payload variant") +) diff --git a/tests/enum-match-duplicate-arm.diag b/tests/enum-match-duplicate-arm.diag new file mode 100644 index 0000000..a915765 --- /dev/null +++ b/tests/enum-match-duplicate-arm.diag @@ -0,0 +1,20 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code DuplicateMatchArm) + (message "duplicate `Color.Red` match arm") + (file "") + (span + (bytes 104 115) + (range 9 6 9 17) + ) + (related + (span + (file "") + (bytes 84 95) + (range 8 6 8 17) + (message "original match arm") + ) + ) +) diff --git a/tests/enum-match-invalid-arm.diag b/tests/enum-match-invalid-arm.diag new file mode 100644 index 0000000..47a1366 --- /dev/null +++ b/tests/enum-match-invalid-arm.diag @@ -0,0 +1,12 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code InvalidEnumMatchArm) + (message "`Shape.Circle` arm is not valid for `Color` match") + (file "") + (span + (bytes 99 113) + (range 9 6 9 20) + ) +) diff --git a/tests/enum-match-non-exhaustive.diag b/tests/enum-match-non-exhaustive.diag new file mode 100644 index 0000000..55a21d0 --- /dev/null +++ b/tests/enum-match-non-exhaustive.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code NonExhaustiveMatch) + (message "match is missing `Color.Blue` arm(s)") + (file "") + (span + (bytes 60 99) + (range 7 3 8 21) + ) + (expected "Color.Red and Color.Blue") + (found "Color.Red") +) diff --git a/tests/enum-match-payload-binding.diag b/tests/enum-match-payload-binding.diag new file mode 100644 index 0000000..82ed8e6 --- /dev/null +++ b/tests/enum-match-payload-binding.diag @@ -0,0 +1,13 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code InvalidEnumMatchArm) + (message "`Color.Red` match arm does not have a payload to bind") + (file "") + (span + (bytes 84 103) + (range 8 6 8 25) + ) + (expected "(Color.Red)") +) diff --git a/tests/enum-ordering.diag b/tests/enum-ordering.diag new file mode 100644 index 0000000..3e3d33d --- /dev/null +++ b/tests/enum-ordering.diag @@ -0,0 +1,13 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedEnumOrdering) + (message "enum ordering comparisons are not supported") + (file "") + (span + (bytes 61 89) + (range 7 3 7 31) + ) + (hint "only enum equality with `=` is supported") +) diff --git a/tests/enum-payload-constructor-arity.diag b/tests/enum-payload-constructor-arity.diag new file mode 100644 index 0000000..65185b2 --- /dev/null +++ b/tests/enum-payload-constructor-arity.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code VariantConstructorArity) + (message "enum constructor `Reading.Value` requires one string payload argument") + (file "") + (span + (bytes 74 89) + (range 8 3 8 18) + ) + (expected "1") + (found "0") +) diff --git a/tests/enum-payload-constructor-type.diag b/tests/enum-payload-constructor-type.diag new file mode 100644 index 0000000..7019d6e --- /dev/null +++ b/tests/enum-payload-constructor-type.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code TypeMismatch) + (message "enum constructor `Reading.Value` payload has wrong type") + (file "") + (span + (bytes 89 90) + (range 8 18 8 19) + ) + (expected "string") + (found "i32") +) diff --git a/tests/enum-payload-direct-scalars.checked.lower b/tests/enum-payload-direct-scalars.checked.lower new file mode 100644 index 0000000..6c0f029 --- /dev/null +++ b/tests/enum-payload-direct-scalars.checked.lower @@ -0,0 +1,276 @@ +program main + enum Reading + variant Missing + variant Value i32 + variant Offset i32 + enum WideReading + variant Missing + variant Value i64 + enum RatioReading + variant Missing + variant Value f64 + enum FlagReading + variant Missing + variant Value bool + enum LabelReading + variant Missing + variant Value string + struct PayloadRecord + field reading: Reading + field wide: WideReading + field ratio: RatioReading + field flag: FlagReading + field label: LabelReading + fn value(payload: i32) -> Reading + enum-variant Reading.Value #1 payload : Reading + var payload : i32 + fn echo_reading(reading: Reading) -> Reading + var reading : Reading + fn local_reading(payload: i32) -> Reading + local let reading : unit + enum-variant Reading.Value #1 payload : Reading + var payload : i32 + var reading : Reading + fn call_reading(payload: i32) -> Reading + call echo_reading : Reading + call local_reading : Reading + var payload : i32 + fn reading_code(reading: Reading) -> i32 + match : i32 + subject + var reading : Reading + arm Reading.Missing + int 0 : i32 + arm Reading.Value payload + var payload : i32 + arm Reading.Offset payload + binary + : i32 + var payload : i32 + int 100 : i32 + fn wide_value(payload: i64) -> WideReading + enum-variant WideReading.Value #1 payload : WideReading + var payload : i64 + fn echo_wide(reading: WideReading) -> WideReading + var reading : WideReading + fn local_wide(payload: i64) -> WideReading + local let reading : unit + enum-variant WideReading.Value #1 payload : WideReading + var payload : i64 + var reading : WideReading + fn call_wide(payload: i64) -> WideReading + call echo_wide : WideReading + call local_wide : WideReading + var payload : i64 + fn wide_code(reading: WideReading) -> i64 + match : i64 + subject + var reading : WideReading + arm WideReading.Missing + i64 0 : i64 + arm WideReading.Value payload + var payload : i64 + fn ratio_value(payload: f64) -> RatioReading + enum-variant RatioReading.Value #1 payload : RatioReading + var payload : f64 + fn ratio_code(reading: RatioReading) -> f64 + match : f64 + subject + var reading : RatioReading + arm RatioReading.Missing + float 0 : f64 + arm RatioReading.Value payload + var payload : f64 + fn flag_value(payload: bool) -> FlagReading + enum-variant FlagReading.Value #1 payload : FlagReading + var payload : bool + fn flag_code(reading: FlagReading) -> bool + match : bool + subject + var reading : FlagReading + arm FlagReading.Missing + bool false : bool + arm FlagReading.Value payload + var payload : bool + fn label_value(payload: string) -> LabelReading + enum-variant LabelReading.Value #1 payload : LabelReading + var payload : string + fn echo_label(reading: LabelReading) -> LabelReading + var reading : LabelReading + fn local_label(payload: string) -> LabelReading + local let reading : unit + enum-variant LabelReading.Value #1 payload : LabelReading + var payload : string + var reading : LabelReading + fn call_label(payload: string) -> LabelReading + call echo_label : LabelReading + call local_label : LabelReading + var payload : string + fn label_text(reading: LabelReading) -> string + match : string + subject + var reading : LabelReading + arm LabelReading.Missing + string "" : string + arm LabelReading.Value payload + var payload : string + fn pack_record(reading: Reading, wide: WideReading, ratio: RatioReading, flag: FlagReading, label: LabelReading) -> PayloadRecord + construct PayloadRecord : PayloadRecord + field reading + var reading : Reading + field wide + var wide : WideReading + field ratio + var ratio : RatioReading + field flag + var flag : FlagReading + field label + var label : LabelReading + fn make_record() -> PayloadRecord + call pack_record : PayloadRecord + enum-variant Reading.Offset #2 payload : Reading + int 5 : i32 + enum-variant WideReading.Value #1 payload : WideReading + i64 4294967296 : i64 + enum-variant RatioReading.Value #1 payload : RatioReading + float 3.5 : f64 + enum-variant FlagReading.Value #1 payload : FlagReading + bool true : bool + enum-variant LabelReading.Value #1 payload : LabelReading + string "hello" : string + fn echo_record(record: PayloadRecord) -> PayloadRecord + var record : PayloadRecord + fn local_record() -> PayloadRecord + local let record : unit + call make_record : PayloadRecord + call echo_record : PayloadRecord + var record : PayloadRecord + fn record_reading_code(record: PayloadRecord) -> i32 + call reading_code : i32 + field-access reading : Reading + var record : PayloadRecord + fn record_wide_code(record: PayloadRecord) -> i64 + call wide_code : i64 + field-access wide : WideReading + var record : PayloadRecord + fn record_ratio_code(record: PayloadRecord) -> f64 + call ratio_code : f64 + field-access ratio : RatioReading + var record : PayloadRecord + fn record_flag_code(record: PayloadRecord) -> bool + call flag_code : bool + field-access flag : FlagReading + var record : PayloadRecord + fn record_label_text(record: PayloadRecord) -> string + call label_text : string + field-access label : LabelReading + var record : PayloadRecord + fn main() -> i32 + call record_reading_code : i32 + call local_record : PayloadRecord + test "i32 enum constructor equality" + binary = : bool + enum-variant Reading.Value #1 payload : Reading + int 7 : i32 + call value : Reading + int 7 : i32 + test "i32 enum equality compares payload" + if : bool + binary = : bool + enum-variant Reading.Value #1 payload : Reading + int 7 : i32 + enum-variant Reading.Value #1 payload : Reading + int 8 : i32 + bool false : bool + bool true : bool + test "i32 enum payloadless equality" + binary = : bool + enum-variant Reading.Missing #0 : Reading + call echo_reading : Reading + enum-variant Reading.Missing #0 : Reading + test "i32 enum local return call flow" + binary = : bool + call call_reading : Reading + int 9 : i32 + enum-variant Reading.Value #1 payload : Reading + int 9 : i32 + test "i64 enum constructor equality" + binary = : bool + enum-variant WideReading.Value #1 payload : WideReading + i64 4294967296 : i64 + call wide_value : WideReading + i64 4294967296 : i64 + test "i64 enum local return call flow" + binary = : bool + call call_wide : WideReading + i64 4294967297 : i64 + enum-variant WideReading.Value #1 payload : WideReading + i64 4294967297 : i64 + test "f64 enum constructor equality" + binary = : bool + enum-variant RatioReading.Value #1 payload : RatioReading + float 3.5 : f64 + call ratio_value : RatioReading + float 3.5 : f64 + test "f64 enum equality compares payload" + if : bool + binary = : bool + enum-variant RatioReading.Value #1 payload : RatioReading + float 3.5 : f64 + enum-variant RatioReading.Value #1 payload : RatioReading + float 4.5 : f64 + bool false : bool + bool true : bool + test "bool enum constructor equality" + binary = : bool + enum-variant FlagReading.Value #1 payload : FlagReading + bool true : bool + call flag_value : FlagReading + bool true : bool + test "bool enum match value" + call flag_code : bool + enum-variant FlagReading.Value #1 payload : FlagReading + bool true : bool + test "string enum constructor equality" + binary = : bool + enum-variant LabelReading.Value #1 payload : LabelReading + string "hello" : string + call label_value : LabelReading + string "hello" : string + test "string enum equality compares payload" + if : bool + binary = : bool + enum-variant LabelReading.Value #1 payload : LabelReading + string "left" : string + enum-variant LabelReading.Value #1 payload : LabelReading + string "right" : string + bool false : bool + bool true : bool + test "string enum local return call flow" + binary = : bool + call call_label : LabelReading + string "hello" : string + enum-variant LabelReading.Value #1 payload : LabelReading + string "hello" : string + test "struct field enum i32 flow" + binary = : bool + call record_reading_code : i32 + call local_record : PayloadRecord + int 105 : i32 + test "struct field enum i64 flow" + binary = : bool + call record_wide_code : i64 + call local_record : PayloadRecord + i64 4294967296 : i64 + test "struct field enum f64 flow" + binary = : bool + call record_ratio_code : f64 + call local_record : PayloadRecord + float 3.5 : f64 + test "struct field enum bool flow" + call record_flag_code : bool + call local_record : PayloadRecord + test "struct field enum string flow" + binary = : bool + call record_label_text : string + call local_record : PayloadRecord + string "hello" : string diff --git a/tests/enum-payload-direct-scalars.slo b/tests/enum-payload-direct-scalars.slo new file mode 100644 index 0000000..eb69cc7 --- /dev/null +++ b/tests/enum-payload-direct-scalars.slo @@ -0,0 +1,202 @@ +(module main) + +(enum Reading + Missing + (Value i32) + (Offset i32)) + +(enum WideReading + Missing + (Value i64)) + +(enum RatioReading + Missing + (Value f64)) + +(enum FlagReading + Missing + (Value bool)) + +(enum LabelReading + Missing + (Value string)) + +(struct PayloadRecord + (reading Reading) + (wide WideReading) + (ratio RatioReading) + (flag FlagReading) + (label LabelReading)) + +(fn value ((payload i32)) -> Reading + (Reading.Value payload)) + +(fn echo_reading ((reading Reading)) -> Reading + reading) + +(fn local_reading ((payload i32)) -> Reading + (let reading Reading (Reading.Value payload)) + reading) + +(fn call_reading ((payload i32)) -> Reading + (echo_reading (local_reading payload))) + +(fn reading_code ((reading Reading)) -> i32 + (match reading + ((Reading.Missing) + 0) + ((Reading.Value payload) + payload) + ((Reading.Offset payload) + (+ payload 100)))) + +(fn wide_value ((payload i64)) -> WideReading + (WideReading.Value payload)) + +(fn echo_wide ((reading WideReading)) -> WideReading + reading) + +(fn local_wide ((payload i64)) -> WideReading + (let reading WideReading (WideReading.Value payload)) + reading) + +(fn call_wide ((payload i64)) -> WideReading + (echo_wide (local_wide payload))) + +(fn wide_code ((reading WideReading)) -> i64 + (match reading + ((WideReading.Missing) + 0i64) + ((WideReading.Value payload) + payload))) + +(fn ratio_value ((payload f64)) -> RatioReading + (RatioReading.Value payload)) + +(fn ratio_code ((reading RatioReading)) -> f64 + (match reading + ((RatioReading.Missing) + 0.0) + ((RatioReading.Value payload) + payload))) + +(fn flag_value ((payload bool)) -> FlagReading + (FlagReading.Value payload)) + +(fn flag_code ((reading FlagReading)) -> bool + (match reading + ((FlagReading.Missing) + false) + ((FlagReading.Value payload) + payload))) + +(fn label_value ((payload string)) -> LabelReading + (LabelReading.Value payload)) + +(fn echo_label ((reading LabelReading)) -> LabelReading + reading) + +(fn local_label ((payload string)) -> LabelReading + (let reading LabelReading (LabelReading.Value payload)) + reading) + +(fn call_label ((payload string)) -> LabelReading + (echo_label (local_label payload))) + +(fn label_text ((reading LabelReading)) -> string + (match reading + ((LabelReading.Missing) + "") + ((LabelReading.Value payload) + payload))) + +(fn pack_record ((reading Reading) (wide WideReading) (ratio RatioReading) (flag FlagReading) (label LabelReading)) -> PayloadRecord + (PayloadRecord (reading reading) (wide wide) (ratio ratio) (flag flag) (label label))) + +(fn make_record () -> PayloadRecord + (pack_record (Reading.Offset 5) (WideReading.Value 4294967296i64) (RatioReading.Value 3.5) (FlagReading.Value true) (LabelReading.Value "hello"))) + +(fn echo_record ((record PayloadRecord)) -> PayloadRecord + record) + +(fn local_record () -> PayloadRecord + (let record PayloadRecord (make_record)) + (echo_record record)) + +(fn record_reading_code ((record PayloadRecord)) -> i32 + (reading_code (. record reading))) + +(fn record_wide_code ((record PayloadRecord)) -> i64 + (wide_code (. record wide))) + +(fn record_ratio_code ((record PayloadRecord)) -> f64 + (ratio_code (. record ratio))) + +(fn record_flag_code ((record PayloadRecord)) -> bool + (flag_code (. record flag))) + +(fn record_label_text ((record PayloadRecord)) -> string + (label_text (. record label))) + +(test "i32 enum constructor equality" + (= (Reading.Value 7) (value 7))) + +(test "i32 enum equality compares payload" + (if (= (Reading.Value 7) (Reading.Value 8)) + false + true)) + +(test "i32 enum payloadless equality" + (= (Reading.Missing) (echo_reading (Reading.Missing)))) + +(test "i32 enum local return call flow" + (= (call_reading 9) (Reading.Value 9))) + +(test "i64 enum constructor equality" + (= (WideReading.Value 4294967296i64) (wide_value 4294967296i64))) + +(test "i64 enum local return call flow" + (= (call_wide 4294967297i64) (WideReading.Value 4294967297i64))) + +(test "f64 enum constructor equality" + (= (RatioReading.Value 3.5) (ratio_value 3.5))) + +(test "f64 enum equality compares payload" + (if (= (RatioReading.Value 3.5) (RatioReading.Value 4.5)) + false + true)) + +(test "bool enum constructor equality" + (= (FlagReading.Value true) (flag_value true))) + +(test "bool enum match value" + (flag_code (FlagReading.Value true))) + +(test "string enum constructor equality" + (= (LabelReading.Value "hello") (label_value "hello"))) + +(test "string enum equality compares payload" + (if (= (LabelReading.Value "left") (LabelReading.Value "right")) + false + true)) + +(test "string enum local return call flow" + (= (call_label "hello") (LabelReading.Value "hello"))) + +(test "struct field enum i32 flow" + (= (record_reading_code (local_record)) 105)) + +(test "struct field enum i64 flow" + (= (record_wide_code (local_record)) 4294967296i64)) + +(test "struct field enum f64 flow" + (= (record_ratio_code (local_record)) 3.5)) + +(test "struct field enum bool flow" + (record_flag_code (local_record))) + +(test "struct field enum string flow" + (= (record_label_text (local_record)) "hello")) + +(fn main () -> i32 + (record_reading_code (local_record))) diff --git a/tests/enum-payload-direct-scalars.surface.lower b/tests/enum-payload-direct-scalars.surface.lower new file mode 100644 index 0000000..0bac5ef --- /dev/null +++ b/tests/enum-payload-direct-scalars.surface.lower @@ -0,0 +1,276 @@ +program main + enum Reading + variant Missing + variant Value i32 + variant Offset i32 + enum WideReading + variant Missing + variant Value i64 + enum RatioReading + variant Missing + variant Value f64 + enum FlagReading + variant Missing + variant Value bool + enum LabelReading + variant Missing + variant Value string + struct PayloadRecord + field reading: Reading + field wide: WideReading + field ratio: RatioReading + field flag: FlagReading + field label: LabelReading + fn value(payload: i32) -> Reading + enum-variant Reading.Value + var payload + fn echo_reading(reading: Reading) -> Reading + var reading + fn local_reading(payload: i32) -> Reading + local let reading: Reading + enum-variant Reading.Value + var payload + var reading + fn call_reading(payload: i32) -> Reading + call echo_reading + call local_reading + var payload + fn reading_code(reading: Reading) -> i32 + match + subject + var reading + arm Reading.Missing + int 0 + arm Reading.Value payload + var payload + arm Reading.Offset payload + binary + + var payload + int 100 + fn wide_value(payload: i64) -> WideReading + enum-variant WideReading.Value + var payload + fn echo_wide(reading: WideReading) -> WideReading + var reading + fn local_wide(payload: i64) -> WideReading + local let reading: WideReading + enum-variant WideReading.Value + var payload + var reading + fn call_wide(payload: i64) -> WideReading + call echo_wide + call local_wide + var payload + fn wide_code(reading: WideReading) -> i64 + match + subject + var reading + arm WideReading.Missing + i64 0 + arm WideReading.Value payload + var payload + fn ratio_value(payload: f64) -> RatioReading + enum-variant RatioReading.Value + var payload + fn ratio_code(reading: RatioReading) -> f64 + match + subject + var reading + arm RatioReading.Missing + float 0 + arm RatioReading.Value payload + var payload + fn flag_value(payload: bool) -> FlagReading + enum-variant FlagReading.Value + var payload + fn flag_code(reading: FlagReading) -> bool + match + subject + var reading + arm FlagReading.Missing + bool false + arm FlagReading.Value payload + var payload + fn label_value(payload: string) -> LabelReading + enum-variant LabelReading.Value + var payload + fn echo_label(reading: LabelReading) -> LabelReading + var reading + fn local_label(payload: string) -> LabelReading + local let reading: LabelReading + enum-variant LabelReading.Value + var payload + var reading + fn call_label(payload: string) -> LabelReading + call echo_label + call local_label + var payload + fn label_text(reading: LabelReading) -> string + match + subject + var reading + arm LabelReading.Missing + string "" + arm LabelReading.Value payload + var payload + fn pack_record(reading: Reading, wide: WideReading, ratio: RatioReading, flag: FlagReading, label: LabelReading) -> PayloadRecord + construct PayloadRecord + field reading + var reading + field wide + var wide + field ratio + var ratio + field flag + var flag + field label + var label + fn make_record() -> PayloadRecord + call pack_record + enum-variant Reading.Offset + int 5 + enum-variant WideReading.Value + i64 4294967296 + enum-variant RatioReading.Value + float 3.5 + enum-variant FlagReading.Value + bool true + enum-variant LabelReading.Value + string "hello" + fn echo_record(record: PayloadRecord) -> PayloadRecord + var record + fn local_record() -> PayloadRecord + local let record: PayloadRecord + call make_record + call echo_record + var record + fn record_reading_code(record: PayloadRecord) -> i32 + call reading_code + field-access reading + var record + fn record_wide_code(record: PayloadRecord) -> i64 + call wide_code + field-access wide + var record + fn record_ratio_code(record: PayloadRecord) -> f64 + call ratio_code + field-access ratio + var record + fn record_flag_code(record: PayloadRecord) -> bool + call flag_code + field-access flag + var record + fn record_label_text(record: PayloadRecord) -> string + call label_text + field-access label + var record + fn main() -> i32 + call record_reading_code + call local_record + test "i32 enum constructor equality" + binary = + enum-variant Reading.Value + int 7 + call value + int 7 + test "i32 enum equality compares payload" + if + binary = + enum-variant Reading.Value + int 7 + enum-variant Reading.Value + int 8 + bool false + bool true + test "i32 enum payloadless equality" + binary = + enum-variant Reading.Missing + call echo_reading + enum-variant Reading.Missing + test "i32 enum local return call flow" + binary = + call call_reading + int 9 + enum-variant Reading.Value + int 9 + test "i64 enum constructor equality" + binary = + enum-variant WideReading.Value + i64 4294967296 + call wide_value + i64 4294967296 + test "i64 enum local return call flow" + binary = + call call_wide + i64 4294967297 + enum-variant WideReading.Value + i64 4294967297 + test "f64 enum constructor equality" + binary = + enum-variant RatioReading.Value + float 3.5 + call ratio_value + float 3.5 + test "f64 enum equality compares payload" + if + binary = + enum-variant RatioReading.Value + float 3.5 + enum-variant RatioReading.Value + float 4.5 + bool false + bool true + test "bool enum constructor equality" + binary = + enum-variant FlagReading.Value + bool true + call flag_value + bool true + test "bool enum match value" + call flag_code + enum-variant FlagReading.Value + bool true + test "string enum constructor equality" + binary = + enum-variant LabelReading.Value + string "hello" + call label_value + string "hello" + test "string enum equality compares payload" + if + binary = + enum-variant LabelReading.Value + string "left" + enum-variant LabelReading.Value + string "right" + bool false + bool true + test "string enum local return call flow" + binary = + call call_label + string "hello" + enum-variant LabelReading.Value + string "hello" + test "struct field enum i32 flow" + binary = + call record_reading_code + call local_record + int 105 + test "struct field enum i64 flow" + binary = + call record_wide_code + call local_record + i64 4294967296 + test "struct field enum f64 flow" + binary = + call record_ratio_code + call local_record + float 3.5 + test "struct field enum bool flow" + call record_flag_code + call local_record + test "struct field enum string flow" + binary = + call record_label_text + call local_record + string "hello" diff --git a/tests/enum-payload-i32.checked.lower b/tests/enum-payload-i32.checked.lower new file mode 100644 index 0000000..63ae486 --- /dev/null +++ b/tests/enum-payload-i32.checked.lower @@ -0,0 +1,89 @@ +program main + enum Reading + variant Missing + variant Value i32 + variant Offset i32 + fn missing() -> Reading + enum-variant Reading.Missing #0 : Reading + fn value(payload: i32) -> Reading + enum-variant Reading.Value #1 payload : Reading + var payload : i32 + fn echo(reading: Reading) -> Reading + var reading : Reading + fn local_reading(payload: i32) -> Reading + local let reading : unit + enum-variant Reading.Value #1 payload : Reading + var payload : i32 + var reading : Reading + fn same_reading(left: Reading, right: Reading) -> bool + binary = : bool + var left : Reading + var right : Reading + fn reading_code(reading: Reading) -> i32 + match : i32 + subject + var reading : Reading + arm Reading.Missing + int 0 : i32 + arm Reading.Value payload + var payload : i32 + arm Reading.Offset payload + binary + : i32 + var payload : i32 + int 100 : i32 + fn call_flow(payload: i32) -> Reading + call echo : Reading + call local_reading : Reading + var payload : i32 + fn main() -> i32 + call reading_code : i32 + call call_flow : Reading + int 42 : i32 + test "enum payload constructor equality" + binary = : bool + enum-variant Reading.Value #1 payload : Reading + int 7 : i32 + call value : Reading + int 7 : i32 + test "enum payload equality compares payload" + if : bool + binary = : bool + enum-variant Reading.Value #1 payload : Reading + int 7 : i32 + enum-variant Reading.Value #1 payload : Reading + int 8 : i32 + bool false : bool + bool true : bool + test "enum payloadless equality" + binary = : bool + enum-variant Reading.Missing #0 : Reading + call missing : Reading + test "enum payload local return call flow" + binary = : bool + call call_flow : Reading + int 9 : i32 + enum-variant Reading.Value #1 payload : Reading + int 9 : i32 + test "enum payload parameter equality" + call same_reading : bool + call value : Reading + int 11 : i32 + enum-variant Reading.Value #1 payload : Reading + int 11 : i32 + test "enum payload match missing" + binary = : bool + call reading_code : i32 + enum-variant Reading.Missing #0 : Reading + int 0 : i32 + test "enum payload match value" + binary = : bool + call reading_code : i32 + enum-variant Reading.Value #1 payload : Reading + int 12 : i32 + int 12 : i32 + test "enum payload match offset" + binary = : bool + call reading_code : i32 + enum-variant Reading.Offset #2 payload : Reading + int 5 : i32 + int 105 : i32 diff --git a/tests/enum-payload-i32.slo b/tests/enum-payload-i32.slo new file mode 100644 index 0000000..39e9007 --- /dev/null +++ b/tests/enum-payload-i32.slo @@ -0,0 +1,63 @@ +(module main) + +(enum Reading + Missing + (Value i32) + (Offset i32)) + +(fn missing () -> Reading + (Reading.Missing)) + +(fn value ((payload i32)) -> Reading + (Reading.Value payload)) + +(fn echo ((reading Reading)) -> Reading + reading) + +(fn local_reading ((payload i32)) -> Reading + (let reading Reading (Reading.Value payload)) + reading) + +(fn same_reading ((left Reading) (right Reading)) -> bool + (= left right)) + +(fn reading_code ((reading Reading)) -> i32 + (match reading + ((Reading.Missing) + 0) + ((Reading.Value payload) + payload) + ((Reading.Offset payload) + (+ payload 100)))) + +(fn call_flow ((payload i32)) -> Reading + (echo (local_reading payload))) + +(test "enum payload constructor equality" + (= (Reading.Value 7) (value 7))) + +(test "enum payload equality compares payload" + (if (= (Reading.Value 7) (Reading.Value 8)) + false + true)) + +(test "enum payloadless equality" + (= (Reading.Missing) (missing))) + +(test "enum payload local return call flow" + (= (call_flow 9) (Reading.Value 9))) + +(test "enum payload parameter equality" + (same_reading (value 11) (Reading.Value 11))) + +(test "enum payload match missing" + (= (reading_code (Reading.Missing)) 0)) + +(test "enum payload match value" + (= (reading_code (Reading.Value 12)) 12)) + +(test "enum payload match offset" + (= (reading_code (Reading.Offset 5)) 105)) + +(fn main () -> i32 + (reading_code (call_flow 42))) diff --git a/tests/enum-payload-i32.surface.lower b/tests/enum-payload-i32.surface.lower new file mode 100644 index 0000000..ec2cb04 --- /dev/null +++ b/tests/enum-payload-i32.surface.lower @@ -0,0 +1,89 @@ +program main + enum Reading + variant Missing + variant Value i32 + variant Offset i32 + fn missing() -> Reading + enum-variant Reading.Missing + fn value(payload: i32) -> Reading + enum-variant Reading.Value + var payload + fn echo(reading: Reading) -> Reading + var reading + fn local_reading(payload: i32) -> Reading + local let reading: Reading + enum-variant Reading.Value + var payload + var reading + fn same_reading(left: Reading, right: Reading) -> bool + binary = + var left + var right + fn reading_code(reading: Reading) -> i32 + match + subject + var reading + arm Reading.Missing + int 0 + arm Reading.Value payload + var payload + arm Reading.Offset payload + binary + + var payload + int 100 + fn call_flow(payload: i32) -> Reading + call echo + call local_reading + var payload + fn main() -> i32 + call reading_code + call call_flow + int 42 + test "enum payload constructor equality" + binary = + enum-variant Reading.Value + int 7 + call value + int 7 + test "enum payload equality compares payload" + if + binary = + enum-variant Reading.Value + int 7 + enum-variant Reading.Value + int 8 + bool false + bool true + test "enum payloadless equality" + binary = + enum-variant Reading.Missing + call missing + test "enum payload local return call flow" + binary = + call call_flow + int 9 + enum-variant Reading.Value + int 9 + test "enum payload parameter equality" + call same_reading + call value + int 11 + enum-variant Reading.Value + int 11 + test "enum payload match missing" + binary = + call reading_code + enum-variant Reading.Missing + int 0 + test "enum payload match value" + binary = + call reading_code + enum-variant Reading.Value + int 12 + int 12 + test "enum payload match offset" + binary = + call reading_code + enum-variant Reading.Offset + int 5 + int 105 diff --git a/tests/enum-payload-match-missing-binding.diag b/tests/enum-payload-match-missing-binding.diag new file mode 100644 index 0000000..d085548 --- /dev/null +++ b/tests/enum-payload-match-missing-binding.diag @@ -0,0 +1,13 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code InvalidEnumMatchArm) + (message "`Reading.Value` match arm must bind its payload") + (file "") + (span + (bytes 133 148) + (range 11 6 11 21) + ) + (expected "(Reading.Value binding)") +) diff --git a/tests/enum-payload-mixed-types.diag b/tests/enum-payload-mixed-types.diag new file mode 100644 index 0000000..83d190f --- /dev/null +++ b/tests/enum-payload-mixed-types.diag @@ -0,0 +1,23 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code MixedEnumPayloadTypesUnsupported) + (message "enum payload variants in one enum must use the same payload type") + (file "") + (span + (bytes 55 61) + (range 6 12 6 18) + ) + (expected "i32") + (found "string") + (hint "split mixed payload kinds into separate enums") + (related + (span + (file "") + (bytes 39 42) + (range 5 10 5 13) + (message "first payload type in this enum") + ) + ) +) diff --git a/tests/enum-payload-recursive-struct.diag b/tests/enum-payload-recursive-struct.diag new file mode 100644 index 0000000..7801a4b --- /dev/null +++ b/tests/enum-payload-recursive-struct.diag @@ -0,0 +1,21 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code RecursiveEnumPayloadStructUnsupported) + (message "recursive enum payload/struct cycles are not supported (`Wrapper -> Node -> Wrapper`)") + (file "") + (span + (bytes 37 44) + (range 5 9 5 16) + ) + (hint "break the cycle by removing the struct payload edge or a direct enum/struct field edge") + (related + (span + (file "") + (bytes 54 61) + (range 7 7 7 14) + (message "recursive enum declaration") + ) + ) +) diff --git a/tests/enum-payload-struct-equality.diag b/tests/enum-payload-struct-equality.diag new file mode 100644 index 0000000..5b2f412 --- /dev/null +++ b/tests/enum-payload-struct-equality.diag @@ -0,0 +1,13 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedEnumEquality) + (message "enum equality is supported only for payloadless enums and enums whose payload type is direct `i32`, `i64`, `u32`, `u64`, `f64`, `bool`, or `string`") + (file "") + (span + (bytes 121 213) + (range 13 3 14 48) + ) + (hint "match the enum and compare fields after extracting the struct payload") +) diff --git a/tests/enum-payload-structs.checked.lower b/tests/enum-payload-structs.checked.lower new file mode 100644 index 0000000..bdf9889 --- /dev/null +++ b/tests/enum-payload-structs.checked.lower @@ -0,0 +1,148 @@ +program main + enum PacketState + variant Missing + variant Live Packet + variant Cached Packet + struct Packet + field values: (array i32 3) + field labels: (array string 2) + field flags: (array bool 2) + fn make_packet(base: i32, head: string) -> Packet + construct Packet : Packet + field values + array : (array i32 3) + var base : i32 + binary + : i32 + var base : i32 + int 1 : i32 + binary + : i32 + var base : i32 + int 2 : i32 + field labels + array : (array string 2) + var head : string + string "tail" : string + field flags + array : (array bool 2) + bool false : bool + bool true : bool + fn live(packet: Packet) -> PacketState + enum-variant PacketState.Live #1 payload : PacketState + var packet : Packet + fn cached(packet: Packet) -> PacketState + enum-variant PacketState.Cached #2 payload : PacketState + var packet : Packet + fn echo_state(state: PacketState) -> PacketState + var state : PacketState + fn local_state(base: i32, head: string) -> PacketState + local let state : unit + enum-variant PacketState.Live #1 payload : PacketState + call make_packet : Packet + var base : i32 + var head : string + var state : PacketState + fn call_state(base: i32, head: string) -> PacketState + call echo_state : PacketState + call cached : PacketState + call make_packet : Packet + var base : i32 + var head : string + fn state_value(state: PacketState, i: i32) -> i32 + match : i32 + subject + var state : PacketState + arm PacketState.Missing + int 0 : i32 + arm PacketState.Live payload + index : i32 + field-access values : (array i32 3) + var payload : Packet + var i : i32 + arm PacketState.Cached payload + index : i32 + field-access values : (array i32 3) + var payload : Packet + var i : i32 + fn state_label(state: PacketState, i: i32) -> string + match : string + subject + var state : PacketState + arm PacketState.Missing + string "" : string + arm PacketState.Live payload + index : string + field-access labels : (array string 2) + var payload : Packet + var i : i32 + arm PacketState.Cached payload + index : string + field-access labels : (array string 2) + var payload : Packet + var i : i32 + fn state_flag(state: PacketState, i: i32) -> bool + match : bool + subject + var state : PacketState + arm PacketState.Missing + bool false : bool + arm PacketState.Live payload + index : bool + field-access flags : (array bool 2) + var payload : Packet + var i : i32 + arm PacketState.Cached payload + index : bool + field-access flags : (array bool 2) + var payload : Packet + var i : i32 + fn main() -> i32 + call state_value : i32 + call call_state : PacketState + int 7 : i32 + string "sun" : string + int 2 : i32 + test "struct payload missing arm" + binary = : bool + call state_value : i32 + enum-variant PacketState.Missing #0 : PacketState + int 0 : i32 + int 0 : i32 + test "struct payload live constructor flow" + binary = : bool + call state_value : i32 + call live : PacketState + call make_packet : Packet + int 7 : i32 + string "sun" : string + int 2 : i32 + int 9 : i32 + test "struct payload cached param return call flow" + binary = : bool + call state_value : i32 + call call_state : PacketState + int 7 : i32 + string "sun" : string + int 1 : i32 + int 8 : i32 + test "struct payload string array field access" + binary = : bool + call state_label : string + call call_state : PacketState + int 7 : i32 + string "sun" : string + int 0 : i32 + string "sun" : string + test "struct payload bool array field access" + call state_flag : bool + call call_state : PacketState + int 7 : i32 + string "sun" : string + int 1 : i32 + test "struct payload local return flow" + binary = : bool + call state_label : string + call local_state : PacketState + int 9 : i32 + string "orb" : string + int 1 : i32 + string "tail" : string diff --git a/tests/enum-payload-structs.slo b/tests/enum-payload-structs.slo new file mode 100644 index 0000000..3589746 --- /dev/null +++ b/tests/enum-payload-structs.slo @@ -0,0 +1,78 @@ +(module main) + +(struct Packet + (values (array i32 3)) + (labels (array string 2)) + (flags (array bool 2))) + +(enum PacketState + Missing + (Live Packet) + (Cached Packet)) + +(fn make_packet ((base i32) (head string)) -> Packet + (Packet (values (array i32 base (+ base 1) (+ base 2))) (labels (array string head "tail")) (flags (array bool false true)))) + +(fn live ((packet Packet)) -> PacketState + (PacketState.Live packet)) + +(fn cached ((packet Packet)) -> PacketState + (PacketState.Cached packet)) + +(fn echo_state ((state PacketState)) -> PacketState + state) + +(fn local_state ((base i32) (head string)) -> PacketState + (let state PacketState (PacketState.Live (make_packet base head))) + state) + +(fn call_state ((base i32) (head string)) -> PacketState + (echo_state (cached (make_packet base head)))) + +(fn state_value ((state PacketState) (i i32)) -> i32 + (match state + ((PacketState.Missing) + 0) + ((PacketState.Live payload) + (index (. payload values) i)) + ((PacketState.Cached payload) + (index (. payload values) i)))) + +(fn state_label ((state PacketState) (i i32)) -> string + (match state + ((PacketState.Missing) + "") + ((PacketState.Live payload) + (index (. payload labels) i)) + ((PacketState.Cached payload) + (index (. payload labels) i)))) + +(fn state_flag ((state PacketState) (i i32)) -> bool + (match state + ((PacketState.Missing) + false) + ((PacketState.Live payload) + (index (. payload flags) i)) + ((PacketState.Cached payload) + (index (. payload flags) i)))) + +(test "struct payload missing arm" + (= (state_value (PacketState.Missing) 0) 0)) + +(test "struct payload live constructor flow" + (= (state_value (live (make_packet 7 "sun")) 2) 9)) + +(test "struct payload cached param return call flow" + (= (state_value (call_state 7 "sun") 1) 8)) + +(test "struct payload string array field access" + (= (state_label (call_state 7 "sun") 0) "sun")) + +(test "struct payload bool array field access" + (state_flag (call_state 7 "sun") 1)) + +(test "struct payload local return flow" + (= (state_label (local_state 9 "orb") 1) "tail")) + +(fn main () -> i32 + (state_value (call_state 7 "sun") 2)) diff --git a/tests/enum-payload-structs.surface.lower b/tests/enum-payload-structs.surface.lower new file mode 100644 index 0000000..fdf7f46 --- /dev/null +++ b/tests/enum-payload-structs.surface.lower @@ -0,0 +1,148 @@ +program main + enum PacketState + variant Missing + variant Live Packet + variant Cached Packet + struct Packet + field values: (array i32 3) + field labels: (array string 2) + field flags: (array bool 2) + fn make_packet(base: i32, head: string) -> Packet + construct Packet + field values + array i32 + var base + binary + + var base + int 1 + binary + + var base + int 2 + field labels + array string + var head + string "tail" + field flags + array bool + bool false + bool true + fn live(packet: Packet) -> PacketState + enum-variant PacketState.Live + var packet + fn cached(packet: Packet) -> PacketState + enum-variant PacketState.Cached + var packet + fn echo_state(state: PacketState) -> PacketState + var state + fn local_state(base: i32, head: string) -> PacketState + local let state: PacketState + enum-variant PacketState.Live + call make_packet + var base + var head + var state + fn call_state(base: i32, head: string) -> PacketState + call echo_state + call cached + call make_packet + var base + var head + fn state_value(state: PacketState, i: i32) -> i32 + match + subject + var state + arm PacketState.Missing + int 0 + arm PacketState.Live payload + index + field-access values + var payload + var i + arm PacketState.Cached payload + index + field-access values + var payload + var i + fn state_label(state: PacketState, i: i32) -> string + match + subject + var state + arm PacketState.Missing + string "" + arm PacketState.Live payload + index + field-access labels + var payload + var i + arm PacketState.Cached payload + index + field-access labels + var payload + var i + fn state_flag(state: PacketState, i: i32) -> bool + match + subject + var state + arm PacketState.Missing + bool false + arm PacketState.Live payload + index + field-access flags + var payload + var i + arm PacketState.Cached payload + index + field-access flags + var payload + var i + fn main() -> i32 + call state_value + call call_state + int 7 + string "sun" + int 2 + test "struct payload missing arm" + binary = + call state_value + enum-variant PacketState.Missing + int 0 + int 0 + test "struct payload live constructor flow" + binary = + call state_value + call live + call make_packet + int 7 + string "sun" + int 2 + int 9 + test "struct payload cached param return call flow" + binary = + call state_value + call call_state + int 7 + string "sun" + int 1 + int 8 + test "struct payload string array field access" + binary = + call state_label + call call_state + int 7 + string "sun" + int 0 + string "sun" + test "struct payload bool array field access" + call state_flag + call call_state + int 7 + string "sun" + int 1 + test "struct payload local return flow" + binary = + call state_label + call local_state + int 9 + string "orb" + int 1 + string "tail" diff --git a/tests/enum-payload-unsupported-type.diag b/tests/enum-payload-unsupported-type.diag new file mode 100644 index 0000000..abe9b96 --- /dev/null +++ b/tests/enum-payload-unsupported-type.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedEnumPayloadType) + (message "enum payload variants support only unary direct `i32`, `i64`, `u32`, `u64`, `f64`, `bool`, `string`, or known non-recursive struct payloads") + (file "") + (span + (bytes 39 48) + (range 5 10 5 19) + ) + (expected "i32, i64, u32, u64, f64, bool, string, or known non-recursive struct type") + (found "(vec i32)") + (hint "use a direct scalar/string payload, a known non-recursive struct payload, or keep the variant payloadless") +) diff --git a/tests/enum-printing.diag b/tests/enum-printing.diag new file mode 100644 index 0000000..c4e8134 --- /dev/null +++ b/tests/enum-printing.diag @@ -0,0 +1,13 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedEnumPrint) + (message "enum printing is not supported") + (file "") + (span + (bytes 66 77) + (range 7 14 7 25) + ) + (hint "compare enum values with `=` or match on variants") +) diff --git a/tests/enum-struct-fields-container.diag b/tests/enum-struct-fields-container.diag new file mode 100644 index 0000000..5654036 --- /dev/null +++ b/tests/enum-struct-fields-container.diag @@ -0,0 +1,13 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedEnumContainer) + (message "enum values in struct field containers are not supported") + (file "") + (span + (bytes 82 96) + (range 8 10 8 24) + ) + (hint "store enum values directly as struct fields") +) diff --git a/tests/enum-struct-fields.checked.lower b/tests/enum-struct-fields.checked.lower new file mode 100644 index 0000000..6a19bf2 --- /dev/null +++ b/tests/enum-struct-fields.checked.lower @@ -0,0 +1,93 @@ +program main + enum Status + variant Ready + variant Blocked + enum Reading + variant Missing + variant Value i32 + struct TaggedReading + field status: Status + field reading: Reading + fn make_tagged(status: Status, reading: Reading) -> TaggedReading + construct TaggedReading : TaggedReading + field status + var status : Status + field reading + var reading : Reading + fn ready_value(payload: i32) -> TaggedReading + call make_tagged : TaggedReading + enum-variant Status.Ready #0 : Status + enum-variant Reading.Value #1 payload : Reading + var payload : i32 + fn missing_blocked() -> TaggedReading + call make_tagged : TaggedReading + enum-variant Status.Blocked #1 : Status + enum-variant Reading.Missing #0 : Reading + fn echo_tagged(tagged: TaggedReading) -> TaggedReading + var tagged : TaggedReading + fn local_tagged(payload: i32) -> TaggedReading + local let tagged : unit + call ready_value : TaggedReading + var payload : i32 + call echo_tagged : TaggedReading + var tagged : TaggedReading + fn status_of(tagged: TaggedReading) -> Status + field-access status : Status + var tagged : TaggedReading + fn reading_of(tagged: TaggedReading) -> Reading + field-access reading : Reading + var tagged : TaggedReading + fn is_ready(tagged: TaggedReading) -> bool + binary = : bool + field-access status : Status + var tagged : TaggedReading + enum-variant Status.Ready #0 : Status + fn reading_matches(tagged: TaggedReading, reading: Reading) -> bool + binary = : bool + field-access reading : Reading + var tagged : TaggedReading + var reading : Reading + fn reading_code(tagged: TaggedReading) -> i32 + match : i32 + subject + field-access reading : Reading + var tagged : TaggedReading + arm Reading.Missing + int 0 : i32 + arm Reading.Value payload + var payload : i32 + fn main() -> i32 + call reading_code : i32 + call local_tagged : TaggedReading + int 42 : i32 + test "enum struct field payloadless equality" + binary = : bool + call status_of : Status + call missing_blocked : TaggedReading + enum-variant Status.Blocked #1 : Status + test "enum struct field unary payload equality" + call reading_matches : bool + call ready_value : TaggedReading + int 7 : i32 + enum-variant Reading.Value #1 payload : Reading + int 7 : i32 + test "enum struct field access return" + binary = : bool + call reading_of : Reading + call missing_blocked : TaggedReading + enum-variant Reading.Missing #0 : Reading + test "enum struct field local param return call flow" + binary = : bool + call reading_code : i32 + call local_tagged : TaggedReading + int 9 : i32 + int 9 : i32 + test "enum struct field match missing" + binary = : bool + call reading_code : i32 + call missing_blocked : TaggedReading + int 0 : i32 + test "enum struct field status predicate" + call is_ready : bool + call ready_value : TaggedReading + int 11 : i32 diff --git a/tests/enum-struct-fields.slo b/tests/enum-struct-fields.slo new file mode 100644 index 0000000..46ff92e --- /dev/null +++ b/tests/enum-struct-fields.slo @@ -0,0 +1,67 @@ +(module main) + +(enum Status Ready Blocked) + +(enum Reading + Missing + (Value i32)) + +(struct TaggedReading + (status Status) + (reading Reading)) + +(fn make_tagged ((status Status) (reading Reading)) -> TaggedReading + (TaggedReading (status status) (reading reading))) + +(fn ready_value ((payload i32)) -> TaggedReading + (make_tagged (Status.Ready) (Reading.Value payload))) + +(fn missing_blocked () -> TaggedReading + (make_tagged (Status.Blocked) (Reading.Missing))) + +(fn echo_tagged ((tagged TaggedReading)) -> TaggedReading + tagged) + +(fn local_tagged ((payload i32)) -> TaggedReading + (let tagged TaggedReading (ready_value payload)) + (echo_tagged tagged)) + +(fn status_of ((tagged TaggedReading)) -> Status + (. tagged status)) + +(fn reading_of ((tagged TaggedReading)) -> Reading + (. tagged reading)) + +(fn is_ready ((tagged TaggedReading)) -> bool + (= (. tagged status) (Status.Ready))) + +(fn reading_matches ((tagged TaggedReading) (reading Reading)) -> bool + (= (. tagged reading) reading)) + +(fn reading_code ((tagged TaggedReading)) -> i32 + (match (. tagged reading) + ((Reading.Missing) + 0) + ((Reading.Value payload) + payload))) + +(test "enum struct field payloadless equality" + (= (status_of (missing_blocked)) (Status.Blocked))) + +(test "enum struct field unary payload equality" + (reading_matches (ready_value 7) (Reading.Value 7))) + +(test "enum struct field access return" + (= (reading_of (missing_blocked)) (Reading.Missing))) + +(test "enum struct field local param return call flow" + (= (reading_code (local_tagged 9)) 9)) + +(test "enum struct field match missing" + (= (reading_code (missing_blocked)) 0)) + +(test "enum struct field status predicate" + (is_ready (ready_value 11))) + +(fn main () -> i32 + (reading_code (local_tagged 42))) diff --git a/tests/enum-struct-fields.surface.lower b/tests/enum-struct-fields.surface.lower new file mode 100644 index 0000000..9e2718a --- /dev/null +++ b/tests/enum-struct-fields.surface.lower @@ -0,0 +1,93 @@ +program main + enum Status + variant Ready + variant Blocked + enum Reading + variant Missing + variant Value i32 + struct TaggedReading + field status: Status + field reading: Reading + fn make_tagged(status: Status, reading: Reading) -> TaggedReading + construct TaggedReading + field status + var status + field reading + var reading + fn ready_value(payload: i32) -> TaggedReading + call make_tagged + enum-variant Status.Ready + enum-variant Reading.Value + var payload + fn missing_blocked() -> TaggedReading + call make_tagged + enum-variant Status.Blocked + enum-variant Reading.Missing + fn echo_tagged(tagged: TaggedReading) -> TaggedReading + var tagged + fn local_tagged(payload: i32) -> TaggedReading + local let tagged: TaggedReading + call ready_value + var payload + call echo_tagged + var tagged + fn status_of(tagged: TaggedReading) -> Status + field-access status + var tagged + fn reading_of(tagged: TaggedReading) -> Reading + field-access reading + var tagged + fn is_ready(tagged: TaggedReading) -> bool + binary = + field-access status + var tagged + enum-variant Status.Ready + fn reading_matches(tagged: TaggedReading, reading: Reading) -> bool + binary = + field-access reading + var tagged + var reading + fn reading_code(tagged: TaggedReading) -> i32 + match + subject + field-access reading + var tagged + arm Reading.Missing + int 0 + arm Reading.Value payload + var payload + fn main() -> i32 + call reading_code + call local_tagged + int 42 + test "enum struct field payloadless equality" + binary = + call status_of + call missing_blocked + enum-variant Status.Blocked + test "enum struct field unary payload equality" + call reading_matches + call ready_value + int 7 + enum-variant Reading.Value + int 7 + test "enum struct field access return" + binary = + call reading_of + call missing_blocked + enum-variant Reading.Missing + test "enum struct field local param return call flow" + binary = + call reading_code + call local_tagged + int 9 + int 9 + test "enum struct field match missing" + binary = + call reading_code + call missing_blocked + int 0 + test "enum struct field status predicate" + call is_ready + call ready_value + int 11 diff --git a/tests/enum-subject-mismatch.diag b/tests/enum-subject-mismatch.diag new file mode 100644 index 0000000..68936ab --- /dev/null +++ b/tests/enum-subject-mismatch.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code EnumSubjectMismatch) + (message "enum equality requires both operands to have the same enum type") + (file "") + (span + (bytes 76 106) + (range 8 3 8 33) + ) + (expected "Color") + (found "Shape") +) diff --git a/tests/enum-unknown-constructor.diag b/tests/enum-unknown-constructor.diag new file mode 100644 index 0000000..939b36a --- /dev/null +++ b/tests/enum-unknown-constructor.diag @@ -0,0 +1,12 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnknownEnumConstructor) + (message "unknown enum `Missing` in variant constructor") + (file "") + (span + (bytes 38 49) + (range 5 4 5 15) + ) +) diff --git a/tests/enum-unknown-variant.diag b/tests/enum-unknown-variant.diag new file mode 100644 index 0000000..7952bbe --- /dev/null +++ b/tests/enum-unknown-variant.diag @@ -0,0 +1,13 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnknownVariantConstructor) + (message "enum `Color` has no variant `Blue`") + (file "") + (span + (bytes 58 68) + (range 7 4 7 14) + ) + (expected "one of Red") +) diff --git a/tests/enum-unqualified-variant-constructor.diag b/tests/enum-unqualified-variant-constructor.diag new file mode 100644 index 0000000..205e68a --- /dev/null +++ b/tests/enum-unqualified-variant-constructor.diag @@ -0,0 +1,12 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnknownFunction) + (message "unknown function `Red`") + (file "") + (span + (bytes 57 62) + (range 7 3 7 8) + ) +) diff --git a/tests/f64-numeric-primitive.checked.lower b/tests/f64-numeric-primitive.checked.lower new file mode 100644 index 0000000..46c108e --- /dev/null +++ b/tests/f64-numeric-primitive.checked.lower @@ -0,0 +1,50 @@ +program main + fn half(value: f64) -> f64 + binary / : f64 + var value : f64 + float 2 : f64 + fn weighted(base: f64, scale: f64) -> f64 + binary + : f64 + var base : f64 + binary * : f64 + var scale : f64 + float 2.5 : f64 + fn local_total() -> f64 + local let subtotal : unit + call weighted : f64 + float 4 : f64 + float 3 : f64 + binary - : f64 + var subtotal : f64 + float 1.5 : f64 + fn close_enough(value: f64) -> bool + if : bool + binary > : bool + var value : f64 + float 9 : f64 + binary < : bool + var value : f64 + float 11 : f64 + bool false : bool + fn exact_literal() -> bool + binary = : bool + call half : f64 + float 7 : f64 + float 3.5 : f64 + fn main() -> i32 + call std.io.print_f64 : unit + call local_total : f64 + if : i32 + call close_enough : bool + call local_total : f64 + int 0 : i32 + int 1 : i32 + test "f64 arithmetic returns exact fixture value" + binary = : bool + call local_total : f64 + float 10 : f64 + test "f64 comparison works in predicates" + call close_enough : bool + call local_total : f64 + test "f64 division and equality" + call exact_literal : bool diff --git a/tests/f64-numeric-primitive.slo b/tests/f64-numeric-primitive.slo new file mode 100644 index 0000000..edd20b5 --- /dev/null +++ b/tests/f64-numeric-primitive.slo @@ -0,0 +1,32 @@ +(module main) + +(fn half ((value f64)) -> f64 + (/ value 2.0)) + +(fn weighted ((base f64) (scale f64)) -> f64 + (+ base (* scale 2.5))) + +(fn local_total () -> f64 + (let subtotal f64 (weighted 4.0 3.0)) + (- subtotal 1.5)) + +(fn close_enough ((value f64)) -> bool + (if (> value 9.0) + (< value 11.0) + false)) + +(fn exact_literal () -> bool + (= (half 7.0) 3.5)) + +(fn main () -> i32 + (std.io.print_f64 (local_total)) + (if (close_enough (local_total)) 0 1)) + +(test "f64 arithmetic returns exact fixture value" + (= (local_total) 10.0)) + +(test "f64 comparison works in predicates" + (close_enough (local_total))) + +(test "f64 division and equality" + (exact_literal)) diff --git a/tests/f64-numeric-primitive.surface.lower b/tests/f64-numeric-primitive.surface.lower new file mode 100644 index 0000000..16857a1 --- /dev/null +++ b/tests/f64-numeric-primitive.surface.lower @@ -0,0 +1,50 @@ +program main + fn half(value: f64) -> f64 + binary / + var value + float 2 + fn weighted(base: f64, scale: f64) -> f64 + binary + + var base + binary * + var scale + float 2.5 + fn local_total() -> f64 + local let subtotal: f64 + call weighted + float 4 + float 3 + binary - + var subtotal + float 1.5 + fn close_enough(value: f64) -> bool + if + binary > + var value + float 9 + binary < + var value + float 11 + bool false + fn exact_literal() -> bool + binary = + call half + float 7 + float 3.5 + fn main() -> i32 + call std.io.print_f64 + call local_total + if + call close_enough + call local_total + int 0 + int 1 + test "f64 arithmetic returns exact fixture value" + binary = + call local_total + float 10 + test "f64 comparison works in predicates" + call close_enough + call local_total + test "f64 division and equality" + call exact_literal diff --git a/tests/f64-to-i32-result.checked.lower b/tests/f64-to-i32-result.checked.lower new file mode 100644 index 0000000..e2268f3 --- /dev/null +++ b/tests/f64-to-i32-result.checked.lower @@ -0,0 +1,81 @@ +program main + fn narrow(value: f64) -> (result i32 i32) + call std.num.f64_to_i32_result : (result i32 i32) + var value : f64 + fn zero_ok() -> (result i32 i32) + call narrow : (result i32 i32) + binary - : f64 + float 2.5 : f64 + float 2.5 : f64 + fn negative_ok() -> (result i32 i32) + call narrow : (result i32 i32) + binary - : f64 + float 1 : f64 + float 13 : f64 + fn fractional_err() -> (result i32 i32) + call narrow : (result i32 i32) + binary / : f64 + float 7 : f64 + float 2 : f64 + fn above_high_err() -> (result i32 i32) + call narrow : (result i32 i32) + float 2147483648 : f64 + fn main() -> i32 + local let value : unit + call negative_ok : (result i32 i32) + if : i32 + std.result.is_ok : bool + var value : (result i32 i32) + if : i32 + binary = : bool + std.result.unwrap_ok : i32 + var value : (result i32 i32) + int -12 : i32 + int 0 : i32 + int 1 : i32 + std.result.unwrap_err : i32 + var value : (result i32 i32) + test "f64 zero narrows to i32" + local let value : unit + call zero_ok : (result i32 i32) + if : bool + std.result.is_ok : bool + var value : (result i32 i32) + binary = : bool + std.result.unwrap_ok : i32 + var value : (result i32 i32) + int 0 : i32 + bool false : bool + test "negative f64 narrows to i32" + local let value : unit + call negative_ok : (result i32 i32) + if : bool + std.result.is_ok : bool + var value : (result i32 i32) + binary = : bool + std.result.unwrap_ok : i32 + var value : (result i32 i32) + int -12 : i32 + bool false : bool + test "fractional f64 returns err" + local let value : unit + call fractional_err : (result i32 i32) + if : bool + std.result.is_err : bool + var value : (result i32 i32) + binary = : bool + std.result.unwrap_err : i32 + var value : (result i32 i32) + int 1 : i32 + bool false : bool + test "above i32 range f64 returns err" + local let value : unit + call above_high_err : (result i32 i32) + if : bool + std.result.is_err : bool + var value : (result i32 i32) + binary = : bool + std.result.unwrap_err : i32 + var value : (result i32 i32) + int 1 : i32 + bool false : bool diff --git a/tests/f64-to-i32-result.slo b/tests/f64-to-i32-result.slo new file mode 100644 index 0000000..601c149 --- /dev/null +++ b/tests/f64-to-i32-result.slo @@ -0,0 +1,48 @@ +(module main) + +(fn narrow ((value f64)) -> (result i32 i32) + (std.num.f64_to_i32_result value)) + +(fn zero_ok () -> (result i32 i32) + (narrow (- 2.5 2.5))) + +(fn negative_ok () -> (result i32 i32) + (narrow (- 1.0 13.0))) + +(fn fractional_err () -> (result i32 i32) + (narrow (/ 7.0 2.0))) + +(fn above_high_err () -> (result i32 i32) + (narrow 2147483648.0)) + +(test "f64 zero narrows to i32" + (let value (result i32 i32) (zero_ok)) + (if (std.result.is_ok value) + (= (std.result.unwrap_ok value) 0) + false)) + +(test "negative f64 narrows to i32" + (let value (result i32 i32) (negative_ok)) + (if (std.result.is_ok value) + (= (std.result.unwrap_ok value) -12) + false)) + +(test "fractional f64 returns err" + (let value (result i32 i32) (fractional_err)) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "above i32 range f64 returns err" + (let value (result i32 i32) (above_high_err)) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(fn main () -> i32 + (let value (result i32 i32) (negative_ok)) + (if (std.result.is_ok value) + (if (= (std.result.unwrap_ok value) -12) + 0 + 1) + (std.result.unwrap_err value))) diff --git a/tests/f64-to-i32-result.surface.lower b/tests/f64-to-i32-result.surface.lower new file mode 100644 index 0000000..46b7099 --- /dev/null +++ b/tests/f64-to-i32-result.surface.lower @@ -0,0 +1,81 @@ +program main + fn narrow(value: f64) -> (result i32 i32) + call std.num.f64_to_i32_result + var value + fn zero_ok() -> (result i32 i32) + call narrow + binary - + float 2.5 + float 2.5 + fn negative_ok() -> (result i32 i32) + call narrow + binary - + float 1 + float 13 + fn fractional_err() -> (result i32 i32) + call narrow + binary / + float 7 + float 2 + fn above_high_err() -> (result i32 i32) + call narrow + float 2147483648 + fn main() -> i32 + local let value: (result i32 i32) + call negative_ok + if + std.result.is_ok + var value + if + binary = + std.result.unwrap_ok + var value + int -12 + int 0 + int 1 + std.result.unwrap_err + var value + test "f64 zero narrows to i32" + local let value: (result i32 i32) + call zero_ok + if + std.result.is_ok + var value + binary = + std.result.unwrap_ok + var value + int 0 + bool false + test "negative f64 narrows to i32" + local let value: (result i32 i32) + call negative_ok + if + std.result.is_ok + var value + binary = + std.result.unwrap_ok + var value + int -12 + bool false + test "fractional f64 returns err" + local let value: (result i32 i32) + call fractional_err + if + std.result.is_err + var value + binary = + std.result.unwrap_err + var value + int 1 + bool false + test "above i32 range f64 returns err" + local let value: (result i32 i32) + call above_high_err + if + std.result.is_err + var value + binary = + std.result.unwrap_err + var value + int 1 + bool false diff --git a/tests/f64-to-i64-result.checked.lower b/tests/f64-to-i64-result.checked.lower new file mode 100644 index 0000000..43b79a2 --- /dev/null +++ b/tests/f64-to-i64-result.checked.lower @@ -0,0 +1,81 @@ +program main + fn narrow(value: f64) -> (result i64 i32) + call std.num.f64_to_i64_result : (result i64 i32) + var value : f64 + fn zero_ok() -> (result i64 i32) + call narrow : (result i64 i32) + binary - : f64 + float 2.5 : f64 + float 2.5 : f64 + fn negative_ok() -> (result i64 i32) + call narrow : (result i64 i32) + binary - : f64 + float 1 : f64 + float 13 : f64 + fn fractional_err() -> (result i64 i32) + call narrow : (result i64 i32) + binary / : f64 + float 7 : f64 + float 2 : f64 + fn above_high_err() -> (result i64 i32) + call narrow : (result i64 i32) + float 9223372036854776000 : f64 + fn main() -> i32 + local let value : unit + call negative_ok : (result i64 i32) + if : i32 + std.result.is_ok : bool + var value : (result i64 i32) + if : i32 + binary = : bool + std.result.unwrap_ok : i64 + var value : (result i64 i32) + i64 -12 : i64 + int 0 : i32 + int 1 : i32 + std.result.unwrap_err : i32 + var value : (result i64 i32) + test "f64 zero narrows to i64" + local let value : unit + call zero_ok : (result i64 i32) + if : bool + std.result.is_ok : bool + var value : (result i64 i32) + binary = : bool + std.result.unwrap_ok : i64 + var value : (result i64 i32) + i64 0 : i64 + bool false : bool + test "negative f64 narrows to i64" + local let value : unit + call negative_ok : (result i64 i32) + if : bool + std.result.is_ok : bool + var value : (result i64 i32) + binary = : bool + std.result.unwrap_ok : i64 + var value : (result i64 i32) + i64 -12 : i64 + bool false : bool + test "fractional f64 returns err for i64" + local let value : unit + call fractional_err : (result i64 i32) + if : bool + std.result.is_err : bool + var value : (result i64 i32) + binary = : bool + std.result.unwrap_err : i32 + var value : (result i64 i32) + int 1 : i32 + bool false : bool + test "above i64 range f64 returns err" + local let value : unit + call above_high_err : (result i64 i32) + if : bool + std.result.is_err : bool + var value : (result i64 i32) + binary = : bool + std.result.unwrap_err : i32 + var value : (result i64 i32) + int 1 : i32 + bool false : bool diff --git a/tests/f64-to-i64-result.slo b/tests/f64-to-i64-result.slo new file mode 100644 index 0000000..8aedccd --- /dev/null +++ b/tests/f64-to-i64-result.slo @@ -0,0 +1,48 @@ +(module main) + +(fn narrow ((value f64)) -> (result i64 i32) + (std.num.f64_to_i64_result value)) + +(fn zero_ok () -> (result i64 i32) + (narrow (- 2.5 2.5))) + +(fn negative_ok () -> (result i64 i32) + (narrow (- 1.0 13.0))) + +(fn fractional_err () -> (result i64 i32) + (narrow (/ 7.0 2.0))) + +(fn above_high_err () -> (result i64 i32) + (narrow 9223372036854776000.0)) + +(test "f64 zero narrows to i64" + (let value (result i64 i32) (zero_ok)) + (if (std.result.is_ok value) + (= (std.result.unwrap_ok value) 0i64) + false)) + +(test "negative f64 narrows to i64" + (let value (result i64 i32) (negative_ok)) + (if (std.result.is_ok value) + (= (std.result.unwrap_ok value) -12i64) + false)) + +(test "fractional f64 returns err for i64" + (let value (result i64 i32) (fractional_err)) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "above i64 range f64 returns err" + (let value (result i64 i32) (above_high_err)) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(fn main () -> i32 + (let value (result i64 i32) (negative_ok)) + (if (std.result.is_ok value) + (if (= (std.result.unwrap_ok value) -12i64) + 0 + 1) + (std.result.unwrap_err value))) diff --git a/tests/f64-to-i64-result.surface.lower b/tests/f64-to-i64-result.surface.lower new file mode 100644 index 0000000..93c88d3 --- /dev/null +++ b/tests/f64-to-i64-result.surface.lower @@ -0,0 +1,81 @@ +program main + fn narrow(value: f64) -> (result i64 i32) + call std.num.f64_to_i64_result + var value + fn zero_ok() -> (result i64 i32) + call narrow + binary - + float 2.5 + float 2.5 + fn negative_ok() -> (result i64 i32) + call narrow + binary - + float 1 + float 13 + fn fractional_err() -> (result i64 i32) + call narrow + binary / + float 7 + float 2 + fn above_high_err() -> (result i64 i32) + call narrow + float 9223372036854776000 + fn main() -> i32 + local let value: (result i64 i32) + call negative_ok + if + std.result.is_ok + var value + if + binary = + std.result.unwrap_ok + var value + i64 -12 + int 0 + int 1 + std.result.unwrap_err + var value + test "f64 zero narrows to i64" + local let value: (result i64 i32) + call zero_ok + if + std.result.is_ok + var value + binary = + std.result.unwrap_ok + var value + i64 0 + bool false + test "negative f64 narrows to i64" + local let value: (result i64 i32) + call negative_ok + if + std.result.is_ok + var value + binary = + std.result.unwrap_ok + var value + i64 -12 + bool false + test "fractional f64 returns err for i64" + local let value: (result i64 i32) + call fractional_err + if + std.result.is_err + var value + binary = + std.result.unwrap_err + var value + int 1 + bool false + test "above i64 range f64 returns err" + local let value: (result i64 i32) + call above_high_err + if + std.result.is_err + var value + binary = + std.result.unwrap_err + var value + int 1 + bool false diff --git a/tests/f64-to-string.checked.lower b/tests/f64-to-string.checked.lower new file mode 100644 index 0000000..1b3c089 --- /dev/null +++ b/tests/f64-to-string.checked.lower @@ -0,0 +1,63 @@ +program main + fn f64_zero_text() -> string + call std.num.f64_to_string : string + binary - : f64 + float 2.5 : f64 + float 2.5 : f64 + fn f64_fractional_text() -> string + call std.num.f64_to_string : string + binary / : f64 + float 7 : f64 + float 2 : f64 + fn f64_negative_text() -> string + call std.num.f64_to_string : string + binary - : f64 + float 2.5 : f64 + float 4 : f64 + fn f64_whole_text() -> string + call std.num.f64_to_string : string + binary + : f64 + float 7 : f64 + float 3 : f64 + fn main() -> i32 + call std.io.print_string : unit + call f64_zero_text : string + call std.io.print_string : unit + call f64_fractional_text : string + call std.io.print_string : unit + call f64_negative_text : string + call std.io.print_string : unit + call f64_whole_text : string + if : i32 + binary = : bool + call std.string.len : i32 + call f64_fractional_text : string + int 3 : i32 + int 0 : i32 + int 1 : i32 + test "f64 zero to string" + binary = : bool + call f64_zero_text : string + string "0.0" : string + test "f64 fractional to string" + binary = : bool + call f64_fractional_text : string + string "3.5" : string + test "f64 negative to string" + binary = : bool + call f64_negative_text : string + string "-1.5" : string + test "f64 whole to string" + binary = : bool + call f64_whole_text : string + string "10.0" : string + test "f64 negative string length" + binary = : bool + call std.string.len : i32 + call f64_negative_text : string + int 4 : i32 + test "f64 whole string length" + binary = : bool + call std.string.len : i32 + call f64_whole_text : string + int 4 : i32 diff --git a/tests/f64-to-string.slo b/tests/f64-to-string.slo new file mode 100644 index 0000000..f24c865 --- /dev/null +++ b/tests/f64-to-string.slo @@ -0,0 +1,40 @@ +(module main) + +(fn f64_zero_text () -> string + (std.num.f64_to_string (- 2.5 2.5))) + +(fn f64_fractional_text () -> string + (std.num.f64_to_string (/ 7.0 2.0))) + +(fn f64_negative_text () -> string + (std.num.f64_to_string (- 2.5 4.0))) + +(fn f64_whole_text () -> string + (std.num.f64_to_string (+ 7.0 3.0))) + +(test "f64 zero to string" + (= (f64_zero_text) "0.0")) + +(test "f64 fractional to string" + (= (f64_fractional_text) "3.5")) + +(test "f64 negative to string" + (= (f64_negative_text) "-1.5")) + +(test "f64 whole to string" + (= (f64_whole_text) "10.0")) + +(test "f64 negative string length" + (= (std.string.len (f64_negative_text)) 4)) + +(test "f64 whole string length" + (= (std.string.len (f64_whole_text)) 4)) + +(fn main () -> i32 + (std.io.print_string (f64_zero_text)) + (std.io.print_string (f64_fractional_text)) + (std.io.print_string (f64_negative_text)) + (std.io.print_string (f64_whole_text)) + (if (= (std.string.len (f64_fractional_text)) 3) + 0 + 1)) diff --git a/tests/f64-to-string.surface.lower b/tests/f64-to-string.surface.lower new file mode 100644 index 0000000..de8e842 --- /dev/null +++ b/tests/f64-to-string.surface.lower @@ -0,0 +1,63 @@ +program main + fn f64_zero_text() -> string + call std.num.f64_to_string + binary - + float 2.5 + float 2.5 + fn f64_fractional_text() -> string + call std.num.f64_to_string + binary / + float 7 + float 2 + fn f64_negative_text() -> string + call std.num.f64_to_string + binary - + float 2.5 + float 4 + fn f64_whole_text() -> string + call std.num.f64_to_string + binary + + float 7 + float 3 + fn main() -> i32 + call std.io.print_string + call f64_zero_text + call std.io.print_string + call f64_fractional_text + call std.io.print_string + call f64_negative_text + call std.io.print_string + call f64_whole_text + if + binary = + call std.string.len + call f64_fractional_text + int 3 + int 0 + int 1 + test "f64 zero to string" + binary = + call f64_zero_text + string "0.0" + test "f64 fractional to string" + binary = + call f64_fractional_text + string "3.5" + test "f64 negative to string" + binary = + call f64_negative_text + string "-1.5" + test "f64 whole to string" + binary = + call f64_whole_text + string "10.0" + test "f64 negative string length" + binary = + call std.string.len + call f64_negative_text + int 4 + test "f64 whole string length" + binary = + call std.string.len + call f64_whole_text + int 4 diff --git a/tests/field-access-on-non-struct.diag b/tests/field-access-on-non-struct.diag new file mode 100644 index 0000000..9836863 --- /dev/null +++ b/tests/field-access-on-non-struct.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code FieldAccessOnNonStruct) + (message "field access requires a struct value") + (file "") + (span + (bytes 40 41) + (range 5 6 5 7) + ) + (expected "struct") + (found "i32") +) diff --git a/tests/formatter-stability-v1.checked.lower b/tests/formatter-stability-v1.checked.lower new file mode 100644 index 0000000..c5239d5 --- /dev/null +++ b/tests/formatter-stability-v1.checked.lower @@ -0,0 +1,84 @@ +program stability + struct Pair + field left: i32 + field right: i32 + fn sum_pair(pair: Pair) -> i32 + binary + : i32 + field-access left : i32 + var pair : Pair + field-access right : i32 + var pair : Pair + fn choose(value: i32) -> i32 + if : i32 + binary < : bool + var value : i32 + int 10 : i32 + if : i32 + binary < : bool + var value : i32 + int 5 : i32 + binary + : i32 + var value : i32 + int 1 : i32 + binary + : i32 + var value : i32 + int 2 : i32 + unsafe : i32 + if : i32 + binary < : bool + var value : i32 + int 20 : i32 + binary + : i32 + var value : i32 + int 3 : i32 + binary + : i32 + var value : i32 + int 4 : i32 + fn loop_sum() -> i32 + local var i : unit + int 0 : i32 + local var total : unit + int 0 : i32 + while : unit + binary < : bool + var i : i32 + int 3 : i32 + set total : unit + unsafe : i32 + if : i32 + binary < : bool + var i : i32 + int 2 : i32 + binary + : i32 + var total : i32 + var i : i32 + binary + : i32 + var total : i32 + int 1 : i32 + set i : unit + binary + : i32 + var i : i32 + int 1 : i32 + var total : i32 + fn accept_many(a: i32, b: i32, c: i32, d: i32, e: i32, f: i32, g: i32, h: i32, i: i32, j: i32) -> i32 + binary + : i32 + var a : i32 + var j : i32 + fn long_inline_call() -> i32 + call accept_many : i32 + int 100000001 : i32 + int 100000002 : i32 + int 100000003 : i32 + int 100000004 : i32 + int 100000005 : i32 + int 100000006 : i32 + int 100000007 : i32 + int 100000008 : i32 + int 100000009 : i32 + int 100000010 : i32 + test "comments stay" + local let value : unit + int 42 : i32 + binary = : bool + var value : i32 + int 42 : i32 diff --git a/tests/formatter-stability-v1.fmt b/tests/formatter-stability-v1.fmt new file mode 100644 index 0000000..17a68f8 --- /dev/null +++ b/tests/formatter-stability-v1.fmt @@ -0,0 +1,55 @@ +; status: formatter-stability-v1 +; Scope: Slovo v1 formatter stability contract for supported comments, nested promoted forms, and long inline forms. + +(module stability) + +; before top-level struct + +(struct Pair + (left i32) + (right i32)) + +; before top-level function + +(fn sum_pair ((pair Pair)) -> i32 + ; before function body expression + (+ (. pair left) (. pair right)) + ; after final function body expression +) + +(fn choose ((value i32)) -> i32 + (if (< value 10) + (if (< value 5) + (+ value 1) + (+ value 2)) + (unsafe + (if (< value 20) + (+ value 3) + (+ value 4))))) + +(fn loop_sum () -> i32 + (var i i32 0) + (var total i32 0) + (while (< i 3) + (set total (unsafe + (if (< i 2) + (+ total i) + (+ total 1)))) + (set i (+ i 1))) + total) + +(fn accept_many ((a i32) (b i32) (c i32) (d i32) (e i32) (f i32) (g i32) (h i32) (i i32) (j i32)) -> i32 + (+ a j)) + +(fn long_inline_call () -> i32 + (accept_many 100000001 100000002 100000003 100000004 100000005 100000006 100000007 100000008 100000009 100000010)) + +(test "comments stay" + ; before test setup + (let value i32 42) + ; before test final expression + (= value 42) + ; after test final expression +) + +; after last top-level form diff --git a/tests/formatter-stability-v1.surface.lower b/tests/formatter-stability-v1.surface.lower new file mode 100644 index 0000000..f288719 --- /dev/null +++ b/tests/formatter-stability-v1.surface.lower @@ -0,0 +1,84 @@ +program stability + struct Pair + field left: i32 + field right: i32 + fn sum_pair(pair: Pair) -> i32 + binary + + field-access left + var pair + field-access right + var pair + fn choose(value: i32) -> i32 + if + binary < + var value + int 10 + if + binary < + var value + int 5 + binary + + var value + int 1 + binary + + var value + int 2 + unsafe + if + binary < + var value + int 20 + binary + + var value + int 3 + binary + + var value + int 4 + fn loop_sum() -> i32 + local var i: i32 + int 0 + local var total: i32 + int 0 + while + binary < + var i + int 3 + set total + unsafe + if + binary < + var i + int 2 + binary + + var total + var i + binary + + var total + int 1 + set i + binary + + var i + int 1 + var total + fn accept_many(a: i32, b: i32, c: i32, d: i32, e: i32, f: i32, g: i32, h: i32, i: i32, j: i32) -> i32 + binary + + var a + var j + fn long_inline_call() -> i32 + call accept_many + int 100000001 + int 100000002 + int 100000003 + int 100000004 + int 100000005 + int 100000006 + int 100000007 + int 100000008 + int 100000009 + int 100000010 + test "comments stay" + local let value: i32 + int 42 + binary = + var value + int 42 diff --git a/tests/host-io-result.checked.lower b/tests/host-io-result.checked.lower new file mode 100644 index 0000000..abcfff2 --- /dev/null +++ b/tests/host-io-result.checked.lower @@ -0,0 +1,107 @@ +program main + fn fixture_path() -> string + string "slovo-exp10-host-io-result.txt" : string + fn ok_text(value: string) -> (result string i32) + ok : (result string i32) + var value : string + fn err_text(code: i32) -> (result string i32) + err : (result string i32) + var code : i32 + fn result_text_or_error(value: (result string i32)) -> string + match : string + subject + var value : (result string i32) + arm ok payload + var payload : string + arm err code + call std.io.print_i32 : unit + var code : i32 + string "error" : string + fn result_text_len_or_code(value: (result string i32)) -> i32 + match : i32 + subject + var value : (result string i32) + arm ok payload + call std.string.len : i32 + var payload : string + arm err code + var code : i32 + fn write_fixture_result() -> (result i32 i32) + call std.fs.write_text_result : (result i32 i32) + call fixture_path : string + string "exp10 host io" : string + fn read_fixture_result() -> (result string i32) + call std.fs.write_text_result : (result i32 i32) + call fixture_path : string + string "exp10 host io" : string + call std.fs.read_text_result : (result string i32) + call fixture_path : string + fn missing_env_result() -> (result string i32) + call std.env.get_result : (result string i32) + string "SLOVO_EXP10_MISSING_ENV" : string + fn first_arg_ok_score() -> i32 + if : i32 + binary < : bool + int 0 : i32 + call std.process.argc : i32 + if : i32 + is_ok : bool + call std.process.arg_result : (result string i32) + int 0 : i32 + int 1 : i32 + int 0 : i32 + int 0 : i32 + fn read_unwrapped_text() -> string + unwrap_ok : string + call read_fixture_result : (result string i32) + fn missing_env_code() -> i32 + unwrap_err : i32 + call missing_env_result : (result string i32) + fn main() -> i32 + call std.io.eprint : unit + string "exp-10 host io result" : string + if : i32 + is_ok : bool + call write_fixture_result : (result i32 i32) + unwrap_ok : i32 + call write_fixture_result : (result i32 i32) + int 1 : i32 + test "string result constructor ok" + binary = : bool + call result_text_or_error : string + call ok_text : (result string i32) + string "constructed" : string + string "constructed" : string + test "string result constructor err" + binary = : bool + call result_text_len_or_code : i32 + call err_text : (result string i32) + int 1 : i32 + int 1 : i32 + test "missing env returns err 1" + binary = : bool + call missing_env_code : i32 + int 1 : i32 + test "arg result is ok when argc is positive" + binary = : bool + call first_arg_ok_score : i32 + if : i32 + binary < : bool + int 0 : i32 + call std.process.argc : i32 + int 1 : i32 + int 0 : i32 + test "write text result returns ok zero" + binary = : bool + unwrap_ok : i32 + call write_fixture_result : (result i32 i32) + int 0 : i32 + test "read text result returns written text" + binary = : bool + call read_unwrapped_text : string + string "exp10 host io" : string + test "read text result match observes string payload" + binary = : bool + call result_text_len_or_code : i32 + call read_fixture_result : (result string i32) + int 13 : i32 diff --git a/tests/host-io-result.slo b/tests/host-io-result.slo new file mode 100644 index 0000000..3ed43a6 --- /dev/null +++ b/tests/host-io-result.slo @@ -0,0 +1,77 @@ +(module main) + +(fn fixture_path () -> string + "slovo-exp10-host-io-result.txt") + +(fn ok_text ((value string)) -> (result string i32) + (ok string i32 value)) + +(fn err_text ((code i32)) -> (result string i32) + (err string i32 code)) + +(fn result_text_or_error ((value (result string i32))) -> string + (match value + ((ok payload) + payload) + ((err code) + (std.io.print_i32 code) + "error"))) + +(fn result_text_len_or_code ((value (result string i32))) -> i32 + (match value + ((ok payload) + (std.string.len payload)) + ((err code) + code))) + +(fn write_fixture_result () -> (result i32 i32) + (std.fs.write_text_result (fixture_path) "exp10 host io")) + +(fn read_fixture_result () -> (result string i32) + (std.fs.write_text_result (fixture_path) "exp10 host io") + (std.fs.read_text_result (fixture_path))) + +(fn missing_env_result () -> (result string i32) + (std.env.get_result "SLOVO_EXP10_MISSING_ENV")) + +(fn first_arg_ok_score () -> i32 + (if (< 0 (std.process.argc)) + (if (is_ok (std.process.arg_result 0)) + 1 + 0) + 0)) + +(fn read_unwrapped_text () -> string + (unwrap_ok (read_fixture_result))) + +(fn missing_env_code () -> i32 + (unwrap_err (missing_env_result))) + +(test "string result constructor ok" + (= (result_text_or_error (ok_text "constructed")) "constructed")) + +(test "string result constructor err" + (= (result_text_len_or_code (err_text 1)) 1)) + +(test "missing env returns err 1" + (= (missing_env_code) 1)) + +(test "arg result is ok when argc is positive" + (= (first_arg_ok_score) (if (< 0 (std.process.argc)) + 1 + 0))) + +(test "write text result returns ok zero" + (= (unwrap_ok (write_fixture_result)) 0)) + +(test "read text result returns written text" + (= (read_unwrapped_text) "exp10 host io")) + +(test "read text result match observes string payload" + (= (result_text_len_or_code (read_fixture_result)) 13)) + +(fn main () -> i32 + (std.io.eprint "exp-10 host io result") + (if (is_ok (write_fixture_result)) + (unwrap_ok (write_fixture_result)) + 1)) diff --git a/tests/host-io-result.surface.lower b/tests/host-io-result.surface.lower new file mode 100644 index 0000000..6e7510b --- /dev/null +++ b/tests/host-io-result.surface.lower @@ -0,0 +1,107 @@ +program main + fn fixture_path() -> string + string "slovo-exp10-host-io-result.txt" + fn ok_text(value: string) -> (result string i32) + ok string i32 + var value + fn err_text(code: i32) -> (result string i32) + err string i32 + var code + fn result_text_or_error(value: (result string i32)) -> string + match + subject + var value + arm ok payload + var payload + arm err code + call std.io.print_i32 + var code + string "error" + fn result_text_len_or_code(value: (result string i32)) -> i32 + match + subject + var value + arm ok payload + call std.string.len + var payload + arm err code + var code + fn write_fixture_result() -> (result i32 i32) + call std.fs.write_text_result + call fixture_path + string "exp10 host io" + fn read_fixture_result() -> (result string i32) + call std.fs.write_text_result + call fixture_path + string "exp10 host io" + call std.fs.read_text_result + call fixture_path + fn missing_env_result() -> (result string i32) + call std.env.get_result + string "SLOVO_EXP10_MISSING_ENV" + fn first_arg_ok_score() -> i32 + if + binary < + int 0 + call std.process.argc + if + is_ok + call std.process.arg_result + int 0 + int 1 + int 0 + int 0 + fn read_unwrapped_text() -> string + unwrap_ok + call read_fixture_result + fn missing_env_code() -> i32 + unwrap_err + call missing_env_result + fn main() -> i32 + call std.io.eprint + string "exp-10 host io result" + if + is_ok + call write_fixture_result + unwrap_ok + call write_fixture_result + int 1 + test "string result constructor ok" + binary = + call result_text_or_error + call ok_text + string "constructed" + string "constructed" + test "string result constructor err" + binary = + call result_text_len_or_code + call err_text + int 1 + int 1 + test "missing env returns err 1" + binary = + call missing_env_code + int 1 + test "arg result is ok when argc is positive" + binary = + call first_arg_ok_score + if + binary < + int 0 + call std.process.argc + int 1 + int 0 + test "write text result returns ok zero" + binary = + unwrap_ok + call write_fixture_result + int 0 + test "read text result returns written text" + binary = + call read_unwrapped_text + string "exp10 host io" + test "read text result match observes string payload" + binary = + call result_text_len_or_code + call read_fixture_result + int 13 diff --git a/tests/host-io.checked.lower b/tests/host-io.checked.lower new file mode 100644 index 0000000..c60e98b --- /dev/null +++ b/tests/host-io.checked.lower @@ -0,0 +1,44 @@ +program main + fn fixture_path() -> string + string "slovo-exp3-host-io.txt" : string + fn write_fixture() -> i32 + call std.fs.write_text : i32 + call fixture_path : string + string "host io" : string + fn read_fixture() -> string + call std.fs.read_text : string + call fixture_path : string + fn missing_env() -> string + call std.env.get : string + string "SLOVO_EXP3_MISSING" : string + fn arg_count_plus_one() -> i32 + binary + : i32 + call std.process.argc : i32 + int 1 : i32 + fn main() -> i32 + call std.io.eprint : unit + string "exp-3 host io" : string + call std.io.print_string : unit + call std.process.arg : string + int 0 : i32 + call std.io.print_string : unit + call read_fixture : string + call std.fs.write_text : i32 + call fixture_path : string + string "host io" : string + test "missing env returns empty string" + binary = : bool + call missing_env : string + string "" : string + test "argc is not negative" + binary < : bool + int 0 : i32 + call arg_count_plus_one : i32 + test "write text returns success" + binary = : bool + call write_fixture : i32 + int 0 : i32 + test "read text returns written text" + binary = : bool + call read_fixture : string + string "host io" : string diff --git a/tests/host-io.slo b/tests/host-io.slo new file mode 100644 index 0000000..7d1eec5 --- /dev/null +++ b/tests/host-io.slo @@ -0,0 +1,34 @@ +(module main) + +(fn fixture_path () -> string + "slovo-exp3-host-io.txt") + +(fn write_fixture () -> i32 + (std.fs.write_text (fixture_path) "host io")) + +(fn read_fixture () -> string + (std.fs.read_text (fixture_path))) + +(fn missing_env () -> string + (std.env.get "SLOVO_EXP3_MISSING")) + +(fn arg_count_plus_one () -> i32 + (+ (std.process.argc) 1)) + +(test "missing env returns empty string" + (= (missing_env) "")) + +(test "argc is not negative" + (< 0 (arg_count_plus_one))) + +(test "write text returns success" + (= (write_fixture) 0)) + +(test "read text returns written text" + (= (read_fixture) "host io")) + +(fn main () -> i32 + (std.io.eprint "exp-3 host io") + (std.io.print_string (std.process.arg 0)) + (std.io.print_string (read_fixture)) + (std.fs.write_text (fixture_path) "host io")) diff --git a/tests/host-io.surface.lower b/tests/host-io.surface.lower new file mode 100644 index 0000000..1b4626b --- /dev/null +++ b/tests/host-io.surface.lower @@ -0,0 +1,44 @@ +program main + fn fixture_path() -> string + string "slovo-exp3-host-io.txt" + fn write_fixture() -> i32 + call std.fs.write_text + call fixture_path + string "host io" + fn read_fixture() -> string + call std.fs.read_text + call fixture_path + fn missing_env() -> string + call std.env.get + string "SLOVO_EXP3_MISSING" + fn arg_count_plus_one() -> i32 + binary + + call std.process.argc + int 1 + fn main() -> i32 + call std.io.eprint + string "exp-3 host io" + call std.io.print_string + call std.process.arg + int 0 + call std.io.print_string + call read_fixture + call std.fs.write_text + call fixture_path + string "host io" + test "missing env returns empty string" + binary = + call missing_env + string "" + test "argc is not negative" + binary < + int 0 + call arg_count_plus_one + test "write text returns success" + binary = + call write_fixture + int 0 + test "read text returns written text" + binary = + call read_fixture + string "host io" diff --git a/tests/i64-literal-out-of-range.diag b/tests/i64-literal-out-of-range.diag new file mode 100644 index 0000000..952238e --- /dev/null +++ b/tests/i64-literal-out-of-range.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code I64LiteralOutOfRange) + (message "i64 literal is outside the supported signed 64-bit range") + (file "") + (span + (bytes 37 59) + (range 5 3 5 25) + ) + (expected "signed 64-bit integer literal with `i64` suffix") + (found "9223372036854775808i64") + (hint "use a value between -9223372036854775808i64 and 9223372036854775807i64") +) diff --git a/tests/i64-numeric-primitive.checked.lower b/tests/i64-numeric-primitive.checked.lower new file mode 100644 index 0000000..92aef21 --- /dev/null +++ b/tests/i64-numeric-primitive.checked.lower @@ -0,0 +1,59 @@ +program main + fn base() -> i64 + i64 2147483648 : i64 + fn adjust(value: i64, delta: i64) -> i64 + binary + : i64 + var value : i64 + var delta : i64 + fn doubled(value: i64) -> i64 + binary * : i64 + var value : i64 + i64 2 : i64 + fn local_total() -> i64 + local let offset : unit + i64 -7 : i64 + call adjust : i64 + binary - : i64 + call doubled : i64 + call base : i64 + i64 0 : i64 + var offset : i64 + fn high_enough(value: i64) -> bool + if : bool + binary > : bool + var value : i64 + i64 4294967280 : i64 + binary < : bool + var value : i64 + i64 4294967300 : i64 + bool false : bool + fn exact_i64() -> bool + binary = : bool + call local_total : i64 + i64 4294967289 : i64 + fn main() -> i32 + call std.io.print_i64 : unit + call local_total : i64 + if : i32 + call high_enough : bool + call local_total : i64 + int 0 : i32 + int 1 : i32 + test "i64 arithmetic returns exact fixture value" + call exact_i64 : bool + test "i64 comparison works in predicates" + call high_enough : bool + call local_total : i64 + test "i64 division and ordering" + if : bool + binary >= : bool + binary / : i64 + call local_total : i64 + i64 3 : i64 + i64 1431655763 : i64 + binary <= : bool + binary / : i64 + call local_total : i64 + i64 3 : i64 + i64 1431655763 : i64 + bool false : bool diff --git a/tests/i64-numeric-primitive.slo b/tests/i64-numeric-primitive.slo new file mode 100644 index 0000000..f297f92 --- /dev/null +++ b/tests/i64-numeric-primitive.slo @@ -0,0 +1,37 @@ +(module main) + +(fn base () -> i64 + 2147483648i64) + +(fn adjust ((value i64) (delta i64)) -> i64 + (+ value delta)) + +(fn doubled ((value i64)) -> i64 + (* value 2i64)) + +(fn local_total () -> i64 + (let offset i64 -7i64) + (adjust (- (doubled (base)) 0i64) offset)) + +(fn high_enough ((value i64)) -> bool + (if (> value 4294967280i64) + (< value 4294967300i64) + false)) + +(fn exact_i64 () -> bool + (= (local_total) 4294967289i64)) + +(fn main () -> i32 + (std.io.print_i64 (local_total)) + (if (high_enough (local_total)) 0 1)) + +(test "i64 arithmetic returns exact fixture value" + (exact_i64)) + +(test "i64 comparison works in predicates" + (high_enough (local_total))) + +(test "i64 division and ordering" + (if (>= (/ (local_total) 3i64) 1431655763i64) + (<= (/ (local_total) 3i64) 1431655763i64) + false)) diff --git a/tests/i64-numeric-primitive.surface.lower b/tests/i64-numeric-primitive.surface.lower new file mode 100644 index 0000000..d8add3f --- /dev/null +++ b/tests/i64-numeric-primitive.surface.lower @@ -0,0 +1,59 @@ +program main + fn base() -> i64 + i64 2147483648 + fn adjust(value: i64, delta: i64) -> i64 + binary + + var value + var delta + fn doubled(value: i64) -> i64 + binary * + var value + i64 2 + fn local_total() -> i64 + local let offset: i64 + i64 -7 + call adjust + binary - + call doubled + call base + i64 0 + var offset + fn high_enough(value: i64) -> bool + if + binary > + var value + i64 4294967280 + binary < + var value + i64 4294967300 + bool false + fn exact_i64() -> bool + binary = + call local_total + i64 4294967289 + fn main() -> i32 + call std.io.print_i64 + call local_total + if + call high_enough + call local_total + int 0 + int 1 + test "i64 arithmetic returns exact fixture value" + call exact_i64 + test "i64 comparison works in predicates" + call high_enough + call local_total + test "i64 division and ordering" + if + binary >= + binary / + call local_total + i64 3 + i64 1431655763 + binary <= + binary / + call local_total + i64 3 + i64 1431655763 + bool false diff --git a/tests/if-branch-type-mismatch.diag b/tests/if-branch-type-mismatch.diag new file mode 100644 index 0000000..dda50f5 --- /dev/null +++ b/tests/if-branch-type-mismatch.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code IfBranchTypeMismatch) + (message "`if` branches must have the same type") + (file "") + (span + (bytes 37 54) + (range 5 3 5 20) + ) + (expected "i32") + (found "bool") +) diff --git a/tests/if-condition-not-bool.diag b/tests/if-condition-not-bool.diag new file mode 100644 index 0000000..cf85240 --- /dev/null +++ b/tests/if-condition-not-bool.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code IfConditionNotBool) + (message "`if` condition must be bool") + (file "") + (span + (bytes 41 42) + (range 5 7 5 8) + ) + (expected "bool") + (found "i32") +) diff --git a/tests/if.checked.lower b/tests/if.checked.lower new file mode 100644 index 0000000..b7ecdb9 --- /dev/null +++ b/tests/if.checked.lower @@ -0,0 +1,28 @@ +program main + fn choose(value: i32) -> i32 + if : i32 + binary < : bool + var value : i32 + int 3 : i32 + int 10 : i32 + int 20 : i32 + fn main() -> i32 + call choose : i32 + int 2 : i32 + test "if chooses then" + binary = : bool + call choose : i32 + int 2 : i32 + int 10 : i32 + test "if chooses else" + binary = : bool + call choose : i32 + int 4 : i32 + int 20 : i32 + test "if returns bool" + if : bool + binary < : bool + int 1 : i32 + int 2 : i32 + bool true : bool + bool false : bool diff --git a/tests/if.slo b/tests/if.slo new file mode 100644 index 0000000..b94c047 --- /dev/null +++ b/tests/if.slo @@ -0,0 +1,20 @@ +(module main) + +(fn choose ((value i32)) -> i32 + (if (< value 3) + 10 + 20)) + +(test "if chooses then" + (= (choose 2) 10)) + +(test "if chooses else" + (= (choose 4) 20)) + +(test "if returns bool" + (if (< 1 2) + true + false)) + +(fn main () -> i32 + (choose 2)) diff --git a/tests/if.surface.lower b/tests/if.surface.lower new file mode 100644 index 0000000..266cfd0 --- /dev/null +++ b/tests/if.surface.lower @@ -0,0 +1,28 @@ +program main + fn choose(value: i32) -> i32 + if + binary < + var value + int 3 + int 10 + int 20 + fn main() -> i32 + call choose + int 2 + test "if chooses then" + binary = + call choose + int 2 + int 10 + test "if chooses else" + binary = + call choose + int 4 + int 20 + test "if returns bool" + if + binary < + int 1 + int 2 + bool true + bool false diff --git a/tests/index-on-non-array.diag b/tests/index-on-non-array.diag new file mode 100644 index 0000000..66f32b4 --- /dev/null +++ b/tests/index-on-non-array.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code IndexOnNonArray) + (message "`index` requires an array value") + (file "") + (span + (bytes 44 45) + (range 5 10 5 11) + ) + (expected "(array i32 N), (array i64 N), (array u32 N), (array u64 N), (array f64 N), (array bool N), (array string N), `(array KnownEnum N)`, or `(array KnownStruct N)`") + (found "i32") +) diff --git a/tests/integer-bitwise.slo b/tests/integer-bitwise.slo new file mode 100644 index 0000000..30b8022 --- /dev/null +++ b/tests/integer-bitwise.slo @@ -0,0 +1,58 @@ +(module main) + +(fn i32_and () -> i32 + (bit_and 6 3)) + +(fn i32_or () -> i32 + (bit_or 4 2)) + +(fn i32_xor () -> i32 + (bit_xor 7 3)) + +(fn i64_and () -> i64 + (bit_and 6i64 3i64)) + +(fn i64_or () -> i64 + (bit_or 4i64 2i64)) + +(fn i64_xor () -> i64 + (bit_xor 7i64 3i64)) + +(fn bitwise_ok () -> bool + (if (= (i32_and) 2) + (if (= (i32_or) 6) + (if (= (i32_xor) 4) + (if (= (i64_and) 2i64) + (if (= (i64_or) 6i64) + (= (i64_xor) 4i64) + false) + false) + false) + false) + false)) + +(fn main () -> i32 + (if (bitwise_ok) + 42 + 1)) + +(test "i32 bit and" + (= (i32_and) 2)) + +(test "i32 bit or" + (= (i32_or) 6)) + +(test "i32 bit xor" + (= (i32_xor) 4)) + +(test "i64 bit and" + (= (i64_and) 2i64)) + +(test "i64 bit or" + (= (i64_or) 6i64)) + +(test "i64 bit xor" + (= (i64_xor) 4i64)) + +(test "integer bitwise summary" + (bitwise_ok)) diff --git a/tests/integer-out-of-range.diag b/tests/integer-out-of-range.diag new file mode 100644 index 0000000..3a3dc37 --- /dev/null +++ b/tests/integer-out-of-range.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code IntegerOutOfRange) + (message "integer literal is outside the supported i32 range") + (file "") + (span + (bytes 37 47) + (range 5 3 5 13) + ) + (expected "i32") + (found "2147483648") +) diff --git a/tests/integer-remainder.slo b/tests/integer-remainder.slo new file mode 100644 index 0000000..94615f0 --- /dev/null +++ b/tests/integer-remainder.slo @@ -0,0 +1,42 @@ +(module main) + +(fn i32_remainder () -> i32 + (% 17 5)) + +(fn i32_signed_remainder () -> i32 + (% -17 5)) + +(fn i64_remainder () -> i64 + (% 42i64 5i64)) + +(fn i64_signed_remainder () -> i64 + (% -17i64 5i64)) + +(fn remainder_ok () -> bool + (if (= (i32_remainder) 2) + (if (= (i32_signed_remainder) -2) + (if (= (i64_remainder) 2i64) + (= (i64_signed_remainder) -2i64) + false) + false) + false)) + +(fn main () -> i32 + (if (remainder_ok) + 42 + 1)) + +(test "i32 remainder" + (= (i32_remainder) 2)) + +(test "i32 signed remainder" + (= (i32_signed_remainder) -2)) + +(test "i64 remainder" + (= (i64_remainder) 2i64)) + +(test "i64 signed remainder" + (= (i64_signed_remainder) -2i64)) + +(test "integer remainder summary" + (remainder_ok)) diff --git a/tests/integer-to-string.checked.lower b/tests/integer-to-string.checked.lower new file mode 100644 index 0000000..7f11748 --- /dev/null +++ b/tests/integer-to-string.checked.lower @@ -0,0 +1,73 @@ +program main + fn i32_zero_text() -> string + call std.num.i32_to_string : string + int 0 : i32 + fn i32_negative_text() -> string + call std.num.i32_to_string : string + int -7 : i32 + fn i32_high_text() -> string + call std.num.i32_to_string : string + int 2147483647 : i32 + fn i64_low_text() -> string + call std.num.i64_to_string : string + i64 -9223372036854775808 : i64 + fn i64_high_text() -> string + call std.num.i64_to_string : string + i64 9223372036854775807 : i64 + fn i64_beyond_i32_text() -> string + call std.num.i64_to_string : string + i64 2147483648 : i64 + fn main() -> i32 + call std.io.print_string : unit + call i32_zero_text : string + call std.io.print_string : unit + call i32_negative_text : string + call std.io.print_string : unit + call i32_high_text : string + call std.io.print_string : unit + call i64_low_text : string + call std.io.print_string : unit + call i64_high_text : string + call std.io.print_string : unit + call i64_beyond_i32_text : string + if : i32 + binary = : bool + call std.string.len : i32 + call i64_high_text : string + int 19 : i32 + int 0 : i32 + int 1 : i32 + test "i32 zero to string" + binary = : bool + call i32_zero_text : string + string "0" : string + test "i32 negative to string" + binary = : bool + call i32_negative_text : string + string "-7" : string + test "i32 high to string" + binary = : bool + call i32_high_text : string + string "2147483647" : string + test "i32 negative string length" + binary = : bool + call std.string.len : i32 + call i32_negative_text : string + int 2 : i32 + test "i64 low to string" + binary = : bool + call i64_low_text : string + string "-9223372036854775808" : string + test "i64 high to string" + binary = : bool + call i64_high_text : string + string "9223372036854775807" : string + test "i64 beyond i32 to string" + binary = : bool + call i64_beyond_i32_text : string + string "2147483648" : string + test "i64 low string length" + binary = : bool + call std.string.len : i32 + call i64_low_text : string + int 20 : i32 diff --git a/tests/integer-to-string.slo b/tests/integer-to-string.slo new file mode 100644 index 0000000..e73bd4d --- /dev/null +++ b/tests/integer-to-string.slo @@ -0,0 +1,54 @@ +(module main) + +(fn i32_zero_text () -> string + (std.num.i32_to_string 0)) + +(fn i32_negative_text () -> string + (std.num.i32_to_string -7)) + +(fn i32_high_text () -> string + (std.num.i32_to_string 2147483647)) + +(fn i64_low_text () -> string + (std.num.i64_to_string -9223372036854775808i64)) + +(fn i64_high_text () -> string + (std.num.i64_to_string 9223372036854775807i64)) + +(fn i64_beyond_i32_text () -> string + (std.num.i64_to_string 2147483648i64)) + +(test "i32 zero to string" + (= (i32_zero_text) "0")) + +(test "i32 negative to string" + (= (i32_negative_text) "-7")) + +(test "i32 high to string" + (= (i32_high_text) "2147483647")) + +(test "i32 negative string length" + (= (std.string.len (i32_negative_text)) 2)) + +(test "i64 low to string" + (= (i64_low_text) "-9223372036854775808")) + +(test "i64 high to string" + (= (i64_high_text) "9223372036854775807")) + +(test "i64 beyond i32 to string" + (= (i64_beyond_i32_text) "2147483648")) + +(test "i64 low string length" + (= (std.string.len (i64_low_text)) 20)) + +(fn main () -> i32 + (std.io.print_string (i32_zero_text)) + (std.io.print_string (i32_negative_text)) + (std.io.print_string (i32_high_text)) + (std.io.print_string (i64_low_text)) + (std.io.print_string (i64_high_text)) + (std.io.print_string (i64_beyond_i32_text)) + (if (= (std.string.len (i64_high_text)) 19) + 0 + 1)) diff --git a/tests/integer-to-string.surface.lower b/tests/integer-to-string.surface.lower new file mode 100644 index 0000000..0795906 --- /dev/null +++ b/tests/integer-to-string.surface.lower @@ -0,0 +1,73 @@ +program main + fn i32_zero_text() -> string + call std.num.i32_to_string + int 0 + fn i32_negative_text() -> string + call std.num.i32_to_string + int -7 + fn i32_high_text() -> string + call std.num.i32_to_string + int 2147483647 + fn i64_low_text() -> string + call std.num.i64_to_string + i64 -9223372036854775808 + fn i64_high_text() -> string + call std.num.i64_to_string + i64 9223372036854775807 + fn i64_beyond_i32_text() -> string + call std.num.i64_to_string + i64 2147483648 + fn main() -> i32 + call std.io.print_string + call i32_zero_text + call std.io.print_string + call i32_negative_text + call std.io.print_string + call i32_high_text + call std.io.print_string + call i64_low_text + call std.io.print_string + call i64_high_text + call std.io.print_string + call i64_beyond_i32_text + if + binary = + call std.string.len + call i64_high_text + int 19 + int 0 + int 1 + test "i32 zero to string" + binary = + call i32_zero_text + string "0" + test "i32 negative to string" + binary = + call i32_negative_text + string "-7" + test "i32 high to string" + binary = + call i32_high_text + string "2147483647" + test "i32 negative string length" + binary = + call std.string.len + call i32_negative_text + int 2 + test "i64 low to string" + binary = + call i64_low_text + string "-9223372036854775808" + test "i64 high to string" + binary = + call i64_high_text + string "9223372036854775807" + test "i64 beyond i32 to string" + binary = + call i64_beyond_i32_text + string "2147483648" + test "i64 low string length" + binary = + call std.string.len + call i64_low_text + int 20 diff --git a/tests/invalid-i64-literal.diag b/tests/invalid-i64-literal.diag new file mode 100644 index 0000000..257f82f --- /dev/null +++ b/tests/invalid-i64-literal.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code InvalidI64Literal) + (message "i64 literal must use signed decimal digits before the `i64` suffix") + (file "") + (span + (bytes 37 43) + (range 5 3 5 9) + ) + (expected "signed decimal digits followed by `i64`") + (found "1.0i64") + (hint "use forms like `42i64` or `-7i64`; other i64 literal forms remain deferred") +) diff --git a/tests/invalid-leading-plus-i64-literal.diag b/tests/invalid-leading-plus-i64-literal.diag new file mode 100644 index 0000000..f8cf516 --- /dev/null +++ b/tests/invalid-leading-plus-i64-literal.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code InvalidI64Literal) + (message "i64 literal must use signed decimal digits before the `i64` suffix") + (file "") + (span + (bytes 37 42) + (range 5 3 5 8) + ) + (expected "signed decimal digits followed by `i64`") + (found "+1i64") + (hint "use forms like `42i64` or `-7i64`; other i64 literal forms remain deferred") +) diff --git a/tests/invalid-sign-only-i64-literal.diag b/tests/invalid-sign-only-i64-literal.diag new file mode 100644 index 0000000..380e7b8 --- /dev/null +++ b/tests/invalid-sign-only-i64-literal.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code InvalidI64Literal) + (message "i64 literal must use signed decimal digits before the `i64` suffix") + (file "") + (span + (bytes 37 41) + (range 5 3 5 7) + ) + (expected "signed decimal digits followed by `i64`") + (found "-i64") + (hint "use forms like `42i64` or `-7i64`; other i64 literal forms remain deferred") +) diff --git a/tests/local-redeclares-parameter.diag b/tests/local-redeclares-parameter.diag new file mode 100644 index 0000000..12049cf --- /dev/null +++ b/tests/local-redeclares-parameter.diag @@ -0,0 +1,13 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code LocalRedeclaresParameter) + (message "local `value` redeclares a parameter") + (file "") + (span + (bytes 51 56) + (range 5 8 5 13) + ) + (hint "choose a local name distinct from parameters") +) diff --git a/tests/local-shadows-function.diag b/tests/local-shadows-function.diag new file mode 100644 index 0000000..e7ee2d2 --- /dev/null +++ b/tests/local-shadows-function.diag @@ -0,0 +1,13 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code LocalShadowsCallable) + (message "local `helper` conflicts with a function or compiler intrinsic") + (file "") + (span + (bytes 69 75) + (range 8 8 8 14) + ) + (hint "choose a local name distinct from functions and compiler intrinsics") +) diff --git a/tests/local-shadows-intrinsic.diag b/tests/local-shadows-intrinsic.diag new file mode 100644 index 0000000..988aaf2 --- /dev/null +++ b/tests/local-shadows-intrinsic.diag @@ -0,0 +1,13 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code LocalShadowsCallable) + (message "local `print_i32` conflicts with a function or compiler intrinsic") + (file "") + (span + (bytes 42 51) + (range 5 8 5 17) + ) + (hint "choose a local name distinct from functions and compiler intrinsics") +) diff --git a/tests/local-variables.checked.lower b/tests/local-variables.checked.lower new file mode 100644 index 0000000..b031b7c --- /dev/null +++ b/tests/local-variables.checked.lower @@ -0,0 +1,98 @@ +program main + fn add_local(a: i32) -> i32 + local let one : unit + int 1 : i32 + local var total : unit + binary + : i32 + var a : i32 + var one : i32 + set total : unit + binary + : i32 + var total : i32 + int 1 : i32 + var total : i32 + fn keep_flag(flag: bool) -> bool + local let local_flag : unit + var flag : bool + var local_flag : bool + fn flip_flag(flag: bool) -> bool + local var current : unit + var flag : bool + set current : unit + if : bool + var current : bool + bool false : bool + bool true : bool + var current : bool + fn add_wide_local(base: i64) -> i64 + local var count : unit + var base : i64 + set count : unit + binary + : i64 + var count : i64 + i64 2 : i64 + var count : i64 + fn add_ratio_local(base: f64) -> f64 + local var amount : unit + var base : f64 + set amount : unit + binary + : f64 + var amount : f64 + float 0.5 : f64 + var amount : f64 + fn main() -> i32 + local var flag : unit + bool false : bool + set flag : unit + call flip_flag : bool + var flag : bool + if : i32 + var flag : bool + call add_local : i32 + int 2 : i32 + int 1 : i32 + test "locals work" + local let base : unit + int 2 : i32 + local var value : unit + call add_local : i32 + var base : i32 + set value : unit + binary + : i32 + var value : i32 + int 1 : i32 + binary = : bool + var value : i32 + int 5 : i32 + test "bool let locals work" + local let expected : unit + bool true : bool + local let actual : unit + call keep_flag : bool + var expected : bool + var actual : bool + test "bool let locals preserve false" + if : bool + call keep_flag : bool + bool false : bool + bool false : bool + bool true : bool + test "bool var set flips true" + if : bool + call flip_flag : bool + bool true : bool + bool false : bool + bool true : bool + test "bool var set flips false" + call flip_flag : bool + bool false : bool + test "i64 var set works" + binary = : bool + call add_wide_local : i64 + i64 40 : i64 + i64 42 : i64 + test "f64 var set works" + binary = : bool + call add_ratio_local : f64 + float 41.5 : f64 + float 42 : f64 diff --git a/tests/local-variables.slo b/tests/local-variables.slo new file mode 100644 index 0000000..441eab9 --- /dev/null +++ b/tests/local-variables.slo @@ -0,0 +1,65 @@ +(module main) + +(fn add_local ((a i32)) -> i32 + (let one i32 1) + (var total i32 (+ a one)) + (set total (+ total 1)) + total) + +(fn keep_flag ((flag bool)) -> bool + (let local_flag bool flag) + local_flag) + +(fn flip_flag ((flag bool)) -> bool + (var current bool flag) + (set current (if current + false + true)) + current) + +(fn add_wide_local ((base i64)) -> i64 + (var count i64 base) + (set count (+ count 2i64)) + count) + +(fn add_ratio_local ((base f64)) -> f64 + (var amount f64 base) + (set amount (+ amount 0.5)) + amount) + +(test "locals work" + (let base i32 2) + (var value i32 (add_local base)) + (set value (+ value 1)) + (= value 5)) + +(test "bool let locals work" + (let expected bool true) + (let actual bool (keep_flag expected)) + actual) + +(test "bool let locals preserve false" + (if (keep_flag false) + false + true)) + +(test "bool var set flips true" + (if (flip_flag true) + false + true)) + +(test "bool var set flips false" + (flip_flag false)) + +(test "i64 var set works" + (= (add_wide_local 40i64) 42i64)) + +(test "f64 var set works" + (= (add_ratio_local 41.5) 42.0)) + +(fn main () -> i32 + (var flag bool false) + (set flag (flip_flag flag)) + (if flag + (add_local 2) + 1)) diff --git a/tests/local-variables.surface.lower b/tests/local-variables.surface.lower new file mode 100644 index 0000000..15534cf --- /dev/null +++ b/tests/local-variables.surface.lower @@ -0,0 +1,98 @@ +program main + fn add_local(a: i32) -> i32 + local let one: i32 + int 1 + local var total: i32 + binary + + var a + var one + set total + binary + + var total + int 1 + var total + fn keep_flag(flag: bool) -> bool + local let local_flag: bool + var flag + var local_flag + fn flip_flag(flag: bool) -> bool + local var current: bool + var flag + set current + if + var current + bool false + bool true + var current + fn add_wide_local(base: i64) -> i64 + local var count: i64 + var base + set count + binary + + var count + i64 2 + var count + fn add_ratio_local(base: f64) -> f64 + local var amount: f64 + var base + set amount + binary + + var amount + float 0.5 + var amount + fn main() -> i32 + local var flag: bool + bool false + set flag + call flip_flag + var flag + if + var flag + call add_local + int 2 + int 1 + test "locals work" + local let base: i32 + int 2 + local var value: i32 + call add_local + var base + set value + binary + + var value + int 1 + binary = + var value + int 5 + test "bool let locals work" + local let expected: bool + bool true + local let actual: bool + call keep_flag + var expected + var actual + test "bool let locals preserve false" + if + call keep_flag + bool false + bool false + bool true + test "bool var set flips true" + if + call flip_flag + bool true + bool false + bool true + test "bool var set flips false" + call flip_flag + bool false + test "i64 var set works" + binary = + call add_wide_local + i64 40 + i64 42 + test "f64 var set works" + binary = + call add_ratio_local + float 41.5 + float 42 diff --git a/tests/malformed-if.diag b/tests/malformed-if.diag new file mode 100644 index 0000000..ea23501 --- /dev/null +++ b/tests/malformed-if.diag @@ -0,0 +1,13 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code MalformedIfForm) + (message "`if` expects condition, then expression, else expression") + (file "") + (span + (bytes 37 48) + (range 5 3 5 14) + ) + (hint "use `(if condition then-expression else-expression)`") +) diff --git a/tests/malformed-match-pattern.diag b/tests/malformed-match-pattern.diag new file mode 100644 index 0000000..2aebb6c --- /dev/null +++ b/tests/malformed-match-pattern.diag @@ -0,0 +1,13 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code MalformedMatchPattern) + (message "`some` match pattern requires one payload binding") + (file "") + (span + (bytes 75 81) + (range 6 6 6 12) + ) + (expected "(some binding)") +) diff --git a/tests/malformed-option-constructor.diag b/tests/malformed-option-constructor.diag new file mode 100644 index 0000000..f891bc9 --- /dev/null +++ b/tests/malformed-option-constructor.diag @@ -0,0 +1,13 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code MalformedOptionConstructor) + (message "`some` constructor must be `(some i32 value)`") + (file "") + (span + (bytes 45 55) + (range 5 3 5 13) + ) + (hint "use `(some i32 value)`") +) diff --git a/tests/malformed-option-unwrap.diag b/tests/malformed-option-unwrap.diag new file mode 100644 index 0000000..7dd89f9 --- /dev/null +++ b/tests/malformed-option-unwrap.diag @@ -0,0 +1,13 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code MalformedUnwrapForm) + (message "`unwrap_some` expects exactly one value") + (file "") + (span + (bytes 37 50) + (range 5 3 5 16) + ) + (hint "use `(unwrap_some value)`") +) diff --git a/tests/malformed-result-constructor.diag b/tests/malformed-result-constructor.diag new file mode 100644 index 0000000..214ec51 --- /dev/null +++ b/tests/malformed-result-constructor.diag @@ -0,0 +1,13 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code MalformedResultConstructor) + (message "`ok` constructor must be `(ok i32 i32 value)`") + (file "") + (span + (bytes 49 61) + (range 5 3 5 15) + ) + (hint "use `(ok i32 i32 value)`") +) diff --git a/tests/malformed-result-err-unwrap.diag b/tests/malformed-result-err-unwrap.diag new file mode 100644 index 0000000..079d42b --- /dev/null +++ b/tests/malformed-result-err-unwrap.diag @@ -0,0 +1,13 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code MalformedUnwrapForm) + (message "`unwrap_err` expects exactly one value") + (file "") + (span + (bytes 37 49) + (range 5 3 5 15) + ) + (hint "use `(unwrap_err value)`") +) diff --git a/tests/malformed-result-ok-unwrap.diag b/tests/malformed-result-ok-unwrap.diag new file mode 100644 index 0000000..9c0d470 --- /dev/null +++ b/tests/malformed-result-ok-unwrap.diag @@ -0,0 +1,13 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code MalformedUnwrapForm) + (message "`unwrap_ok` expects exactly one value") + (file "") + (span + (bytes 37 48) + (range 5 3 5 14) + ) + (hint "use `(unwrap_ok value)`") +) diff --git a/tests/malformed-unsafe-form.diag b/tests/malformed-unsafe-form.diag new file mode 100644 index 0000000..776c5d3 --- /dev/null +++ b/tests/malformed-unsafe-form.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code MalformedUnsafeForm) + (message "`unsafe` block must contain a final expression") + (file "") + (span + (bytes 37 45) + (range 5 3 5 11) + ) + (expected "(unsafe body-form... final-expression)") + (hint "use `(unsafe expression)` or add supported body forms before the final expression") +) diff --git a/tests/malformed-while.diag b/tests/malformed-while.diag new file mode 100644 index 0000000..473a87b --- /dev/null +++ b/tests/malformed-while.diag @@ -0,0 +1,13 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code MalformedWhileForm) + (message "`while` must have a condition") + (file "") + (span + (bytes 37 44) + (range 5 3 5 10) + ) + (hint "use `(while condition body...)`") +) diff --git a/tests/match-arm-type-mismatch.diag b/tests/match-arm-type-mismatch.diag new file mode 100644 index 0000000..9c17fc6 --- /dev/null +++ b/tests/match-arm-type-mismatch.diag @@ -0,0 +1,22 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code MatchArmTypeMismatch) + (message "match arm final expressions must have the same type") + (file "") + (span + (bytes 123 128) + (range 9 7 9 12) + ) + (expected "i32") + (found "bool") + (related + (span + (file "") + (bytes 96 103) + (range 7 7 7 14) + (message "first arm result type") + ) + ) +) diff --git a/tests/match-binding-collision.diag b/tests/match-binding-collision.diag new file mode 100644 index 0000000..13e9472 --- /dev/null +++ b/tests/match-binding-collision.diag @@ -0,0 +1,20 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code MatchBindingCollision) + (message "match payload binding `payload` collides with a visible name") + (file "") + (span + (bytes 95 102) + (range 6 12 6 19) + ) + (related + (span + (file "") + (bytes 48 55) + (range 4 33 4 40) + (message "visible binding") + ) + ) +) diff --git a/tests/match-subject-type-mismatch.diag b/tests/match-subject-type-mismatch.diag new file mode 100644 index 0000000..98d9bc1 --- /dev/null +++ b/tests/match-subject-type-mismatch.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code MatchSubjectTypeMismatch) + (message "match subject must be `(option i32)`, `(option i64)`, `(option u32)`, `(option u64)`, `(option f64)`, `(option bool)`, `(option string)`, `(result i32 i32)`, `(result i64 i32)`, `(result u32 i32)`, `(result u64 i32)`, `(result f64 i32)`, `(result bool i32)`, `(result string i32)`, or a known enum") + (file "") + (span + (bytes 44 45) + (range 5 10 5 11) + ) + (expected "(option i32), (option i64), (option u32), (option u64), (option f64), (option bool), (option string), (result i32 i32), (result i64 i32), (result u32 i32), (result u64 i32), (result f64 i32), (result bool i32), (result string i32), or known enum") + (found "i32") +) diff --git a/tests/nested-local-declaration.diag b/tests/nested-local-declaration.diag new file mode 100644 index 0000000..92dffe9 --- /dev/null +++ b/tests/nested-local-declaration.diag @@ -0,0 +1,13 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code LocalDeclarationNotAllowed) + (message "local declarations are allowed only as body forms") + (file "") + (span + (bytes 40 53) + (range 5 6 5 19) + ) + (hint "move the declaration before the final body expression") +) diff --git a/tests/nested-while.diag b/tests/nested-while.diag new file mode 100644 index 0000000..dc21e0c --- /dev/null +++ b/tests/nested-while.diag @@ -0,0 +1,13 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code NestedWhileUnsupported) + (message "nested `while` loops are not supported in first-pass loop bodies") + (file "") + (span + (bytes 53 86) + (range 6 5 7 21) + ) + (hint "keep first-pass loop bodies flat") +) diff --git a/tests/non-exhaustive-match.diag b/tests/non-exhaustive-match.diag new file mode 100644 index 0000000..bcee2a9 --- /dev/null +++ b/tests/non-exhaustive-match.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code NonExhaustiveMatch) + (message "match is missing `none` arm(s)") + (file "") + (span + (bytes 57 105) + (range 5 3 7 16) + ) + (expected "some and none") + (found "some") +) diff --git a/tests/numeric-struct-fields.checked.lower b/tests/numeric-struct-fields.checked.lower new file mode 100644 index 0000000..57a61bc --- /dev/null +++ b/tests/numeric-struct-fields.checked.lower @@ -0,0 +1,134 @@ +program main + struct NumericRecord + field wide: i64 + field ratio: f64 + fn make_record(wide: i64, ratio: f64) -> NumericRecord + construct NumericRecord : NumericRecord + field wide + var wide : i64 + field ratio + var ratio : f64 + fn literal_record() -> NumericRecord + construct NumericRecord : NumericRecord + field wide + i64 2147483648 : i64 + field ratio + float 3.5 : f64 + fn echo_record(record: NumericRecord) -> NumericRecord + var record : NumericRecord + fn local_record(wide: i64, ratio: f64) -> NumericRecord + local let record : unit + call make_record : NumericRecord + var wide : i64 + var ratio : f64 + call echo_record : NumericRecord + var record : NumericRecord + fn record_wide(record: NumericRecord) -> i64 + field-access wide : i64 + var record : NumericRecord + fn record_ratio(record: NumericRecord) -> f64 + field-access ratio : f64 + var record : NumericRecord + fn wide_total(record: NumericRecord) -> i64 + binary + : i64 + field-access wide : i64 + var record : NumericRecord + i64 10 : i64 + fn ratio_total(record: NumericRecord) -> f64 + binary + : f64 + field-access ratio : f64 + var record : NumericRecord + float 1.5 : f64 + fn wide_in_range(record: NumericRecord) -> bool + if : bool + binary > : bool + field-access wide : i64 + var record : NumericRecord + i64 2147483640 : i64 + binary < : bool + field-access wide : i64 + var record : NumericRecord + i64 2147483660 : i64 + bool false : bool + fn ratio_in_range(record: NumericRecord) -> bool + if : bool + binary > : bool + field-access ratio : f64 + var record : NumericRecord + float 3 : f64 + binary < : bool + field-access ratio : f64 + var record : NumericRecord + float 4 : f64 + bool false : bool + fn wide_text(record: NumericRecord) -> string + call std.num.i64_to_string : string + field-access wide : i64 + var record : NumericRecord + fn ratio_text(record: NumericRecord) -> string + call std.num.f64_to_string : string + field-access ratio : f64 + var record : NumericRecord + fn main() -> i32 + call std.io.print_i64 : unit + call record_wide : i64 + call literal_record : NumericRecord + call std.io.print_f64 : unit + call record_ratio : f64 + call literal_record : NumericRecord + if : i32 + call wide_in_range : bool + call local_record : NumericRecord + i64 2147483648 : i64 + float 3.5 : f64 + if : i32 + call ratio_in_range : bool + call local_record : NumericRecord + i64 2147483648 : i64 + float 3.5 : f64 + int 0 : i32 + int 1 : i32 + int 1 : i32 + test "numeric struct i64 field access" + binary = : bool + call record_wide : i64 + call literal_record : NumericRecord + i64 2147483648 : i64 + test "numeric struct f64 field access" + binary = : bool + call record_ratio : f64 + call literal_record : NumericRecord + float 3.5 : f64 + test "numeric struct i64 arithmetic after access" + binary = : bool + call wide_total : i64 + call literal_record : NumericRecord + i64 2147483658 : i64 + test "numeric struct f64 arithmetic after access" + binary = : bool + call ratio_total : f64 + call literal_record : NumericRecord + float 5 : f64 + test "numeric struct i64 comparison after access" + call wide_in_range : bool + call literal_record : NumericRecord + test "numeric struct f64 comparison after access" + call ratio_in_range : bool + call literal_record : NumericRecord + test "numeric struct local param return call flow" + binary = : bool + call wide_total : i64 + call local_record : NumericRecord + i64 2147483648 : i64 + float 3.5 : f64 + i64 2147483658 : i64 + test "numeric struct i64 format after access" + binary = : bool + call wide_text : string + call literal_record : NumericRecord + string "2147483648" : string + test "numeric struct f64 format after access" + binary = : bool + call ratio_text : string + call literal_record : NumericRecord + string "3.5" : string diff --git a/tests/numeric-struct-fields.slo b/tests/numeric-struct-fields.slo new file mode 100644 index 0000000..6034d90 --- /dev/null +++ b/tests/numeric-struct-fields.slo @@ -0,0 +1,82 @@ +(module main) + +(struct NumericRecord + (wide i64) + (ratio f64)) + +(fn make_record ((wide i64) (ratio f64)) -> NumericRecord + (NumericRecord (wide wide) (ratio ratio))) + +(fn literal_record () -> NumericRecord + (NumericRecord (wide 2147483648i64) (ratio 3.5))) + +(fn echo_record ((record NumericRecord)) -> NumericRecord + record) + +(fn local_record ((wide i64) (ratio f64)) -> NumericRecord + (let record NumericRecord (make_record wide ratio)) + (echo_record record)) + +(fn record_wide ((record NumericRecord)) -> i64 + (. record wide)) + +(fn record_ratio ((record NumericRecord)) -> f64 + (. record ratio)) + +(fn wide_total ((record NumericRecord)) -> i64 + (+ (. record wide) 10i64)) + +(fn ratio_total ((record NumericRecord)) -> f64 + (+ (. record ratio) 1.5)) + +(fn wide_in_range ((record NumericRecord)) -> bool + (if (> (. record wide) 2147483640i64) + (< (. record wide) 2147483660i64) + false)) + +(fn ratio_in_range ((record NumericRecord)) -> bool + (if (> (. record ratio) 3.0) + (< (. record ratio) 4.0) + false)) + +(fn wide_text ((record NumericRecord)) -> string + (std.num.i64_to_string (. record wide))) + +(fn ratio_text ((record NumericRecord)) -> string + (std.num.f64_to_string (. record ratio))) + +(test "numeric struct i64 field access" + (= (record_wide (literal_record)) 2147483648i64)) + +(test "numeric struct f64 field access" + (= (record_ratio (literal_record)) 3.5)) + +(test "numeric struct i64 arithmetic after access" + (= (wide_total (literal_record)) 2147483658i64)) + +(test "numeric struct f64 arithmetic after access" + (= (ratio_total (literal_record)) 5.0)) + +(test "numeric struct i64 comparison after access" + (wide_in_range (literal_record))) + +(test "numeric struct f64 comparison after access" + (ratio_in_range (literal_record))) + +(test "numeric struct local param return call flow" + (= (wide_total (local_record 2147483648i64 3.5)) 2147483658i64)) + +(test "numeric struct i64 format after access" + (= (wide_text (literal_record)) "2147483648")) + +(test "numeric struct f64 format after access" + (= (ratio_text (literal_record)) "3.5")) + +(fn main () -> i32 + (std.io.print_i64 (record_wide (literal_record))) + (std.io.print_f64 (record_ratio (literal_record))) + (if (wide_in_range (local_record 2147483648i64 3.5)) + (if (ratio_in_range (local_record 2147483648i64 3.5)) + 0 + 1) + 1)) diff --git a/tests/numeric-struct-fields.surface.lower b/tests/numeric-struct-fields.surface.lower new file mode 100644 index 0000000..289b11b --- /dev/null +++ b/tests/numeric-struct-fields.surface.lower @@ -0,0 +1,134 @@ +program main + struct NumericRecord + field wide: i64 + field ratio: f64 + fn make_record(wide: i64, ratio: f64) -> NumericRecord + construct NumericRecord + field wide + var wide + field ratio + var ratio + fn literal_record() -> NumericRecord + construct NumericRecord + field wide + i64 2147483648 + field ratio + float 3.5 + fn echo_record(record: NumericRecord) -> NumericRecord + var record + fn local_record(wide: i64, ratio: f64) -> NumericRecord + local let record: NumericRecord + call make_record + var wide + var ratio + call echo_record + var record + fn record_wide(record: NumericRecord) -> i64 + field-access wide + var record + fn record_ratio(record: NumericRecord) -> f64 + field-access ratio + var record + fn wide_total(record: NumericRecord) -> i64 + binary + + field-access wide + var record + i64 10 + fn ratio_total(record: NumericRecord) -> f64 + binary + + field-access ratio + var record + float 1.5 + fn wide_in_range(record: NumericRecord) -> bool + if + binary > + field-access wide + var record + i64 2147483640 + binary < + field-access wide + var record + i64 2147483660 + bool false + fn ratio_in_range(record: NumericRecord) -> bool + if + binary > + field-access ratio + var record + float 3 + binary < + field-access ratio + var record + float 4 + bool false + fn wide_text(record: NumericRecord) -> string + call std.num.i64_to_string + field-access wide + var record + fn ratio_text(record: NumericRecord) -> string + call std.num.f64_to_string + field-access ratio + var record + fn main() -> i32 + call std.io.print_i64 + call record_wide + call literal_record + call std.io.print_f64 + call record_ratio + call literal_record + if + call wide_in_range + call local_record + i64 2147483648 + float 3.5 + if + call ratio_in_range + call local_record + i64 2147483648 + float 3.5 + int 0 + int 1 + int 1 + test "numeric struct i64 field access" + binary = + call record_wide + call literal_record + i64 2147483648 + test "numeric struct f64 field access" + binary = + call record_ratio + call literal_record + float 3.5 + test "numeric struct i64 arithmetic after access" + binary = + call wide_total + call literal_record + i64 2147483658 + test "numeric struct f64 arithmetic after access" + binary = + call ratio_total + call literal_record + float 5 + test "numeric struct i64 comparison after access" + call wide_in_range + call literal_record + test "numeric struct f64 comparison after access" + call ratio_in_range + call literal_record + test "numeric struct local param return call flow" + binary = + call wide_total + call local_record + i64 2147483648 + float 3.5 + i64 2147483658 + test "numeric struct i64 format after access" + binary = + call wide_text + call literal_record + string "2147483648" + test "numeric struct f64 format after access" + binary = + call ratio_text + call literal_record + string "3.5" diff --git a/tests/numeric-widening-conversions.checked.lower b/tests/numeric-widening-conversions.checked.lower new file mode 100644 index 0000000..322e3e6 --- /dev/null +++ b/tests/numeric-widening-conversions.checked.lower @@ -0,0 +1,45 @@ +program main + fn base_i64() -> i64 + call std.num.i32_to_i64 : i64 + int 2147483647 : i32 + fn widened_i64() -> i64 + binary + : i64 + call base_i64 : i64 + i64 1 : i64 + fn half_step() -> f64 + binary + : f64 + call std.num.i32_to_f64 : f64 + int 5 : i32 + float 0.5 : f64 + fn wide_as_f64() -> f64 + call std.num.i64_to_f64 : f64 + call widened_i64 : i64 + fn main() -> i32 + call std.io.print_i64 : unit + call widened_i64 : i64 + call std.io.print_f64 : unit + call wide_as_f64 : f64 + if : i32 + binary = : bool + call widened_i64 : i64 + i64 2147483648 : i64 + int 0 : i32 + int 1 : i32 + test "i32 widens to i64" + binary = : bool + call std.num.i32_to_i64 : i64 + int 42 : i32 + i64 42 : i64 + test "i32 to i64 feeds i64 arithmetic" + binary = : bool + call widened_i64 : i64 + i64 2147483648 : i64 + test "i32 widens to f64" + binary = : bool + call std.num.i32_to_f64 : f64 + int 42 : i32 + float 42 : f64 + test "i64 widens to f64" + binary = : bool + call wide_as_f64 : f64 + float 2147483648 : f64 diff --git a/tests/numeric-widening-conversions.slo b/tests/numeric-widening-conversions.slo new file mode 100644 index 0000000..8efc10c --- /dev/null +++ b/tests/numeric-widening-conversions.slo @@ -0,0 +1,32 @@ +(module main) + +(fn base_i64 () -> i64 + (std.num.i32_to_i64 2147483647)) + +(fn widened_i64 () -> i64 + (+ (base_i64) 1i64)) + +(fn half_step () -> f64 + (+ (std.num.i32_to_f64 5) 0.5)) + +(fn wide_as_f64 () -> f64 + (std.num.i64_to_f64 (widened_i64))) + +(fn main () -> i32 + (std.io.print_i64 (widened_i64)) + (std.io.print_f64 (wide_as_f64)) + (if (= (widened_i64) 2147483648i64) + 0 + 1)) + +(test "i32 widens to i64" + (= (std.num.i32_to_i64 42) 42i64)) + +(test "i32 to i64 feeds i64 arithmetic" + (= (widened_i64) 2147483648i64)) + +(test "i32 widens to f64" + (= (std.num.i32_to_f64 42) 42.0)) + +(test "i64 widens to f64" + (= (wide_as_f64) 2147483648.0)) diff --git a/tests/numeric-widening-conversions.surface.lower b/tests/numeric-widening-conversions.surface.lower new file mode 100644 index 0000000..a630eec --- /dev/null +++ b/tests/numeric-widening-conversions.surface.lower @@ -0,0 +1,45 @@ +program main + fn base_i64() -> i64 + call std.num.i32_to_i64 + int 2147483647 + fn widened_i64() -> i64 + binary + + call base_i64 + i64 1 + fn half_step() -> f64 + binary + + call std.num.i32_to_f64 + int 5 + float 0.5 + fn wide_as_f64() -> f64 + call std.num.i64_to_f64 + call widened_i64 + fn main() -> i32 + call std.io.print_i64 + call widened_i64 + call std.io.print_f64 + call wide_as_f64 + if + binary = + call widened_i64 + i64 2147483648 + int 0 + int 1 + test "i32 widens to i64" + binary = + call std.num.i32_to_i64 + int 42 + i64 42 + test "i32 to i64 feeds i64 arithmetic" + binary = + call widened_i64 + i64 2147483648 + test "i32 widens to f64" + binary = + call std.num.i32_to_f64 + int 42 + float 42 + test "i64 widens to f64" + binary = + call wide_as_f64 + float 2147483648 diff --git a/tests/option-constructor-type-mismatch.diag b/tests/option-constructor-type-mismatch.diag new file mode 100644 index 0000000..3f49c16 --- /dev/null +++ b/tests/option-constructor-type-mismatch.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code TypeMismatch) + (message "option `some` value has the wrong type") + (file "") + (span + (bytes 55 59) + (range 5 13 5 17) + ) + (expected "i32") + (found "bool") +) diff --git a/tests/option-observation-non-option.diag b/tests/option-observation-non-option.diag new file mode 100644 index 0000000..fe6bbb1 --- /dev/null +++ b/tests/option-observation-non-option.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code OptionObservationTypeMismatch) + (message "`is_some` requires an `(option i32)`, `(option i64)`, `(option f64)`, `(option bool)`, or `(option string)` value") + (file "") + (span + (bytes 47 48) + (range 5 12 5 13) + ) + (expected "(option i32), (option i64), (option f64), (option bool), or (option string)") + (found "i32") +) diff --git a/tests/option-result-flow.checked.lower b/tests/option-result-flow.checked.lower new file mode 100644 index 0000000..228846e --- /dev/null +++ b/tests/option-result-flow.checked.lower @@ -0,0 +1,197 @@ +program main + fn maybe_value(value: i32) -> (option i32) + some : (option i32) + var value : i32 + fn maybe_empty() -> (option i32) + none : (option i32) + fn maybe_wide_value(value: i64) -> (option i64) + some : (option i64) + var value : i64 + fn maybe_wide_empty() -> (option i64) + none : (option i64) + fn maybe_float_value(value: f64) -> (option f64) + some : (option f64) + var value : f64 + fn maybe_float_empty() -> (option f64) + none : (option f64) + fn maybe_flag_value(value: bool) -> (option bool) + some : (option bool) + var value : bool + fn maybe_flag_empty() -> (option bool) + none : (option bool) + fn maybe_string_value(value: string) -> (option string) + some : (option string) + var value : string + fn maybe_string_empty() -> (option string) + none : (option string) + fn option_score(value: (option i32)) -> i32 + if : i32 + is_some : bool + var value : (option i32) + int 1 : i32 + int 0 : i32 + fn option_empty_score(value: (option i32)) -> i32 + if : i32 + is_none : bool + var value : (option i32) + int 1 : i32 + int 0 : i32 + fn option_wide_score(value: (option i64)) -> i32 + if : i32 + is_some : bool + var value : (option i64) + int 1 : i32 + int 0 : i32 + fn option_wide_empty_score(value: (option i64)) -> i32 + if : i32 + is_none : bool + var value : (option i64) + int 1 : i32 + int 0 : i32 + fn option_float_score(value: (option f64)) -> i32 + if : i32 + is_some : bool + var value : (option f64) + int 1 : i32 + int 0 : i32 + fn option_float_empty_score(value: (option f64)) -> i32 + if : i32 + is_none : bool + var value : (option f64) + int 1 : i32 + int 0 : i32 + fn option_flag_score(value: (option bool)) -> i32 + if : i32 + is_some : bool + var value : (option bool) + int 1 : i32 + int 0 : i32 + fn option_flag_empty_score(value: (option bool)) -> i32 + if : i32 + is_none : bool + var value : (option bool) + int 1 : i32 + int 0 : i32 + fn option_string_score(value: (option string)) -> i32 + if : i32 + is_some : bool + var value : (option string) + int 1 : i32 + int 0 : i32 + fn option_string_empty_score(value: (option string)) -> i32 + if : i32 + is_none : bool + var value : (option string) + int 1 : i32 + int 0 : i32 + fn result_ok_value(value: i32) -> (result i32 i32) + ok : (result i32 i32) + var value : i32 + fn result_err_value(code: i32) -> (result i32 i32) + err : (result i32 i32) + var code : i32 + fn result_success_score(value: (result i32 i32)) -> i32 + if : i32 + is_ok : bool + var value : (result i32 i32) + int 1 : i32 + int 0 : i32 + fn result_failure_score(value: (result i32 i32)) -> i32 + if : i32 + is_err : bool + var value : (result i32 i32) + int 1 : i32 + int 0 : i32 + fn option_local_flow() -> i32 + local let value : unit + call maybe_value : (option i32) + int 42 : i32 + call option_score : i32 + var value : (option i32) + fn option_wide_local_flow() -> i32 + local let value : unit + call maybe_wide_value : (option i64) + i64 2147483648 : i64 + call option_wide_score : i32 + var value : (option i64) + fn option_float_local_flow() -> i32 + local let value : unit + call maybe_float_value : (option f64) + float 42.5 : f64 + call option_float_score : i32 + var value : (option f64) + fn option_flag_local_flow() -> i32 + local let value : unit + call maybe_flag_value : (option bool) + bool true : bool + call option_flag_score : i32 + var value : (option bool) + fn option_string_local_flow() -> i32 + local let value : unit + call maybe_string_value : (option string) + string "slovo" : string + call option_string_score : i32 + var value : (option string) + fn result_local_flow() -> i32 + local let value : unit + call result_err_value : (result i32 i32) + int 7 : i32 + call result_failure_score : i32 + var value : (result i32 i32) + fn main() -> i32 + int 0 : i32 + test "option local value flow" + binary = : bool + call option_local_flow : i32 + int 1 : i32 + test "option call observation" + binary = : bool + call option_empty_score : i32 + call maybe_empty : (option i32) + int 1 : i32 + test "option i64 local value flow" + binary = : bool + call option_wide_local_flow : i32 + int 1 : i32 + test "option i64 call observation" + binary = : bool + call option_wide_empty_score : i32 + call maybe_wide_empty : (option i64) + int 1 : i32 + test "option f64 local value flow" + binary = : bool + call option_float_local_flow : i32 + int 1 : i32 + test "option f64 call observation" + binary = : bool + call option_float_empty_score : i32 + call maybe_float_empty : (option f64) + int 1 : i32 + test "option bool local value flow" + binary = : bool + call option_flag_local_flow : i32 + int 1 : i32 + test "option bool call observation" + binary = : bool + call option_flag_empty_score : i32 + call maybe_flag_empty : (option bool) + int 1 : i32 + test "option string local value flow" + binary = : bool + call option_string_local_flow : i32 + int 1 : i32 + test "option string call observation" + binary = : bool + call option_string_empty_score : i32 + call maybe_string_empty : (option string) + int 1 : i32 + test "result call observation" + binary = : bool + call result_success_score : i32 + call result_ok_value : (result i32 i32) + int 42 : i32 + int 1 : i32 + test "result local value flow" + binary = : bool + call result_local_flow : i32 + int 1 : i32 diff --git a/tests/option-result-flow.slo b/tests/option-result-flow.slo new file mode 100644 index 0000000..14b9ba0 --- /dev/null +++ b/tests/option-result-flow.slo @@ -0,0 +1,160 @@ +(module main) + +(fn maybe_value ((value i32)) -> (option i32) + (some i32 value)) + +(fn maybe_empty () -> (option i32) + (none i32)) + +(fn maybe_wide_value ((value i64)) -> (option i64) + (some i64 value)) + +(fn maybe_wide_empty () -> (option i64) + (none i64)) + +(fn maybe_float_value ((value f64)) -> (option f64) + (some f64 value)) + +(fn maybe_float_empty () -> (option f64) + (none f64)) + +(fn maybe_flag_value ((value bool)) -> (option bool) + (some bool value)) + +(fn maybe_flag_empty () -> (option bool) + (none bool)) + +(fn maybe_string_value ((value string)) -> (option string) + (some string value)) + +(fn maybe_string_empty () -> (option string) + (none string)) + +(fn option_score ((value (option i32))) -> i32 + (if (is_some value) + 1 + 0)) + +(fn option_empty_score ((value (option i32))) -> i32 + (if (is_none value) + 1 + 0)) + +(fn option_wide_score ((value (option i64))) -> i32 + (if (is_some value) + 1 + 0)) + +(fn option_wide_empty_score ((value (option i64))) -> i32 + (if (is_none value) + 1 + 0)) + +(fn option_float_score ((value (option f64))) -> i32 + (if (is_some value) + 1 + 0)) + +(fn option_float_empty_score ((value (option f64))) -> i32 + (if (is_none value) + 1 + 0)) + +(fn option_flag_score ((value (option bool))) -> i32 + (if (is_some value) + 1 + 0)) + +(fn option_flag_empty_score ((value (option bool))) -> i32 + (if (is_none value) + 1 + 0)) + +(fn option_string_score ((value (option string))) -> i32 + (if (is_some value) + 1 + 0)) + +(fn option_string_empty_score ((value (option string))) -> i32 + (if (is_none value) + 1 + 0)) + +(fn result_ok_value ((value i32)) -> (result i32 i32) + (ok i32 i32 value)) + +(fn result_err_value ((code i32)) -> (result i32 i32) + (err i32 i32 code)) + +(fn result_success_score ((value (result i32 i32))) -> i32 + (if (is_ok value) + 1 + 0)) + +(fn result_failure_score ((value (result i32 i32))) -> i32 + (if (is_err value) + 1 + 0)) + +(fn option_local_flow () -> i32 + (let value (option i32) (maybe_value 42)) + (option_score value)) + +(fn option_wide_local_flow () -> i32 + (let value (option i64) (maybe_wide_value 2147483648i64)) + (option_wide_score value)) + +(fn option_float_local_flow () -> i32 + (let value (option f64) (maybe_float_value 42.5)) + (option_float_score value)) + +(fn option_flag_local_flow () -> i32 + (let value (option bool) (maybe_flag_value true)) + (option_flag_score value)) + +(fn option_string_local_flow () -> i32 + (let value (option string) (maybe_string_value "slovo")) + (option_string_score value)) + +(fn result_local_flow () -> i32 + (let value (result i32 i32) (result_err_value 7)) + (result_failure_score value)) + +(test "option local value flow" + (= (option_local_flow) 1)) + +(test "option call observation" + (= (option_empty_score (maybe_empty)) 1)) + +(test "option i64 local value flow" + (= (option_wide_local_flow) 1)) + +(test "option i64 call observation" + (= (option_wide_empty_score (maybe_wide_empty)) 1)) + +(test "option f64 local value flow" + (= (option_float_local_flow) 1)) + +(test "option f64 call observation" + (= (option_float_empty_score (maybe_float_empty)) 1)) + +(test "option bool local value flow" + (= (option_flag_local_flow) 1)) + +(test "option bool call observation" + (= (option_flag_empty_score (maybe_flag_empty)) 1)) + +(test "option string local value flow" + (= (option_string_local_flow) 1)) + +(test "option string call observation" + (= (option_string_empty_score (maybe_string_empty)) 1)) + +(test "result call observation" + (= (result_success_score (result_ok_value 42)) 1)) + +(test "result local value flow" + (= (result_local_flow) 1)) + +(fn main () -> i32 + 0) diff --git a/tests/option-result-flow.surface.lower b/tests/option-result-flow.surface.lower new file mode 100644 index 0000000..e9d0712 --- /dev/null +++ b/tests/option-result-flow.surface.lower @@ -0,0 +1,197 @@ +program main + fn maybe_value(value: i32) -> (option i32) + some i32 + var value + fn maybe_empty() -> (option i32) + none i32 + fn maybe_wide_value(value: i64) -> (option i64) + some i64 + var value + fn maybe_wide_empty() -> (option i64) + none i64 + fn maybe_float_value(value: f64) -> (option f64) + some f64 + var value + fn maybe_float_empty() -> (option f64) + none f64 + fn maybe_flag_value(value: bool) -> (option bool) + some bool + var value + fn maybe_flag_empty() -> (option bool) + none bool + fn maybe_string_value(value: string) -> (option string) + some string + var value + fn maybe_string_empty() -> (option string) + none string + fn option_score(value: (option i32)) -> i32 + if + is_some + var value + int 1 + int 0 + fn option_empty_score(value: (option i32)) -> i32 + if + is_none + var value + int 1 + int 0 + fn option_wide_score(value: (option i64)) -> i32 + if + is_some + var value + int 1 + int 0 + fn option_wide_empty_score(value: (option i64)) -> i32 + if + is_none + var value + int 1 + int 0 + fn option_float_score(value: (option f64)) -> i32 + if + is_some + var value + int 1 + int 0 + fn option_float_empty_score(value: (option f64)) -> i32 + if + is_none + var value + int 1 + int 0 + fn option_flag_score(value: (option bool)) -> i32 + if + is_some + var value + int 1 + int 0 + fn option_flag_empty_score(value: (option bool)) -> i32 + if + is_none + var value + int 1 + int 0 + fn option_string_score(value: (option string)) -> i32 + if + is_some + var value + int 1 + int 0 + fn option_string_empty_score(value: (option string)) -> i32 + if + is_none + var value + int 1 + int 0 + fn result_ok_value(value: i32) -> (result i32 i32) + ok i32 i32 + var value + fn result_err_value(code: i32) -> (result i32 i32) + err i32 i32 + var code + fn result_success_score(value: (result i32 i32)) -> i32 + if + is_ok + var value + int 1 + int 0 + fn result_failure_score(value: (result i32 i32)) -> i32 + if + is_err + var value + int 1 + int 0 + fn option_local_flow() -> i32 + local let value: (option i32) + call maybe_value + int 42 + call option_score + var value + fn option_wide_local_flow() -> i32 + local let value: (option i64) + call maybe_wide_value + i64 2147483648 + call option_wide_score + var value + fn option_float_local_flow() -> i32 + local let value: (option f64) + call maybe_float_value + float 42.5 + call option_float_score + var value + fn option_flag_local_flow() -> i32 + local let value: (option bool) + call maybe_flag_value + bool true + call option_flag_score + var value + fn option_string_local_flow() -> i32 + local let value: (option string) + call maybe_string_value + string "slovo" + call option_string_score + var value + fn result_local_flow() -> i32 + local let value: (result i32 i32) + call result_err_value + int 7 + call result_failure_score + var value + fn main() -> i32 + int 0 + test "option local value flow" + binary = + call option_local_flow + int 1 + test "option call observation" + binary = + call option_empty_score + call maybe_empty + int 1 + test "option i64 local value flow" + binary = + call option_wide_local_flow + int 1 + test "option i64 call observation" + binary = + call option_wide_empty_score + call maybe_wide_empty + int 1 + test "option f64 local value flow" + binary = + call option_float_local_flow + int 1 + test "option f64 call observation" + binary = + call option_float_empty_score + call maybe_float_empty + int 1 + test "option bool local value flow" + binary = + call option_flag_local_flow + int 1 + test "option bool call observation" + binary = + call option_flag_empty_score + call maybe_flag_empty + int 1 + test "option string local value flow" + binary = + call option_string_local_flow + int 1 + test "option string call observation" + binary = + call option_string_empty_score + call maybe_string_empty + int 1 + test "result call observation" + binary = + call result_success_score + call result_ok_value + int 42 + int 1 + test "result local value flow" + binary = + call result_local_flow + int 1 diff --git a/tests/option-result-match.checked.lower b/tests/option-result-match.checked.lower new file mode 100644 index 0000000..002f8df --- /dev/null +++ b/tests/option-result-match.checked.lower @@ -0,0 +1,259 @@ +program main + fn maybe_value(value: i32) -> (option i32) + some : (option i32) + var value : i32 + fn maybe_empty() -> (option i32) + none : (option i32) + fn maybe_wide_value(value: i64) -> (option i64) + some : (option i64) + var value : i64 + fn maybe_wide_empty() -> (option i64) + none : (option i64) + fn maybe_float_value(value: f64) -> (option f64) + some : (option f64) + var value : f64 + fn maybe_float_empty() -> (option f64) + none : (option f64) + fn maybe_flag_value(value: bool) -> (option bool) + some : (option bool) + var value : bool + fn maybe_flag_empty() -> (option bool) + none : (option bool) + fn maybe_string_value(value: string) -> (option string) + some : (option string) + var value : string + fn maybe_string_empty() -> (option string) + none : (option string) + fn result_ok_value(value: i32) -> (result i32 i32) + ok : (result i32 i32) + var value : i32 + fn result_err_value(code: i32) -> (result i32 i32) + err : (result i32 i32) + var code : i32 + fn option_value_or(value: (option i32), fallback: i32) -> i32 + match : i32 + subject + var value : (option i32) + arm some payload + var payload : i32 + arm none + var fallback : i32 + fn option_bump_or_zero(value: (option i32)) -> i32 + match : i32 + subject + var value : (option i32) + arm some payload + local let bumped : unit + binary + : i32 + var payload : i32 + int 1 : i32 + var bumped : i32 + arm none + int 0 : i32 + fn option_wide_value_or(value: (option i64), fallback: i64) -> i64 + match : i64 + subject + var value : (option i64) + arm some payload + var payload : i64 + arm none + var fallback : i64 + fn option_wide_bump_or_zero(value: (option i64)) -> i64 + match : i64 + subject + var value : (option i64) + arm some payload + local let bumped : unit + binary + : i64 + var payload : i64 + i64 1 : i64 + var bumped : i64 + arm none + i64 0 : i64 + fn option_float_value_or(value: (option f64), fallback: f64) -> f64 + match : f64 + subject + var value : (option f64) + arm some payload + var payload : f64 + arm none + var fallback : f64 + fn option_float_bump_or_zero(value: (option f64)) -> f64 + match : f64 + subject + var value : (option f64) + arm some payload + local let bumped : unit + binary + : f64 + var payload : f64 + float 1 : f64 + var bumped : f64 + arm none + float 0 : f64 + fn option_flag_value_or(value: (option bool), fallback: bool) -> bool + match : bool + subject + var value : (option bool) + arm some payload + var payload : bool + arm none + var fallback : bool + fn option_flag_selected_or(value: (option bool), fallback: bool) -> bool + match : bool + subject + var value : (option bool) + arm some payload + if : bool + var payload : bool + bool true : bool + bool false : bool + var payload : bool + arm none + var fallback : bool + fn option_string_value_or(value: (option string), fallback: string) -> string + match : string + subject + var value : (option string) + arm some payload + var payload : string + arm none + var fallback : string + fn option_string_selected_or(value: (option string), fallback: string) -> string + match : string + subject + var value : (option string) + arm some payload + local let chosen : unit + var payload : string + var chosen : string + arm none + var fallback : string + fn result_value_or_code(value: (result i32 i32)) -> i32 + match : i32 + subject + var value : (result i32 i32) + arm ok payload + var payload : i32 + arm err code + var code : i32 + fn result_score(value: (result i32 i32)) -> i32 + match : i32 + subject + var value : (result i32 i32) + arm ok payload + binary + : i32 + var payload : i32 + int 10 : i32 + arm err code + var code : i32 + fn main() -> i32 + int 0 : i32 + test "option match some payload" + binary = : bool + call option_value_or : i32 + call maybe_value : (option i32) + int 42 : i32 + int 0 : i32 + int 42 : i32 + test "option match none fallback" + binary = : bool + call option_value_or : i32 + call maybe_empty : (option i32) + int 7 : i32 + int 7 : i32 + test "option match multi expression arm" + binary = : bool + call option_bump_or_zero : i32 + call maybe_value : (option i32) + int 8 : i32 + int 9 : i32 + test "option i64 match some payload" + binary = : bool + call option_wide_value_or : i64 + call maybe_wide_value : (option i64) + i64 2147483648 : i64 + i64 0 : i64 + i64 2147483648 : i64 + test "option i64 match none fallback" + binary = : bool + call option_wide_value_or : i64 + call maybe_wide_empty : (option i64) + i64 7 : i64 + i64 7 : i64 + test "option i64 match multi expression arm" + binary = : bool + call option_wide_bump_or_zero : i64 + call maybe_wide_value : (option i64) + i64 8 : i64 + i64 9 : i64 + test "option f64 match some payload" + binary = : bool + call option_float_value_or : f64 + call maybe_float_value : (option f64) + float 42.5 : f64 + float 0 : f64 + float 42.5 : f64 + test "option f64 match none fallback" + binary = : bool + call option_float_value_or : f64 + call maybe_float_empty : (option f64) + float 7 : f64 + float 7 : f64 + test "option f64 match multi expression arm" + binary = : bool + call option_float_bump_or_zero : f64 + call maybe_float_value : (option f64) + float 8.5 : f64 + float 9.5 : f64 + test "option bool match some payload" + call option_flag_value_or : bool + call maybe_flag_value : (option bool) + bool true : bool + bool false : bool + test "option bool match none fallback" + call option_flag_value_or : bool + call maybe_flag_empty : (option bool) + bool true : bool + test "option bool match multi expression arm" + call option_flag_selected_or : bool + call maybe_flag_value : (option bool) + bool true : bool + bool false : bool + test "option string match some payload" + binary = : bool + call option_string_value_or : string + call maybe_string_value : (option string) + string "slovo" : string + string "fallback" : string + string "slovo" : string + test "option string match none fallback" + binary = : bool + call option_string_value_or : string + call maybe_string_empty : (option string) + string "fallback" : string + string "fallback" : string + test "option string match multi expression arm" + binary = : bool + call option_string_selected_or : string + call maybe_string_value : (option string) + string "oak" : string + string "fallback" : string + string "oak" : string + test "result match ok payload" + binary = : bool + call result_value_or_code : i32 + call result_ok_value : (result i32 i32) + int 30 : i32 + int 30 : i32 + test "result match err payload" + binary = : bool + call result_value_or_code : i32 + call result_err_value : (result i32 i32) + int 5 : i32 + int 5 : i32 + test "result match computed arm" + binary = : bool + call result_score : i32 + call result_ok_value : (result i32 i32) + int 2 : i32 + int 12 : i32 diff --git a/tests/option-result-match.slo b/tests/option-result-match.slo new file mode 100644 index 0000000..827d6a3 --- /dev/null +++ b/tests/option-result-match.slo @@ -0,0 +1,185 @@ +(module main) + +(fn maybe_value ((value i32)) -> (option i32) + (some i32 value)) + +(fn maybe_empty () -> (option i32) + (none i32)) + +(fn maybe_wide_value ((value i64)) -> (option i64) + (some i64 value)) + +(fn maybe_wide_empty () -> (option i64) + (none i64)) + +(fn maybe_float_value ((value f64)) -> (option f64) + (some f64 value)) + +(fn maybe_float_empty () -> (option f64) + (none f64)) + +(fn maybe_flag_value ((value bool)) -> (option bool) + (some bool value)) + +(fn maybe_flag_empty () -> (option bool) + (none bool)) + +(fn maybe_string_value ((value string)) -> (option string) + (some string value)) + +(fn maybe_string_empty () -> (option string) + (none string)) + +(fn result_ok_value ((value i32)) -> (result i32 i32) + (ok i32 i32 value)) + +(fn result_err_value ((code i32)) -> (result i32 i32) + (err i32 i32 code)) + +(fn option_value_or ((value (option i32)) (fallback i32)) -> i32 + (match value + ((some payload) + payload) + ((none) + fallback))) + +(fn option_bump_or_zero ((value (option i32))) -> i32 + (match value + ((some payload) + (let bumped i32 (+ payload 1)) + bumped) + ((none) + 0))) + +(fn option_wide_value_or ((value (option i64)) (fallback i64)) -> i64 + (match value + ((some payload) + payload) + ((none) + fallback))) + +(fn option_wide_bump_or_zero ((value (option i64))) -> i64 + (match value + ((some payload) + (let bumped i64 (+ payload 1i64)) + bumped) + ((none) + 0i64))) + +(fn option_float_value_or ((value (option f64)) (fallback f64)) -> f64 + (match value + ((some payload) + payload) + ((none) + fallback))) + +(fn option_float_bump_or_zero ((value (option f64))) -> f64 + (match value + ((some payload) + (let bumped f64 (+ payload 1.0)) + bumped) + ((none) + 0.0))) + +(fn option_flag_value_or ((value (option bool)) (fallback bool)) -> bool + (match value + ((some payload) + payload) + ((none) + fallback))) + +(fn option_flag_selected_or ((value (option bool)) (fallback bool)) -> bool + (match value + ((some payload) + (if payload + true + false) + payload) + ((none) + fallback))) + +(fn option_string_value_or ((value (option string)) (fallback string)) -> string + (match value + ((some payload) + payload) + ((none) + fallback))) + +(fn option_string_selected_or ((value (option string)) (fallback string)) -> string + (match value + ((some payload) + (let chosen string payload) + chosen) + ((none) + fallback))) + +(fn result_value_or_code ((value (result i32 i32))) -> i32 + (match value + ((ok payload) + payload) + ((err code) + code))) + +(fn result_score ((value (result i32 i32))) -> i32 + (match value + ((ok payload) + (+ payload 10)) + ((err code) + code))) + +(test "option match some payload" + (= (option_value_or (maybe_value 42) 0) 42)) + +(test "option match none fallback" + (= (option_value_or (maybe_empty) 7) 7)) + +(test "option match multi expression arm" + (= (option_bump_or_zero (maybe_value 8)) 9)) + +(test "option i64 match some payload" + (= (option_wide_value_or (maybe_wide_value 2147483648i64) 0i64) 2147483648i64)) + +(test "option i64 match none fallback" + (= (option_wide_value_or (maybe_wide_empty) 7i64) 7i64)) + +(test "option i64 match multi expression arm" + (= (option_wide_bump_or_zero (maybe_wide_value 8i64)) 9i64)) + +(test "option f64 match some payload" + (= (option_float_value_or (maybe_float_value 42.5) 0.0) 42.5)) + +(test "option f64 match none fallback" + (= (option_float_value_or (maybe_float_empty) 7.0) 7.0)) + +(test "option f64 match multi expression arm" + (= (option_float_bump_or_zero (maybe_float_value 8.5)) 9.5)) + +(test "option bool match some payload" + (option_flag_value_or (maybe_flag_value true) false)) + +(test "option bool match none fallback" + (option_flag_value_or (maybe_flag_empty) true)) + +(test "option bool match multi expression arm" + (option_flag_selected_or (maybe_flag_value true) false)) + +(test "option string match some payload" + (= (option_string_value_or (maybe_string_value "slovo") "fallback") "slovo")) + +(test "option string match none fallback" + (= (option_string_value_or (maybe_string_empty) "fallback") "fallback")) + +(test "option string match multi expression arm" + (= (option_string_selected_or (maybe_string_value "oak") "fallback") "oak")) + +(test "result match ok payload" + (= (result_value_or_code (result_ok_value 30)) 30)) + +(test "result match err payload" + (= (result_value_or_code (result_err_value 5)) 5)) + +(test "result match computed arm" + (= (result_score (result_ok_value 2)) 12)) + +(fn main () -> i32 + 0) diff --git a/tests/option-result-match.surface.lower b/tests/option-result-match.surface.lower new file mode 100644 index 0000000..0a4709d --- /dev/null +++ b/tests/option-result-match.surface.lower @@ -0,0 +1,259 @@ +program main + fn maybe_value(value: i32) -> (option i32) + some i32 + var value + fn maybe_empty() -> (option i32) + none i32 + fn maybe_wide_value(value: i64) -> (option i64) + some i64 + var value + fn maybe_wide_empty() -> (option i64) + none i64 + fn maybe_float_value(value: f64) -> (option f64) + some f64 + var value + fn maybe_float_empty() -> (option f64) + none f64 + fn maybe_flag_value(value: bool) -> (option bool) + some bool + var value + fn maybe_flag_empty() -> (option bool) + none bool + fn maybe_string_value(value: string) -> (option string) + some string + var value + fn maybe_string_empty() -> (option string) + none string + fn result_ok_value(value: i32) -> (result i32 i32) + ok i32 i32 + var value + fn result_err_value(code: i32) -> (result i32 i32) + err i32 i32 + var code + fn option_value_or(value: (option i32), fallback: i32) -> i32 + match + subject + var value + arm some payload + var payload + arm none + var fallback + fn option_bump_or_zero(value: (option i32)) -> i32 + match + subject + var value + arm some payload + local let bumped: i32 + binary + + var payload + int 1 + var bumped + arm none + int 0 + fn option_wide_value_or(value: (option i64), fallback: i64) -> i64 + match + subject + var value + arm some payload + var payload + arm none + var fallback + fn option_wide_bump_or_zero(value: (option i64)) -> i64 + match + subject + var value + arm some payload + local let bumped: i64 + binary + + var payload + i64 1 + var bumped + arm none + i64 0 + fn option_float_value_or(value: (option f64), fallback: f64) -> f64 + match + subject + var value + arm some payload + var payload + arm none + var fallback + fn option_float_bump_or_zero(value: (option f64)) -> f64 + match + subject + var value + arm some payload + local let bumped: f64 + binary + + var payload + float 1 + var bumped + arm none + float 0 + fn option_flag_value_or(value: (option bool), fallback: bool) -> bool + match + subject + var value + arm some payload + var payload + arm none + var fallback + fn option_flag_selected_or(value: (option bool), fallback: bool) -> bool + match + subject + var value + arm some payload + if + var payload + bool true + bool false + var payload + arm none + var fallback + fn option_string_value_or(value: (option string), fallback: string) -> string + match + subject + var value + arm some payload + var payload + arm none + var fallback + fn option_string_selected_or(value: (option string), fallback: string) -> string + match + subject + var value + arm some payload + local let chosen: string + var payload + var chosen + arm none + var fallback + fn result_value_or_code(value: (result i32 i32)) -> i32 + match + subject + var value + arm ok payload + var payload + arm err code + var code + fn result_score(value: (result i32 i32)) -> i32 + match + subject + var value + arm ok payload + binary + + var payload + int 10 + arm err code + var code + fn main() -> i32 + int 0 + test "option match some payload" + binary = + call option_value_or + call maybe_value + int 42 + int 0 + int 42 + test "option match none fallback" + binary = + call option_value_or + call maybe_empty + int 7 + int 7 + test "option match multi expression arm" + binary = + call option_bump_or_zero + call maybe_value + int 8 + int 9 + test "option i64 match some payload" + binary = + call option_wide_value_or + call maybe_wide_value + i64 2147483648 + i64 0 + i64 2147483648 + test "option i64 match none fallback" + binary = + call option_wide_value_or + call maybe_wide_empty + i64 7 + i64 7 + test "option i64 match multi expression arm" + binary = + call option_wide_bump_or_zero + call maybe_wide_value + i64 8 + i64 9 + test "option f64 match some payload" + binary = + call option_float_value_or + call maybe_float_value + float 42.5 + float 0 + float 42.5 + test "option f64 match none fallback" + binary = + call option_float_value_or + call maybe_float_empty + float 7 + float 7 + test "option f64 match multi expression arm" + binary = + call option_float_bump_or_zero + call maybe_float_value + float 8.5 + float 9.5 + test "option bool match some payload" + call option_flag_value_or + call maybe_flag_value + bool true + bool false + test "option bool match none fallback" + call option_flag_value_or + call maybe_flag_empty + bool true + test "option bool match multi expression arm" + call option_flag_selected_or + call maybe_flag_value + bool true + bool false + test "option string match some payload" + binary = + call option_string_value_or + call maybe_string_value + string "slovo" + string "fallback" + string "slovo" + test "option string match none fallback" + binary = + call option_string_value_or + call maybe_string_empty + string "fallback" + string "fallback" + test "option string match multi expression arm" + binary = + call option_string_selected_or + call maybe_string_value + string "oak" + string "fallback" + string "oak" + test "result match ok payload" + binary = + call result_value_or_code + call result_ok_value + int 30 + int 30 + test "result match err payload" + binary = + call result_value_or_code + call result_err_value + int 5 + int 5 + test "result match computed arm" + binary = + call result_score + call result_ok_value + int 2 + int 12 diff --git a/tests/option-result-payload.checked.lower b/tests/option-result-payload.checked.lower new file mode 100644 index 0000000..5627436 --- /dev/null +++ b/tests/option-result-payload.checked.lower @@ -0,0 +1,342 @@ +program main + fn maybe_value(value: i32) -> (option i32) + some : (option i32) + var value : i32 + fn maybe_empty() -> (option i32) + none : (option i32) + fn maybe_wide_value(value: i64) -> (option i64) + some : (option i64) + var value : i64 + fn maybe_wide_empty() -> (option i64) + none : (option i64) + fn maybe_float_value(value: f64) -> (option f64) + some : (option f64) + var value : f64 + fn maybe_float_empty() -> (option f64) + none : (option f64) + fn maybe_flag_value(value: bool) -> (option bool) + some : (option bool) + var value : bool + fn maybe_flag_empty() -> (option bool) + none : (option bool) + fn maybe_string_value(value: string) -> (option string) + some : (option string) + var value : string + fn maybe_string_empty() -> (option string) + none : (option string) + fn result_ok_value(value: i32) -> (result i32 i32) + ok : (result i32 i32) + var value : i32 + fn result_err_value(code: i32) -> (result i32 i32) + err : (result i32 i32) + var code : i32 + fn option_direct_payload() -> i32 + unwrap_some : i32 + some : (option i32) + int 42 : i32 + fn option_param_payload(value: (option i32)) -> i32 + unwrap_some : i32 + var value : (option i32) + fn option_local_payload() -> i32 + local let value : unit + call maybe_value : (option i32) + int 21 : i32 + unwrap_some : i32 + var value : (option i32) + fn option_call_payload() -> i32 + unwrap_some : i32 + call maybe_value : (option i32) + int 22 : i32 + fn option_guarded_payload(value: (option i32)) -> i32 + if : i32 + is_some : bool + var value : (option i32) + unwrap_some : i32 + var value : (option i32) + int 0 : i32 + fn option_wide_direct_payload() -> i64 + unwrap_some : i64 + some : (option i64) + i64 2147483648 : i64 + fn option_wide_param_payload(value: (option i64)) -> i64 + unwrap_some : i64 + var value : (option i64) + fn option_wide_local_payload() -> i64 + local let value : unit + call maybe_wide_value : (option i64) + i64 21 : i64 + unwrap_some : i64 + var value : (option i64) + fn option_wide_call_payload() -> i64 + unwrap_some : i64 + call maybe_wide_value : (option i64) + i64 22 : i64 + fn option_wide_guarded_payload(value: (option i64)) -> i64 + if : i64 + is_some : bool + var value : (option i64) + unwrap_some : i64 + var value : (option i64) + i64 0 : i64 + fn option_float_direct_payload() -> f64 + unwrap_some : f64 + some : (option f64) + float 42.5 : f64 + fn option_float_param_payload(value: (option f64)) -> f64 + unwrap_some : f64 + var value : (option f64) + fn option_float_local_payload() -> f64 + local let value : unit + call maybe_float_value : (option f64) + float 21.5 : f64 + unwrap_some : f64 + var value : (option f64) + fn option_float_call_payload() -> f64 + unwrap_some : f64 + call maybe_float_value : (option f64) + float 22.5 : f64 + fn option_float_guarded_payload(value: (option f64)) -> f64 + if : f64 + is_some : bool + var value : (option f64) + unwrap_some : f64 + var value : (option f64) + float 0 : f64 + fn option_flag_direct_payload() -> bool + unwrap_some : bool + some : (option bool) + bool true : bool + fn option_flag_param_payload(value: (option bool)) -> bool + unwrap_some : bool + var value : (option bool) + fn option_flag_local_payload() -> bool + local let value : unit + call maybe_flag_value : (option bool) + bool true : bool + unwrap_some : bool + var value : (option bool) + fn option_flag_call_payload() -> bool + unwrap_some : bool + call maybe_flag_value : (option bool) + bool true : bool + fn option_flag_guarded_payload(value: (option bool)) -> bool + if : bool + is_some : bool + var value : (option bool) + unwrap_some : bool + var value : (option bool) + bool false : bool + fn option_string_direct_payload() -> string + unwrap_some : string + some : (option string) + string "slovo" : string + fn option_string_param_payload(value: (option string)) -> string + unwrap_some : string + var value : (option string) + fn option_string_local_payload() -> string + local let value : unit + call maybe_string_value : (option string) + string "oak" : string + unwrap_some : string + var value : (option string) + fn option_string_call_payload() -> string + unwrap_some : string + call maybe_string_value : (option string) + string "pine" : string + fn option_string_guarded_payload(value: (option string)) -> string + if : string + is_some : bool + var value : (option string) + unwrap_some : string + var value : (option string) + string "fallback" : string + fn result_ok_direct_payload() -> i32 + unwrap_ok : i32 + ok : (result i32 i32) + int 30 : i32 + fn result_err_direct_payload() -> i32 + unwrap_err : i32 + err : (result i32 i32) + int 7 : i32 + fn result_ok_param_payload(value: (result i32 i32)) -> i32 + unwrap_ok : i32 + var value : (result i32 i32) + fn result_err_param_payload(value: (result i32 i32)) -> i32 + unwrap_err : i32 + var value : (result i32 i32) + fn result_ok_local_payload() -> i32 + local let value : unit + call result_ok_value : (result i32 i32) + int 31 : i32 + unwrap_ok : i32 + var value : (result i32 i32) + fn result_err_call_payload() -> i32 + unwrap_err : i32 + call result_err_value : (result i32 i32) + int 9 : i32 + fn main() -> i32 + int 0 : i32 + test "unwrap some direct constructor" + binary = : bool + call option_direct_payload : i32 + int 42 : i32 + test "unwrap some local" + binary = : bool + call option_local_payload : i32 + int 21 : i32 + test "unwrap some call" + binary = : bool + call option_call_payload : i32 + int 22 : i32 + test "unwrap some parameter" + binary = : bool + call option_param_payload : i32 + call maybe_value : (option i32) + int 23 : i32 + int 23 : i32 + test "guarded unwrap some present" + binary = : bool + call option_guarded_payload : i32 + call maybe_value : (option i32) + int 24 : i32 + int 24 : i32 + test "guarded unwrap some absent" + binary = : bool + call option_guarded_payload : i32 + call maybe_empty : (option i32) + int 0 : i32 + test "unwrap some i64 direct constructor" + binary = : bool + call option_wide_direct_payload : i64 + i64 2147483648 : i64 + test "unwrap some i64 local" + binary = : bool + call option_wide_local_payload : i64 + i64 21 : i64 + test "unwrap some i64 call" + binary = : bool + call option_wide_call_payload : i64 + i64 22 : i64 + test "unwrap some i64 parameter" + binary = : bool + call option_wide_param_payload : i64 + call maybe_wide_value : (option i64) + i64 23 : i64 + i64 23 : i64 + test "guarded unwrap some i64 present" + binary = : bool + call option_wide_guarded_payload : i64 + call maybe_wide_value : (option i64) + i64 24 : i64 + i64 24 : i64 + test "guarded unwrap some i64 absent" + binary = : bool + call option_wide_guarded_payload : i64 + call maybe_wide_empty : (option i64) + i64 0 : i64 + test "unwrap some f64 direct constructor" + binary = : bool + call option_float_direct_payload : f64 + float 42.5 : f64 + test "unwrap some f64 local" + binary = : bool + call option_float_local_payload : f64 + float 21.5 : f64 + test "unwrap some f64 call" + binary = : bool + call option_float_call_payload : f64 + float 22.5 : f64 + test "unwrap some f64 parameter" + binary = : bool + call option_float_param_payload : f64 + call maybe_float_value : (option f64) + float 23.5 : f64 + float 23.5 : f64 + test "guarded unwrap some f64 present" + binary = : bool + call option_float_guarded_payload : f64 + call maybe_float_value : (option f64) + float 24.5 : f64 + float 24.5 : f64 + test "guarded unwrap some f64 absent" + binary = : bool + call option_float_guarded_payload : f64 + call maybe_float_empty : (option f64) + float 0 : f64 + test "unwrap some bool direct constructor" + call option_flag_direct_payload : bool + test "unwrap some bool local" + call option_flag_local_payload : bool + test "unwrap some bool call" + call option_flag_call_payload : bool + test "unwrap some bool parameter" + call option_flag_param_payload : bool + call maybe_flag_value : (option bool) + bool true : bool + test "guarded unwrap some bool present" + call option_flag_guarded_payload : bool + call maybe_flag_value : (option bool) + bool true : bool + test "guarded unwrap some bool absent" + if : bool + call option_flag_guarded_payload : bool + call maybe_flag_empty : (option bool) + bool false : bool + bool true : bool + test "unwrap some string direct constructor" + binary = : bool + call option_string_direct_payload : string + string "slovo" : string + test "unwrap some string local" + binary = : bool + call option_string_local_payload : string + string "oak" : string + test "unwrap some string call" + binary = : bool + call option_string_call_payload : string + string "pine" : string + test "unwrap some string parameter" + binary = : bool + call option_string_param_payload : string + call maybe_string_value : (option string) + string "birch" : string + string "birch" : string + test "guarded unwrap some string present" + binary = : bool + call option_string_guarded_payload : string + call maybe_string_value : (option string) + string "cedar" : string + string "cedar" : string + test "guarded unwrap some string absent" + binary = : bool + call option_string_guarded_payload : string + call maybe_string_empty : (option string) + string "fallback" : string + test "unwrap ok direct constructor" + binary = : bool + call result_ok_direct_payload : i32 + int 30 : i32 + test "unwrap err direct constructor" + binary = : bool + call result_err_direct_payload : i32 + int 7 : i32 + test "unwrap ok parameter" + binary = : bool + call result_ok_param_payload : i32 + call result_ok_value : (result i32 i32) + int 32 : i32 + int 32 : i32 + test "unwrap err parameter" + binary = : bool + call result_err_param_payload : i32 + call result_err_value : (result i32 i32) + int 8 : i32 + int 8 : i32 + test "unwrap ok local" + binary = : bool + call result_ok_local_payload : i32 + int 31 : i32 + test "unwrap err call" + binary = : bool + call result_err_call_payload : i32 + int 9 : i32 diff --git a/tests/option-result-payload.slo b/tests/option-result-payload.slo new file mode 100644 index 0000000..80af456 --- /dev/null +++ b/tests/option-result-payload.slo @@ -0,0 +1,259 @@ +(module main) + +(fn maybe_value ((value i32)) -> (option i32) + (some i32 value)) + +(fn maybe_empty () -> (option i32) + (none i32)) + +(fn maybe_wide_value ((value i64)) -> (option i64) + (some i64 value)) + +(fn maybe_wide_empty () -> (option i64) + (none i64)) + +(fn maybe_float_value ((value f64)) -> (option f64) + (some f64 value)) + +(fn maybe_float_empty () -> (option f64) + (none f64)) + +(fn maybe_flag_value ((value bool)) -> (option bool) + (some bool value)) + +(fn maybe_flag_empty () -> (option bool) + (none bool)) + +(fn maybe_string_value ((value string)) -> (option string) + (some string value)) + +(fn maybe_string_empty () -> (option string) + (none string)) + +(fn result_ok_value ((value i32)) -> (result i32 i32) + (ok i32 i32 value)) + +(fn result_err_value ((code i32)) -> (result i32 i32) + (err i32 i32 code)) + +(fn option_direct_payload () -> i32 + (unwrap_some (some i32 42))) + +(fn option_param_payload ((value (option i32))) -> i32 + (unwrap_some value)) + +(fn option_local_payload () -> i32 + (let value (option i32) (maybe_value 21)) + (unwrap_some value)) + +(fn option_call_payload () -> i32 + (unwrap_some (maybe_value 22))) + +(fn option_guarded_payload ((value (option i32))) -> i32 + (if (is_some value) + (unwrap_some value) + 0)) + +(fn option_wide_direct_payload () -> i64 + (unwrap_some (some i64 2147483648i64))) + +(fn option_wide_param_payload ((value (option i64))) -> i64 + (unwrap_some value)) + +(fn option_wide_local_payload () -> i64 + (let value (option i64) (maybe_wide_value 21i64)) + (unwrap_some value)) + +(fn option_wide_call_payload () -> i64 + (unwrap_some (maybe_wide_value 22i64))) + +(fn option_wide_guarded_payload ((value (option i64))) -> i64 + (if (is_some value) + (unwrap_some value) + 0i64)) + +(fn option_float_direct_payload () -> f64 + (unwrap_some (some f64 42.5))) + +(fn option_float_param_payload ((value (option f64))) -> f64 + (unwrap_some value)) + +(fn option_float_local_payload () -> f64 + (let value (option f64) (maybe_float_value 21.5)) + (unwrap_some value)) + +(fn option_float_call_payload () -> f64 + (unwrap_some (maybe_float_value 22.5))) + +(fn option_float_guarded_payload ((value (option f64))) -> f64 + (if (is_some value) + (unwrap_some value) + 0.0)) + +(fn option_flag_direct_payload () -> bool + (unwrap_some (some bool true))) + +(fn option_flag_param_payload ((value (option bool))) -> bool + (unwrap_some value)) + +(fn option_flag_local_payload () -> bool + (let value (option bool) (maybe_flag_value true)) + (unwrap_some value)) + +(fn option_flag_call_payload () -> bool + (unwrap_some (maybe_flag_value true))) + +(fn option_flag_guarded_payload ((value (option bool))) -> bool + (if (is_some value) + (unwrap_some value) + false)) + +(fn option_string_direct_payload () -> string + (unwrap_some (some string "slovo"))) + +(fn option_string_param_payload ((value (option string))) -> string + (unwrap_some value)) + +(fn option_string_local_payload () -> string + (let value (option string) (maybe_string_value "oak")) + (unwrap_some value)) + +(fn option_string_call_payload () -> string + (unwrap_some (maybe_string_value "pine"))) + +(fn option_string_guarded_payload ((value (option string))) -> string + (if (is_some value) + (unwrap_some value) + "fallback")) + +(fn result_ok_direct_payload () -> i32 + (unwrap_ok (ok i32 i32 30))) + +(fn result_err_direct_payload () -> i32 + (unwrap_err (err i32 i32 7))) + +(fn result_ok_param_payload ((value (result i32 i32))) -> i32 + (unwrap_ok value)) + +(fn result_err_param_payload ((value (result i32 i32))) -> i32 + (unwrap_err value)) + +(fn result_ok_local_payload () -> i32 + (let value (result i32 i32) (result_ok_value 31)) + (unwrap_ok value)) + +(fn result_err_call_payload () -> i32 + (unwrap_err (result_err_value 9))) + +(test "unwrap some direct constructor" + (= (option_direct_payload) 42)) + +(test "unwrap some local" + (= (option_local_payload) 21)) + +(test "unwrap some call" + (= (option_call_payload) 22)) + +(test "unwrap some parameter" + (= (option_param_payload (maybe_value 23)) 23)) + +(test "guarded unwrap some present" + (= (option_guarded_payload (maybe_value 24)) 24)) + +(test "guarded unwrap some absent" + (= (option_guarded_payload (maybe_empty)) 0)) + +(test "unwrap some i64 direct constructor" + (= (option_wide_direct_payload) 2147483648i64)) + +(test "unwrap some i64 local" + (= (option_wide_local_payload) 21i64)) + +(test "unwrap some i64 call" + (= (option_wide_call_payload) 22i64)) + +(test "unwrap some i64 parameter" + (= (option_wide_param_payload (maybe_wide_value 23i64)) 23i64)) + +(test "guarded unwrap some i64 present" + (= (option_wide_guarded_payload (maybe_wide_value 24i64)) 24i64)) + +(test "guarded unwrap some i64 absent" + (= (option_wide_guarded_payload (maybe_wide_empty)) 0i64)) + +(test "unwrap some f64 direct constructor" + (= (option_float_direct_payload) 42.5)) + +(test "unwrap some f64 local" + (= (option_float_local_payload) 21.5)) + +(test "unwrap some f64 call" + (= (option_float_call_payload) 22.5)) + +(test "unwrap some f64 parameter" + (= (option_float_param_payload (maybe_float_value 23.5)) 23.5)) + +(test "guarded unwrap some f64 present" + (= (option_float_guarded_payload (maybe_float_value 24.5)) 24.5)) + +(test "guarded unwrap some f64 absent" + (= (option_float_guarded_payload (maybe_float_empty)) 0.0)) + +(test "unwrap some bool direct constructor" + (option_flag_direct_payload)) + +(test "unwrap some bool local" + (option_flag_local_payload)) + +(test "unwrap some bool call" + (option_flag_call_payload)) + +(test "unwrap some bool parameter" + (option_flag_param_payload (maybe_flag_value true))) + +(test "guarded unwrap some bool present" + (option_flag_guarded_payload (maybe_flag_value true))) + +(test "guarded unwrap some bool absent" + (if (option_flag_guarded_payload (maybe_flag_empty)) + false + true)) + +(test "unwrap some string direct constructor" + (= (option_string_direct_payload) "slovo")) + +(test "unwrap some string local" + (= (option_string_local_payload) "oak")) + +(test "unwrap some string call" + (= (option_string_call_payload) "pine")) + +(test "unwrap some string parameter" + (= (option_string_param_payload (maybe_string_value "birch")) "birch")) + +(test "guarded unwrap some string present" + (= (option_string_guarded_payload (maybe_string_value "cedar")) "cedar")) + +(test "guarded unwrap some string absent" + (= (option_string_guarded_payload (maybe_string_empty)) "fallback")) + +(test "unwrap ok direct constructor" + (= (result_ok_direct_payload) 30)) + +(test "unwrap err direct constructor" + (= (result_err_direct_payload) 7)) + +(test "unwrap ok parameter" + (= (result_ok_param_payload (result_ok_value 32)) 32)) + +(test "unwrap err parameter" + (= (result_err_param_payload (result_err_value 8)) 8)) + +(test "unwrap ok local" + (= (result_ok_local_payload) 31)) + +(test "unwrap err call" + (= (result_err_call_payload) 9)) + +(fn main () -> i32 + 0) diff --git a/tests/option-result-payload.surface.lower b/tests/option-result-payload.surface.lower new file mode 100644 index 0000000..d7dbd80 --- /dev/null +++ b/tests/option-result-payload.surface.lower @@ -0,0 +1,342 @@ +program main + fn maybe_value(value: i32) -> (option i32) + some i32 + var value + fn maybe_empty() -> (option i32) + none i32 + fn maybe_wide_value(value: i64) -> (option i64) + some i64 + var value + fn maybe_wide_empty() -> (option i64) + none i64 + fn maybe_float_value(value: f64) -> (option f64) + some f64 + var value + fn maybe_float_empty() -> (option f64) + none f64 + fn maybe_flag_value(value: bool) -> (option bool) + some bool + var value + fn maybe_flag_empty() -> (option bool) + none bool + fn maybe_string_value(value: string) -> (option string) + some string + var value + fn maybe_string_empty() -> (option string) + none string + fn result_ok_value(value: i32) -> (result i32 i32) + ok i32 i32 + var value + fn result_err_value(code: i32) -> (result i32 i32) + err i32 i32 + var code + fn option_direct_payload() -> i32 + unwrap_some + some i32 + int 42 + fn option_param_payload(value: (option i32)) -> i32 + unwrap_some + var value + fn option_local_payload() -> i32 + local let value: (option i32) + call maybe_value + int 21 + unwrap_some + var value + fn option_call_payload() -> i32 + unwrap_some + call maybe_value + int 22 + fn option_guarded_payload(value: (option i32)) -> i32 + if + is_some + var value + unwrap_some + var value + int 0 + fn option_wide_direct_payload() -> i64 + unwrap_some + some i64 + i64 2147483648 + fn option_wide_param_payload(value: (option i64)) -> i64 + unwrap_some + var value + fn option_wide_local_payload() -> i64 + local let value: (option i64) + call maybe_wide_value + i64 21 + unwrap_some + var value + fn option_wide_call_payload() -> i64 + unwrap_some + call maybe_wide_value + i64 22 + fn option_wide_guarded_payload(value: (option i64)) -> i64 + if + is_some + var value + unwrap_some + var value + i64 0 + fn option_float_direct_payload() -> f64 + unwrap_some + some f64 + float 42.5 + fn option_float_param_payload(value: (option f64)) -> f64 + unwrap_some + var value + fn option_float_local_payload() -> f64 + local let value: (option f64) + call maybe_float_value + float 21.5 + unwrap_some + var value + fn option_float_call_payload() -> f64 + unwrap_some + call maybe_float_value + float 22.5 + fn option_float_guarded_payload(value: (option f64)) -> f64 + if + is_some + var value + unwrap_some + var value + float 0 + fn option_flag_direct_payload() -> bool + unwrap_some + some bool + bool true + fn option_flag_param_payload(value: (option bool)) -> bool + unwrap_some + var value + fn option_flag_local_payload() -> bool + local let value: (option bool) + call maybe_flag_value + bool true + unwrap_some + var value + fn option_flag_call_payload() -> bool + unwrap_some + call maybe_flag_value + bool true + fn option_flag_guarded_payload(value: (option bool)) -> bool + if + is_some + var value + unwrap_some + var value + bool false + fn option_string_direct_payload() -> string + unwrap_some + some string + string "slovo" + fn option_string_param_payload(value: (option string)) -> string + unwrap_some + var value + fn option_string_local_payload() -> string + local let value: (option string) + call maybe_string_value + string "oak" + unwrap_some + var value + fn option_string_call_payload() -> string + unwrap_some + call maybe_string_value + string "pine" + fn option_string_guarded_payload(value: (option string)) -> string + if + is_some + var value + unwrap_some + var value + string "fallback" + fn result_ok_direct_payload() -> i32 + unwrap_ok + ok i32 i32 + int 30 + fn result_err_direct_payload() -> i32 + unwrap_err + err i32 i32 + int 7 + fn result_ok_param_payload(value: (result i32 i32)) -> i32 + unwrap_ok + var value + fn result_err_param_payload(value: (result i32 i32)) -> i32 + unwrap_err + var value + fn result_ok_local_payload() -> i32 + local let value: (result i32 i32) + call result_ok_value + int 31 + unwrap_ok + var value + fn result_err_call_payload() -> i32 + unwrap_err + call result_err_value + int 9 + fn main() -> i32 + int 0 + test "unwrap some direct constructor" + binary = + call option_direct_payload + int 42 + test "unwrap some local" + binary = + call option_local_payload + int 21 + test "unwrap some call" + binary = + call option_call_payload + int 22 + test "unwrap some parameter" + binary = + call option_param_payload + call maybe_value + int 23 + int 23 + test "guarded unwrap some present" + binary = + call option_guarded_payload + call maybe_value + int 24 + int 24 + test "guarded unwrap some absent" + binary = + call option_guarded_payload + call maybe_empty + int 0 + test "unwrap some i64 direct constructor" + binary = + call option_wide_direct_payload + i64 2147483648 + test "unwrap some i64 local" + binary = + call option_wide_local_payload + i64 21 + test "unwrap some i64 call" + binary = + call option_wide_call_payload + i64 22 + test "unwrap some i64 parameter" + binary = + call option_wide_param_payload + call maybe_wide_value + i64 23 + i64 23 + test "guarded unwrap some i64 present" + binary = + call option_wide_guarded_payload + call maybe_wide_value + i64 24 + i64 24 + test "guarded unwrap some i64 absent" + binary = + call option_wide_guarded_payload + call maybe_wide_empty + i64 0 + test "unwrap some f64 direct constructor" + binary = + call option_float_direct_payload + float 42.5 + test "unwrap some f64 local" + binary = + call option_float_local_payload + float 21.5 + test "unwrap some f64 call" + binary = + call option_float_call_payload + float 22.5 + test "unwrap some f64 parameter" + binary = + call option_float_param_payload + call maybe_float_value + float 23.5 + float 23.5 + test "guarded unwrap some f64 present" + binary = + call option_float_guarded_payload + call maybe_float_value + float 24.5 + float 24.5 + test "guarded unwrap some f64 absent" + binary = + call option_float_guarded_payload + call maybe_float_empty + float 0 + test "unwrap some bool direct constructor" + call option_flag_direct_payload + test "unwrap some bool local" + call option_flag_local_payload + test "unwrap some bool call" + call option_flag_call_payload + test "unwrap some bool parameter" + call option_flag_param_payload + call maybe_flag_value + bool true + test "guarded unwrap some bool present" + call option_flag_guarded_payload + call maybe_flag_value + bool true + test "guarded unwrap some bool absent" + if + call option_flag_guarded_payload + call maybe_flag_empty + bool false + bool true + test "unwrap some string direct constructor" + binary = + call option_string_direct_payload + string "slovo" + test "unwrap some string local" + binary = + call option_string_local_payload + string "oak" + test "unwrap some string call" + binary = + call option_string_call_payload + string "pine" + test "unwrap some string parameter" + binary = + call option_string_param_payload + call maybe_string_value + string "birch" + string "birch" + test "guarded unwrap some string present" + binary = + call option_string_guarded_payload + call maybe_string_value + string "cedar" + string "cedar" + test "guarded unwrap some string absent" + binary = + call option_string_guarded_payload + call maybe_string_empty + string "fallback" + test "unwrap ok direct constructor" + binary = + call result_ok_direct_payload + int 30 + test "unwrap err direct constructor" + binary = + call result_err_direct_payload + int 7 + test "unwrap ok parameter" + binary = + call result_ok_param_payload + call result_ok_value + int 32 + int 32 + test "unwrap err parameter" + binary = + call result_err_param_payload + call result_err_value + int 8 + int 8 + test "unwrap ok local" + binary = + call result_ok_local_payload + int 31 + test "unwrap err call" + binary = + call result_err_call_payload + int 9 diff --git a/tests/option-result.checked.lower b/tests/option-result.checked.lower new file mode 100644 index 0000000..bd6e3c2 --- /dev/null +++ b/tests/option-result.checked.lower @@ -0,0 +1,34 @@ +program main + fn maybe_value() -> (option i32) + some : (option i32) + int 42 : i32 + fn maybe_empty() -> (option i32) + none : (option i32) + fn maybe_wide_value() -> (option i64) + some : (option i64) + i64 2147483648 : i64 + fn maybe_wide_empty() -> (option i64) + none : (option i64) + fn maybe_float_value() -> (option f64) + some : (option f64) + float 42.5 : f64 + fn maybe_float_empty() -> (option f64) + none : (option f64) + fn maybe_flag_value() -> (option bool) + some : (option bool) + bool true : bool + fn maybe_flag_empty() -> (option bool) + none : (option bool) + fn maybe_string_value() -> (option string) + some : (option string) + string "slovo" : string + fn maybe_string_empty() -> (option string) + none : (option string) + fn result_ok() -> (result i32 i32) + ok : (result i32 i32) + int 42 : i32 + fn result_err() -> (result i32 i32) + err : (result i32 i32) + int 7 : i32 + fn main() -> i32 + int 0 : i32 diff --git a/tests/option-result.slo b/tests/option-result.slo new file mode 100644 index 0000000..d54a5fe --- /dev/null +++ b/tests/option-result.slo @@ -0,0 +1,40 @@ +(module main) + +(fn maybe_value () -> (option i32) + (some i32 42)) + +(fn maybe_empty () -> (option i32) + (none i32)) + +(fn maybe_wide_value () -> (option i64) + (some i64 2147483648i64)) + +(fn maybe_wide_empty () -> (option i64) + (none i64)) + +(fn maybe_float_value () -> (option f64) + (some f64 42.5)) + +(fn maybe_float_empty () -> (option f64) + (none f64)) + +(fn maybe_flag_value () -> (option bool) + (some bool true)) + +(fn maybe_flag_empty () -> (option bool) + (none bool)) + +(fn maybe_string_value () -> (option string) + (some string "slovo")) + +(fn maybe_string_empty () -> (option string) + (none string)) + +(fn result_ok () -> (result i32 i32) + (ok i32 i32 42)) + +(fn result_err () -> (result i32 i32) + (err i32 i32 7)) + +(fn main () -> i32 + 0) diff --git a/tests/option-result.surface.lower b/tests/option-result.surface.lower new file mode 100644 index 0000000..de29eea --- /dev/null +++ b/tests/option-result.surface.lower @@ -0,0 +1,34 @@ +program main + fn maybe_value() -> (option i32) + some i32 + int 42 + fn maybe_empty() -> (option i32) + none i32 + fn maybe_wide_value() -> (option i64) + some i64 + i64 2147483648 + fn maybe_wide_empty() -> (option i64) + none i64 + fn maybe_float_value() -> (option f64) + some f64 + float 42.5 + fn maybe_float_empty() -> (option f64) + none f64 + fn maybe_flag_value() -> (option bool) + some bool + bool true + fn maybe_flag_empty() -> (option bool) + none bool + fn maybe_string_value() -> (option string) + some string + string "slovo" + fn maybe_string_empty() -> (option string) + none string + fn result_ok() -> (result i32 i32) + ok i32 i32 + int 42 + fn result_err() -> (result i32 i32) + err i32 i32 + int 7 + fn main() -> i32 + int 0 diff --git a/tests/option-unwrap-non-option.diag b/tests/option-unwrap-non-option.diag new file mode 100644 index 0000000..5c91315 --- /dev/null +++ b/tests/option-unwrap-non-option.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code OptionUnwrapTypeMismatch) + (message "`unwrap_some` requires an `(option i32)`, `(option i64)`, `(option f64)`, `(option bool)`, or `(option string)` value") + (file "") + (span + (bytes 50 51) + (range 5 16 5 17) + ) + (expected "(option i32), (option i64), (option f64), (option bool), or (option string)") + (found "i32") +) diff --git a/tests/owned-string-concat.checked.lower b/tests/owned-string-concat.checked.lower new file mode 100644 index 0000000..ecf1fec --- /dev/null +++ b/tests/owned-string-concat.checked.lower @@ -0,0 +1,31 @@ +program main + fn join(left: string, right: string) -> string + call std.string.concat : string + var left : string + var right : string + fn greeting() -> string + call std.string.concat : string + string "hello, " : string + string "slovo" : string + fn greeting_len() -> i32 + call std.string.len : i32 + call greeting : string + fn main() -> i32 + call std.io.print_string : unit + call join : string + string "hello, " : string + string "slovo" : string + call std.io.print_i32 : unit + call std.string.len : i32 + call join : string + string "ab" : string + string "cd" : string + int 0 : i32 + test "owned string concat equality" + binary = : bool + call greeting : string + string "hello, slovo" : string + test "owned string concat length" + binary = : bool + call greeting_len : i32 + int 12 : i32 diff --git a/tests/owned-string-concat.slo b/tests/owned-string-concat.slo new file mode 100644 index 0000000..808c345 --- /dev/null +++ b/tests/owned-string-concat.slo @@ -0,0 +1,21 @@ +(module main) + +(fn join ((left string) (right string)) -> string + (std.string.concat left right)) + +(fn greeting () -> string + (std.string.concat "hello, " "slovo")) + +(fn greeting_len () -> i32 + (std.string.len (greeting))) + +(test "owned string concat equality" + (= (greeting) "hello, slovo")) + +(test "owned string concat length" + (= (greeting_len) 12)) + +(fn main () -> i32 + (std.io.print_string (join "hello, " "slovo")) + (std.io.print_i32 (std.string.len (join "ab" "cd"))) + 0) diff --git a/tests/owned-string-concat.surface.lower b/tests/owned-string-concat.surface.lower new file mode 100644 index 0000000..750c5b5 --- /dev/null +++ b/tests/owned-string-concat.surface.lower @@ -0,0 +1,31 @@ +program main + fn join(left: string, right: string) -> string + call std.string.concat + var left + var right + fn greeting() -> string + call std.string.concat + string "hello, " + string "slovo" + fn greeting_len() -> i32 + call std.string.len + call greeting + fn main() -> i32 + call std.io.print_string + call join + string "hello, " + string "slovo" + call std.io.print_i32 + call std.string.len + call join + string "ab" + string "cd" + int 0 + test "owned string concat equality" + binary = + call greeting + string "hello, slovo" + test "owned string concat length" + binary = + call greeting_len + int 12 diff --git a/tests/parser-atoms.sexpr b/tests/parser-atoms.sexpr new file mode 100644 index 0000000..41248fd --- /dev/null +++ b/tests/parser-atoms.sexpr @@ -0,0 +1,9 @@ +list + ident module + ident syntax +list + ident sample + list + float 3.5 + string "line\nquote\"slash\\" + arrow -> diff --git a/tests/primitive-struct-fields.checked.lower b/tests/primitive-struct-fields.checked.lower new file mode 100644 index 0000000..1543e5a --- /dev/null +++ b/tests/primitive-struct-fields.checked.lower @@ -0,0 +1,99 @@ +program main + struct PrimitiveRecord + field id: i32 + field active: bool + field label: string + fn make_record(id: i32, label: string) -> PrimitiveRecord + construct PrimitiveRecord : PrimitiveRecord + field id + var id : i32 + field active + binary = : bool + var id : i32 + int 7 : i32 + field label + var label : string + fn expected_record() -> PrimitiveRecord + call make_record : PrimitiveRecord + int 7 : i32 + string "alpha" : string + fn inactive_record() -> PrimitiveRecord + construct PrimitiveRecord : PrimitiveRecord + field id + int 3 : i32 + field active + bool false : bool + field label + string "beta" : string + fn echo_record(record: PrimitiveRecord) -> PrimitiveRecord + var record : PrimitiveRecord + fn local_record(label: string) -> PrimitiveRecord + local let record : unit + call make_record : PrimitiveRecord + int 7 : i32 + var label : string + call echo_record : PrimitiveRecord + var record : PrimitiveRecord + fn record_id(record: PrimitiveRecord) -> i32 + field-access id : i32 + var record : PrimitiveRecord + fn record_active(record: PrimitiveRecord) -> bool + field-access active : bool + var record : PrimitiveRecord + fn record_label(record: PrimitiveRecord) -> string + field-access label : string + var record : PrimitiveRecord + fn label_matches(record: PrimitiveRecord, label: string) -> bool + binary = : bool + field-access label : string + var record : PrimitiveRecord + var label : string + fn active_score(record: PrimitiveRecord) -> i32 + if : i32 + field-access active : bool + var record : PrimitiveRecord + field-access id : i32 + var record : PrimitiveRecord + int 0 : i32 + fn label_len(record: PrimitiveRecord) -> i32 + call std.string.len : i32 + field-access label : string + var record : PrimitiveRecord + fn main() -> i32 + call active_score : i32 + call local_record : PrimitiveRecord + string "alpha" : string + test "primitive struct i32 field access" + binary = : bool + call record_id : i32 + call expected_record : PrimitiveRecord + int 7 : i32 + test "primitive struct bool field predicate" + call record_active : bool + call expected_record : PrimitiveRecord + test "primitive struct bool field false branch" + binary = : bool + call active_score : i32 + call inactive_record : PrimitiveRecord + int 0 : i32 + test "primitive struct string field equality" + call label_matches : bool + call expected_record : PrimitiveRecord + string "alpha" : string + test "primitive struct string field length" + binary = : bool + call label_len : i32 + call expected_record : PrimitiveRecord + int 5 : i32 + test "primitive struct local param return call flow" + binary = : bool + call active_score : i32 + call local_record : PrimitiveRecord + string "alpha" : string + int 7 : i32 + test "primitive struct string field access return" + binary = : bool + call record_label : string + call local_record : PrimitiveRecord + string "alpha" : string + string "alpha" : string diff --git a/tests/primitive-struct-fields.slo b/tests/primitive-struct-fields.slo new file mode 100644 index 0000000..5083c6b --- /dev/null +++ b/tests/primitive-struct-fields.slo @@ -0,0 +1,66 @@ +(module main) + +(struct PrimitiveRecord + (id i32) + (active bool) + (label string)) + +(fn make_record ((id i32) (label string)) -> PrimitiveRecord + (PrimitiveRecord (id id) (active (= id 7)) (label label))) + +(fn expected_record () -> PrimitiveRecord + (make_record 7 "alpha")) + +(fn inactive_record () -> PrimitiveRecord + (PrimitiveRecord (id 3) (active false) (label "beta"))) + +(fn echo_record ((record PrimitiveRecord)) -> PrimitiveRecord + record) + +(fn local_record ((label string)) -> PrimitiveRecord + (let record PrimitiveRecord (make_record 7 label)) + (echo_record record)) + +(fn record_id ((record PrimitiveRecord)) -> i32 + (. record id)) + +(fn record_active ((record PrimitiveRecord)) -> bool + (. record active)) + +(fn record_label ((record PrimitiveRecord)) -> string + (. record label)) + +(fn label_matches ((record PrimitiveRecord) (label string)) -> bool + (= (. record label) label)) + +(fn active_score ((record PrimitiveRecord)) -> i32 + (if (. record active) + (. record id) + 0)) + +(fn label_len ((record PrimitiveRecord)) -> i32 + (std.string.len (. record label))) + +(test "primitive struct i32 field access" + (= (record_id (expected_record)) 7)) + +(test "primitive struct bool field predicate" + (record_active (expected_record))) + +(test "primitive struct bool field false branch" + (= (active_score (inactive_record)) 0)) + +(test "primitive struct string field equality" + (label_matches (expected_record) "alpha")) + +(test "primitive struct string field length" + (= (label_len (expected_record)) 5)) + +(test "primitive struct local param return call flow" + (= (active_score (local_record "alpha")) 7)) + +(test "primitive struct string field access return" + (= (record_label (local_record "alpha")) "alpha")) + +(fn main () -> i32 + (active_score (local_record "alpha"))) diff --git a/tests/primitive-struct-fields.surface.lower b/tests/primitive-struct-fields.surface.lower new file mode 100644 index 0000000..df574f1 --- /dev/null +++ b/tests/primitive-struct-fields.surface.lower @@ -0,0 +1,99 @@ +program main + struct PrimitiveRecord + field id: i32 + field active: bool + field label: string + fn make_record(id: i32, label: string) -> PrimitiveRecord + construct PrimitiveRecord + field id + var id + field active + binary = + var id + int 7 + field label + var label + fn expected_record() -> PrimitiveRecord + call make_record + int 7 + string "alpha" + fn inactive_record() -> PrimitiveRecord + construct PrimitiveRecord + field id + int 3 + field active + bool false + field label + string "beta" + fn echo_record(record: PrimitiveRecord) -> PrimitiveRecord + var record + fn local_record(label: string) -> PrimitiveRecord + local let record: PrimitiveRecord + call make_record + int 7 + var label + call echo_record + var record + fn record_id(record: PrimitiveRecord) -> i32 + field-access id + var record + fn record_active(record: PrimitiveRecord) -> bool + field-access active + var record + fn record_label(record: PrimitiveRecord) -> string + field-access label + var record + fn label_matches(record: PrimitiveRecord, label: string) -> bool + binary = + field-access label + var record + var label + fn active_score(record: PrimitiveRecord) -> i32 + if + field-access active + var record + field-access id + var record + int 0 + fn label_len(record: PrimitiveRecord) -> i32 + call std.string.len + field-access label + var record + fn main() -> i32 + call active_score + call local_record + string "alpha" + test "primitive struct i32 field access" + binary = + call record_id + call expected_record + int 7 + test "primitive struct bool field predicate" + call record_active + call expected_record + test "primitive struct bool field false branch" + binary = + call active_score + call inactive_record + int 0 + test "primitive struct string field equality" + call label_matches + call expected_record + string "alpha" + test "primitive struct string field length" + binary = + call label_len + call expected_record + int 5 + test "primitive struct local param return call flow" + binary = + call active_score + call local_record + string "alpha" + int 7 + test "primitive struct string field access return" + binary = + call record_label + call local_record + string "alpha" + string "alpha" diff --git a/tests/print-bool-arity.diag b/tests/print-bool-arity.diag new file mode 100644 index 0000000..0af72b2 --- /dev/null +++ b/tests/print-bool-arity.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ArityMismatch) + (message "function `print_bool` called with wrong number of arguments") + (file "") + (span + (bytes 37 60) + (range 5 3 5 26) + ) + (expected "1") + (found "2") +) diff --git a/tests/print-bool-type.diag b/tests/print-bool-type.diag new file mode 100644 index 0000000..59f12d1 --- /dev/null +++ b/tests/print-bool-type.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code TypeMismatch) + (message "cannot call `print_bool` with argument of wrong type") + (file "") + (span + (bytes 49 50) + (range 5 15 5 16) + ) + (expected "bool") + (found "i32") +) diff --git a/tests/print-bool.checked.lower b/tests/print-bool.checked.lower new file mode 100644 index 0000000..6f9c228 --- /dev/null +++ b/tests/print-bool.checked.lower @@ -0,0 +1,11 @@ +program main + fn main() -> i32 + call print_bool : unit + bool true : bool + call print_bool : unit + bool false : bool + call print_bool : unit + binary = : bool + string "slovo" : string + string "slovo" : string + int 0 : i32 diff --git a/tests/print-bool.slo b/tests/print-bool.slo new file mode 100644 index 0000000..04d8439 --- /dev/null +++ b/tests/print-bool.slo @@ -0,0 +1,7 @@ +(module main) + +(fn main () -> i32 + (print_bool true) + (print_bool false) + (print_bool (= "slovo" "slovo")) + 0) diff --git a/tests/print-bool.surface.lower b/tests/print-bool.surface.lower new file mode 100644 index 0000000..27e39f7 --- /dev/null +++ b/tests/print-bool.surface.lower @@ -0,0 +1,11 @@ +program main + fn main() -> i32 + call print_bool + bool true + call print_bool + bool false + call print_bool + binary = + string "slovo" + string "slovo" + int 0 diff --git a/tests/random.checked.lower b/tests/random.checked.lower new file mode 100644 index 0000000..66f3ba8 --- /dev/null +++ b/tests/random.checked.lower @@ -0,0 +1,16 @@ +program main + fn random_i32() -> i32 + call std.random.i32 : i32 + fn random_is_non_negative() -> bool + if : bool + binary < : bool + call random_i32 : i32 + int 0 : i32 + bool false : bool + bool true : bool + fn main() -> i32 + call std.io.print_bool : unit + call random_is_non_negative : bool + int 0 : i32 + test "random i32 is non-negative" + call random_is_non_negative : bool diff --git a/tests/random.slo b/tests/random.slo new file mode 100644 index 0000000..fb387ce --- /dev/null +++ b/tests/random.slo @@ -0,0 +1,16 @@ +(module main) + +(fn random_i32 () -> i32 + (std.random.i32)) + +(fn random_is_non_negative () -> bool + (if (< (random_i32) 0) + false + true)) + +(test "random i32 is non-negative" + (random_is_non_negative)) + +(fn main () -> i32 + (std.io.print_bool (random_is_non_negative)) + 0) diff --git a/tests/random.surface.lower b/tests/random.surface.lower new file mode 100644 index 0000000..4e72507 --- /dev/null +++ b/tests/random.surface.lower @@ -0,0 +1,16 @@ +program main + fn random_i32() -> i32 + call std.random.i32 + fn random_is_non_negative() -> bool + if + binary < + call random_i32 + int 0 + bool false + bool true + fn main() -> i32 + call std.io.print_bool + call random_is_non_negative + int 0 + test "random i32 is non-negative" + call random_is_non_negative diff --git a/tests/recursive-struct-field.diag b/tests/recursive-struct-field.diag new file mode 100644 index 0000000..c138185 --- /dev/null +++ b/tests/recursive-struct-field.diag @@ -0,0 +1,21 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code RecursiveStructFieldUnsupported) + (message "recursive struct fields are not supported (`Node -> Node`)") + (file "") + (span + (bytes 37 41) + (range 5 9 5 13) + ) + (hint "flatten the struct graph or remove the recursive field") + (related + (span + (file "") + (bytes 24 28) + (range 4 9 4 13) + (message "recursive struct declaration") + ) + ) +) diff --git a/tests/result-constructor-type-mismatch.diag b/tests/result-constructor-type-mismatch.diag new file mode 100644 index 0000000..c4154ce --- /dev/null +++ b/tests/result-constructor-type-mismatch.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code TypeMismatch) + (message "result `err` value has the wrong type") + (file "") + (span + (bytes 62 66) + (range 5 16 5 20) + ) + (expected "i32") + (found "bool") +) diff --git a/tests/result-err-unwrap-non-result.diag b/tests/result-err-unwrap-non-result.diag new file mode 100644 index 0000000..02000c0 --- /dev/null +++ b/tests/result-err-unwrap-non-result.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ResultUnwrapTypeMismatch) + (message "`unwrap_err` requires a `(result i32 i32)`, `(result i64 i32)`, `(result f64 i32)`, `(result bool i32)`, or `(result string i32)` value") + (file "") + (span + (bytes 49 50) + (range 5 15 5 16) + ) + (expected "(result i32 i32), (result i64 i32), (result f64 i32), (result bool i32), or (result string i32)") + (found "i32") +) diff --git a/tests/result-helpers.checked.lower b/tests/result-helpers.checked.lower new file mode 100644 index 0000000..fe4334a --- /dev/null +++ b/tests/result-helpers.checked.lower @@ -0,0 +1,93 @@ +program main + fn i32_ok() -> (result i32 i32) + ok : (result i32 i32) + int 42 : i32 + fn i32_err() -> (result i32 i32) + err : (result i32 i32) + int 7 : i32 + fn text_ok() -> (result string i32) + ok : (result string i32) + string "value" : string + fn text_err() -> (result string i32) + err : (result string i32) + int 9 : i32 + fn observe_i32_ok() -> bool + std.result.is_ok : bool + call i32_ok : (result i32 i32) + fn observe_i32_err() -> bool + std.result.is_err : bool + call i32_err : (result i32 i32) + fn unwrap_i32_ok() -> i32 + std.result.unwrap_ok : i32 + call i32_ok : (result i32 i32) + fn unwrap_i32_err() -> i32 + std.result.unwrap_err : i32 + call i32_err : (result i32 i32) + fn observe_text_ok() -> bool + std.result.is_ok : bool + call text_ok : (result string i32) + fn observe_text_err() -> bool + std.result.is_err : bool + call text_err : (result string i32) + fn unwrap_text_ok() -> string + std.result.unwrap_ok : string + call text_ok : (result string i32) + fn unwrap_text_err() -> i32 + std.result.unwrap_err : i32 + call text_err : (result string i32) + fn legacy_i32_ok() -> bool + is_ok : bool + call i32_ok : (result i32 i32) + fn legacy_text_err() -> i32 + unwrap_err : i32 + call text_err : (result string i32) + fn main() -> i32 + if : i32 + std.result.is_ok : bool + call i32_ok : (result i32 i32) + std.result.unwrap_ok : i32 + call i32_ok : (result i32 i32) + std.result.unwrap_err : i32 + call i32_err : (result i32 i32) + test "std result i32 observers" + if : bool + std.result.is_ok : bool + call i32_ok : (result i32 i32) + std.result.is_err : bool + call i32_err : (result i32 i32) + bool false : bool + test "std result i32 unwraps" + binary = : bool + binary + : i32 + std.result.unwrap_ok : i32 + call i32_ok : (result i32 i32) + std.result.unwrap_err : i32 + call i32_err : (result i32 i32) + int 49 : i32 + test "std result string observers" + if : bool + std.result.is_ok : bool + call text_ok : (result string i32) + std.result.is_err : bool + call text_err : (result string i32) + bool false : bool + test "std result string unwraps" + if : bool + binary = : bool + std.result.unwrap_ok : string + call text_ok : (result string i32) + string "value" : string + binary = : bool + std.result.unwrap_err : i32 + call text_err : (result string i32) + int 9 : i32 + bool false : bool + test "legacy result helpers still work" + if : bool + is_ok : bool + call i32_ok : (result i32 i32) + binary = : bool + unwrap_err : i32 + call text_err : (result string i32) + int 9 : i32 + bool false : bool diff --git a/tests/result-helpers.slo b/tests/result-helpers.slo new file mode 100644 index 0000000..70ace9d --- /dev/null +++ b/tests/result-helpers.slo @@ -0,0 +1,71 @@ +(module main) + +(fn i32_ok () -> (result i32 i32) + (ok i32 i32 42)) + +(fn i32_err () -> (result i32 i32) + (err i32 i32 7)) + +(fn text_ok () -> (result string i32) + (ok string i32 "value")) + +(fn text_err () -> (result string i32) + (err string i32 9)) + +(fn observe_i32_ok () -> bool + (std.result.is_ok (i32_ok))) + +(fn observe_i32_err () -> bool + (std.result.is_err (i32_err))) + +(fn unwrap_i32_ok () -> i32 + (std.result.unwrap_ok (i32_ok))) + +(fn unwrap_i32_err () -> i32 + (std.result.unwrap_err (i32_err))) + +(fn observe_text_ok () -> bool + (std.result.is_ok (text_ok))) + +(fn observe_text_err () -> bool + (std.result.is_err (text_err))) + +(fn unwrap_text_ok () -> string + (std.result.unwrap_ok (text_ok))) + +(fn unwrap_text_err () -> i32 + (std.result.unwrap_err (text_err))) + +(fn legacy_i32_ok () -> bool + (is_ok (i32_ok))) + +(fn legacy_text_err () -> i32 + (unwrap_err (text_err))) + +(test "std result i32 observers" + (if (std.result.is_ok (i32_ok)) + (std.result.is_err (i32_err)) + false)) + +(test "std result i32 unwraps" + (= (+ (std.result.unwrap_ok (i32_ok)) (std.result.unwrap_err (i32_err))) 49)) + +(test "std result string observers" + (if (std.result.is_ok (text_ok)) + (std.result.is_err (text_err)) + false)) + +(test "std result string unwraps" + (if (= (std.result.unwrap_ok (text_ok)) "value") + (= (std.result.unwrap_err (text_err)) 9) + false)) + +(test "legacy result helpers still work" + (if (is_ok (i32_ok)) + (= (unwrap_err (text_err)) 9) + false)) + +(fn main () -> i32 + (if (std.result.is_ok (i32_ok)) + (std.result.unwrap_ok (i32_ok)) + (std.result.unwrap_err (i32_err)))) diff --git a/tests/result-helpers.surface.lower b/tests/result-helpers.surface.lower new file mode 100644 index 0000000..3d89297 --- /dev/null +++ b/tests/result-helpers.surface.lower @@ -0,0 +1,93 @@ +program main + fn i32_ok() -> (result i32 i32) + ok i32 i32 + int 42 + fn i32_err() -> (result i32 i32) + err i32 i32 + int 7 + fn text_ok() -> (result string i32) + ok string i32 + string "value" + fn text_err() -> (result string i32) + err string i32 + int 9 + fn observe_i32_ok() -> bool + std.result.is_ok + call i32_ok + fn observe_i32_err() -> bool + std.result.is_err + call i32_err + fn unwrap_i32_ok() -> i32 + std.result.unwrap_ok + call i32_ok + fn unwrap_i32_err() -> i32 + std.result.unwrap_err + call i32_err + fn observe_text_ok() -> bool + std.result.is_ok + call text_ok + fn observe_text_err() -> bool + std.result.is_err + call text_err + fn unwrap_text_ok() -> string + std.result.unwrap_ok + call text_ok + fn unwrap_text_err() -> i32 + std.result.unwrap_err + call text_err + fn legacy_i32_ok() -> bool + is_ok + call i32_ok + fn legacy_text_err() -> i32 + unwrap_err + call text_err + fn main() -> i32 + if + std.result.is_ok + call i32_ok + std.result.unwrap_ok + call i32_ok + std.result.unwrap_err + call i32_err + test "std result i32 observers" + if + std.result.is_ok + call i32_ok + std.result.is_err + call i32_err + bool false + test "std result i32 unwraps" + binary = + binary + + std.result.unwrap_ok + call i32_ok + std.result.unwrap_err + call i32_err + int 49 + test "std result string observers" + if + std.result.is_ok + call text_ok + std.result.is_err + call text_err + bool false + test "std result string unwraps" + if + binary = + std.result.unwrap_ok + call text_ok + string "value" + binary = + std.result.unwrap_err + call text_err + int 9 + bool false + test "legacy result helpers still work" + if + is_ok + call i32_ok + binary = + unwrap_err + call text_err + int 9 + bool false diff --git a/tests/result-observation-non-result.diag b/tests/result-observation-non-result.diag new file mode 100644 index 0000000..b39a84d --- /dev/null +++ b/tests/result-observation-non-result.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ResultObservationTypeMismatch) + (message "`is_ok` requires a `(result i32 i32)`, `(result i64 i32)`, `(result f64 i32)`, `(result bool i32)`, or `(result string i32)` value") + (file "") + (span + (bytes 45 46) + (range 5 10 5 11) + ) + (expected "(result i32 i32), (result i64 i32), (result f64 i32), (result bool i32), or (result string i32)") + (found "i32") +) diff --git a/tests/result-ok-unwrap-non-result.diag b/tests/result-ok-unwrap-non-result.diag new file mode 100644 index 0000000..ba2b13c --- /dev/null +++ b/tests/result-ok-unwrap-non-result.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ResultUnwrapTypeMismatch) + (message "`unwrap_ok` requires a `(result i32 i32)`, `(result i64 i32)`, `(result f64 i32)`, `(result bool i32)`, or `(result string i32)` value") + (file "") + (span + (bytes 48 49) + (range 5 14 5 15) + ) + (expected "(result i32 i32), (result i64 i32), (result f64 i32), (result bool i32), or (result string i32)") + (found "i32") +) diff --git a/tests/set-immutable-local.diag b/tests/set-immutable-local.diag new file mode 100644 index 0000000..8844ac9 --- /dev/null +++ b/tests/set-immutable-local.diag @@ -0,0 +1,13 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code CannotAssignImmutableLocal) + (message "cannot assign to immutable local `x`") + (file "") + (span + (bytes 58 59) + (range 6 8 6 9) + ) + (hint "declare it with `var` to make it mutable") +) diff --git a/tests/set-parameter.diag b/tests/set-parameter.diag new file mode 100644 index 0000000..57ea180 --- /dev/null +++ b/tests/set-parameter.diag @@ -0,0 +1,12 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code CannotAssignParameter) + (message "cannot assign to parameter `value`") + (file "") + (span + (bytes 51 56) + (range 5 8 5 13) + ) +) diff --git a/tests/set-type-mismatch.diag b/tests/set-type-mismatch.diag new file mode 100644 index 0000000..0c8b990 --- /dev/null +++ b/tests/set-type-mismatch.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code TypeMismatch) + (message "cannot assign value of wrong type to `x`") + (file "") + (span + (bytes 60 64) + (range 6 10 6 14) + ) + (expected "i32") + (found "bool") +) diff --git a/tests/set-unknown-local.diag b/tests/set-unknown-local.diag new file mode 100644 index 0000000..20a2415 --- /dev/null +++ b/tests/set-unknown-local.diag @@ -0,0 +1,12 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnknownVariable) + (message "unknown variable `missing`") + (file "") + (span + (bytes 42 49) + (range 5 8 5 15) + ) +) diff --git a/tests/single-file-main-i64-return.diag b/tests/single-file-main-i64-return.diag new file mode 100644 index 0000000..858d45a --- /dev/null +++ b/tests/single-file-main-i64-return.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code SingleFileMainSignature) + (message "single-file `main` must have no parameters and return `i32`") + (file "") + (span + (bytes 16 42) + (range 4 1 5 8) + ) + (expected "fn main () -> i32") + (found "fn main () -> i64") + (hint "return an i32 exit status from main; use helper functions for wider values") +) diff --git a/tests/standard-io-host-env.checked.lower b/tests/standard-io-host-env.checked.lower new file mode 100644 index 0000000..357b7db --- /dev/null +++ b/tests/standard-io-host-env.checked.lower @@ -0,0 +1,27 @@ +program main + fn main() -> i32 + call std.io.eprint : unit + string "diagnostic" : string + local let arg_count : unit + call std.process.argc : i32 + local let first_arg : unit + call std.process.arg : string + int 0 : i32 + local let env_value : unit + call std.env.get : string + string "GLAGOL_EXP_3_ENV" : string + local let input : unit + call std.fs.read_text : string + string "glagol-exp-3-input.txt" : string + local let status : unit + call std.fs.write_text : i32 + string "glagol-exp-3-output.txt" : string + var input : string + binary + : i32 + var arg_count : i32 + binary + : i32 + var status : i32 + call std.string.len : i32 + call std.string.concat : string + var first_arg : string + var env_value : string diff --git a/tests/standard-io-host-env.slo b/tests/standard-io-host-env.slo new file mode 100644 index 0000000..2de8696 --- /dev/null +++ b/tests/standard-io-host-env.slo @@ -0,0 +1,10 @@ +(module main) + +(fn main () -> i32 + (std.io.eprint "diagnostic") + (let arg_count i32 (std.process.argc)) + (let first_arg string (std.process.arg 0)) + (let env_value string (std.env.get "GLAGOL_EXP_3_ENV")) + (let input string (std.fs.read_text "glagol-exp-3-input.txt")) + (let status i32 (std.fs.write_text "glagol-exp-3-output.txt" input)) + (+ arg_count (+ status (std.string.len (std.string.concat first_arg env_value))))) diff --git a/tests/standard-io-host-env.surface.lower b/tests/standard-io-host-env.surface.lower new file mode 100644 index 0000000..e52eb9f --- /dev/null +++ b/tests/standard-io-host-env.surface.lower @@ -0,0 +1,27 @@ +program main + fn main() -> i32 + call std.io.eprint + string "diagnostic" + local let arg_count: i32 + call std.process.argc + local let first_arg: string + call std.process.arg + int 0 + local let env_value: string + call std.env.get + string "GLAGOL_EXP_3_ENV" + local let input: string + call std.fs.read_text + string "glagol-exp-3-input.txt" + local let status: i32 + call std.fs.write_text + string "glagol-exp-3-output.txt" + var input + binary + + var arg_count + binary + + var status + call std.string.len + call std.string.concat + var first_arg + var env_value diff --git a/tests/standard-runtime.checked.lower b/tests/standard-runtime.checked.lower new file mode 100644 index 0000000..c47b229 --- /dev/null +++ b/tests/standard-runtime.checked.lower @@ -0,0 +1,31 @@ +program main + fn label() -> string + string "standard" : string + fn echo(value: string) -> string + var value : string + fn label_len() -> i32 + call std.string.len : i32 + call echo : string + string "standard" : string + fn main() -> i32 + call std.io.print_string : unit + call echo : string + string "standard" : string + call std.io.print_bool : unit + binary = : bool + call echo : string + string "standard" : string + string "standard" : string + call std.io.print_i32 : unit + call std.string.len : i32 + string "standard" : string + int 0 : i32 + test "std string equality" + binary = : bool + call echo : string + string "standard" : string + string "standard" : string + test "std string byte length" + binary = : bool + call label_len : i32 + int 8 : i32 diff --git a/tests/standard-runtime.slo b/tests/standard-runtime.slo new file mode 100644 index 0000000..607fd52 --- /dev/null +++ b/tests/standard-runtime.slo @@ -0,0 +1,22 @@ +(module main) + +(fn label () -> string + "standard") + +(fn echo ((value string)) -> string + value) + +(fn label_len () -> i32 + (std.string.len (echo "standard"))) + +(test "std string equality" + (= (echo "standard") "standard")) + +(test "std string byte length" + (= (label_len) 8)) + +(fn main () -> i32 + (std.io.print_string (echo "standard")) + (std.io.print_bool (= (echo "standard") "standard")) + (std.io.print_i32 (std.string.len "standard")) + 0) diff --git a/tests/standard-runtime.surface.lower b/tests/standard-runtime.surface.lower new file mode 100644 index 0000000..d59fc7c --- /dev/null +++ b/tests/standard-runtime.surface.lower @@ -0,0 +1,31 @@ +program main + fn label() -> string + string "standard" + fn echo(value: string) -> string + var value + fn label_len() -> i32 + call std.string.len + call echo + string "standard" + fn main() -> i32 + call std.io.print_string + call echo + string "standard" + call std.io.print_bool + binary = + call echo + string "standard" + string "standard" + call std.io.print_i32 + call std.string.len + string "standard" + int 0 + test "std string equality" + binary = + call echo + string "standard" + string "standard" + test "std string byte length" + binary = + call label_len + int 8 diff --git a/tests/std-abi-layout-unsupported.diag b/tests/std-abi-layout-unsupported.diag new file mode 100644 index 0000000..cd6f5c0 --- /dev/null +++ b/tests/std-abi-layout-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.abi.layout` is not supported") + (file "") + (span + (bytes 38 52) + (range 5 4 5 18) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.abi.layout") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-async-spawn-unsupported.diag b/tests/std-async-spawn-unsupported.diag new file mode 100644 index 0000000..0e72136 --- /dev/null +++ b/tests/std-async-spawn-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.async.spawn` is not supported") + (file "") + (span + (bytes 38 53) + (range 5 4 5 19) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.async.spawn") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-env-get-result-type.diag b/tests/std-env-get-result-type.diag new file mode 100644 index 0000000..5cf9673 --- /dev/null +++ b/tests/std-env-get-result-type.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code TypeMismatch) + (message "cannot call `std.env.get_result` with argument of wrong type") + (file "") + (span + (bytes 57 58) + (range 5 23 5 24) + ) + (expected "string") + (found "i32") +) diff --git a/tests/std-env-get-type.diag b/tests/std-env-get-type.diag new file mode 100644 index 0000000..8a49dc1 --- /dev/null +++ b/tests/std-env-get-type.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code TypeMismatch) + (message "cannot call `std.env.get` with argument of wrong type") + (file "") + (span + (bytes 53 54) + (range 5 16 5 17) + ) + (expected "string") + (found "i32") +) diff --git a/tests/std-fs-list-dir-unsupported.diag b/tests/std-fs-list-dir-unsupported.diag new file mode 100644 index 0000000..1dbacd2 --- /dev/null +++ b/tests/std-fs-list-dir-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.fs.list_dir` is not supported") + (file "") + (span + (bytes 38 53) + (range 5 4 5 19) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.fs.list_dir") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-fs-read-binary-unsupported-alias.diag b/tests/std-fs-read-binary-unsupported-alias.diag new file mode 100644 index 0000000..86cb3ef --- /dev/null +++ b/tests/std-fs-read-binary-unsupported-alias.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.fs.read_binary` is not supported") + (file "") + (span + (bytes 41 59) + (range 5 4 5 22) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.fs.read_binary") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-fs-read-text-helper-shadow.diag b/tests/std-fs-read-text-helper-shadow.diag new file mode 100644 index 0000000..5efc811 --- /dev/null +++ b/tests/std-fs-read-text-helper-shadow.diag @@ -0,0 +1,12 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code DuplicateFunction) + (message "compiler-known callable `__glagol_fs_read_text` is reserved") + (file "") + (span + (bytes 16 84) + (range 4 1 5 17) + ) +) diff --git a/tests/std-fs-write-text-arity.diag b/tests/std-fs-write-text-arity.diag new file mode 100644 index 0000000..7c91cab --- /dev/null +++ b/tests/std-fs-write-text-arity.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ArityMismatch) + (message "function `std.fs.write_text` called with wrong number of arguments") + (file "") + (span + (bytes 37 63) + (range 5 3 5 29) + ) + (expected "2") + (found "1") +) diff --git a/tests/std-fs-write-text-result-arity.diag b/tests/std-fs-write-text-result-arity.diag new file mode 100644 index 0000000..fc843af --- /dev/null +++ b/tests/std-fs-write-text-result-arity.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ArityMismatch) + (message "function `std.fs.write_text_result` called with wrong number of arguments") + (file "") + (span + (bytes 37 70) + (range 5 3 5 36) + ) + (expected "2") + (found "1") +) diff --git a/tests/std-fs-write-text-result-type.diag b/tests/std-fs-write-text-result-type.diag new file mode 100644 index 0000000..dfbd301 --- /dev/null +++ b/tests/std-fs-write-text-result-type.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code TypeMismatch) + (message "cannot call `std.fs.write_text_result` with argument of wrong type") + (file "") + (span + (bytes 70 71) + (range 5 36 5 37) + ) + (expected "string") + (found "i32") +) diff --git a/tests/std-io-eprint-arity.diag b/tests/std-io-eprint-arity.diag new file mode 100644 index 0000000..4d68c9f --- /dev/null +++ b/tests/std-io-eprint-arity.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ArityMismatch) + (message "function `std.io.eprint` called with wrong number of arguments") + (file "") + (span + (bytes 37 60) + (range 5 3 5 26) + ) + (expected "1") + (found "2") +) diff --git a/tests/std-io-eprint-result-context.diag b/tests/std-io-eprint-result-context.diag new file mode 100644 index 0000000..e99c273 --- /dev/null +++ b/tests/std-io-eprint-result-context.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ReturnTypeMismatch) + (message "function `main` returns wrong type") + (file "") + (span + (bytes 16 66) + (range 4 1 5 32) + ) + (expected "i32") + (found "unit") +) diff --git a/tests/std-io-prompt-unsupported.diag b/tests/std-io-prompt-unsupported.diag new file mode 100644 index 0000000..63fc759 --- /dev/null +++ b/tests/std-io-prompt-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.io.prompt` is not supported") + (file "") + (span + (bytes 41 54) + (range 5 4 5 17) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.io.prompt") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-io-read-line-unsupported.diag b/tests/std-io-read-line-unsupported.diag new file mode 100644 index 0000000..a0952f5 --- /dev/null +++ b/tests/std-io-read-line-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.io.read_line` is not supported") + (file "") + (span + (bytes 38 54) + (range 5 4 5 20) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.io.read_line") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-io-read-stdin-async-unsupported.diag b/tests/std-io-read-stdin-async-unsupported.diag new file mode 100644 index 0000000..cb0daaf --- /dev/null +++ b/tests/std-io-read-stdin-async-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.io.read_stdin_async` is not supported") + (file "") + (span + (bytes 38 61) + (range 5 4 5 27) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.io.read_stdin_async") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-io-read-stdin-binary-unsupported.diag b/tests/std-io-read-stdin-binary-unsupported.diag new file mode 100644 index 0000000..73f95ad --- /dev/null +++ b/tests/std-io-read-stdin-binary-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.io.read_stdin_binary` is not supported") + (file "") + (span + (bytes 38 62) + (range 5 4 5 28) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.io.read_stdin_binary") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-io-read-stdin-bytes-unsupported.diag b/tests/std-io-read-stdin-bytes-unsupported.diag new file mode 100644 index 0000000..24f5315 --- /dev/null +++ b/tests/std-io-read-stdin-bytes-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.io.read_stdin_bytes` is not supported") + (file "") + (span + (bytes 38 61) + (range 5 4 5 27) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.io.read_stdin_bytes") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-io-read-stdin-result-arity.diag b/tests/std-io-read-stdin-result-arity.diag new file mode 100644 index 0000000..641a70a --- /dev/null +++ b/tests/std-io-read-stdin-result-arity.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ArityMismatch) + (message "function `std.io.read_stdin_result` called with wrong number of arguments") + (file "") + (span + (bytes 37 65) + (range 5 3 5 31) + ) + (expected "0") + (found "1") +) diff --git a/tests/std-io-read-stdin-result-bool-context.diag b/tests/std-io-read-stdin-result-bool-context.diag new file mode 100644 index 0000000..154cd25 --- /dev/null +++ b/tests/std-io-read-stdin-result-bool-context.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code IfConditionNotBool) + (message "`if` condition must be bool") + (file "") + (span + (bytes 41 67) + (range 5 7 5 33) + ) + (expected "bool") + (found "(result string i32)") +) diff --git a/tests/std-io-read-stdin-result-helper-shadow.diag b/tests/std-io-read-stdin-result-helper-shadow.diag new file mode 100644 index 0000000..a22ca74 --- /dev/null +++ b/tests/std-io-read-stdin-result-helper-shadow.diag @@ -0,0 +1,12 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code DuplicateFunction) + (message "compiler-known callable `__glagol_io_read_stdin_result` is reserved") + (file "") + (span + (bytes 16 97) + (range 4 1 5 22) + ) +) diff --git a/tests/std-io-read-stdin-result-name-shadow.diag b/tests/std-io-read-stdin-result-name-shadow.diag new file mode 100644 index 0000000..c15794d --- /dev/null +++ b/tests/std-io-read-stdin-result-name-shadow.diag @@ -0,0 +1,12 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code DuplicateFunction) + (message "duplicate function `std.io.read_stdin_result`") + (file "") + (span + (bytes 16 92) + (range 4 1 5 22) + ) +) diff --git a/tests/std-io-read-stdin-result-type.diag b/tests/std-io-read-stdin-result-type.diag new file mode 100644 index 0000000..139719f --- /dev/null +++ b/tests/std-io-read-stdin-result-type.diag @@ -0,0 +1,27 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code TypeMismatch) + (message "local `value` initializer has the wrong type") + (file "") + (span + (bytes 52 78) + (range 5 18 5 44) + ) + (expected "i32") + (found "(result string i32)") +) + +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnknownVariable) + (message "unknown variable `value`") + (file "") + (span + (bytes 82 87) + (range 6 3 6 8) + ) +) diff --git a/tests/std-io-read-stdin-unsupported.diag b/tests/std-io-read-stdin-unsupported.diag new file mode 100644 index 0000000..ffa172a --- /dev/null +++ b/tests/std-io-read-stdin-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.io.read_stdin` is not supported") + (file "") + (span + (bytes 38 55) + (range 5 4 5 21) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.io.read_stdin") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-io-stdin-encoding-unsupported.diag b/tests/std-io-stdin-encoding-unsupported.diag new file mode 100644 index 0000000..01ff74c --- /dev/null +++ b/tests/std-io-stdin-encoding-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.io.stdin_encoding` is not supported") + (file "") + (span + (bytes 38 59) + (range 5 4 5 25) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.io.stdin_encoding") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-io-stdin-lines-unsupported.diag b/tests/std-io-stdin-lines-unsupported.diag new file mode 100644 index 0000000..40e5d2a --- /dev/null +++ b/tests/std-io-stdin-lines-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.io.stdin_lines` is not supported") + (file "") + (span + (bytes 38 56) + (range 5 4 5 22) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.io.stdin_lines") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-io-stdin-stream-unsupported.diag b/tests/std-io-stdin-stream-unsupported.diag new file mode 100644 index 0000000..304d141 --- /dev/null +++ b/tests/std-io-stdin-stream-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.io.stdin_stream` is not supported") + (file "") + (span + (bytes 38 57) + (range 5 4 5 23) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.io.stdin_stream") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-net-connect-unsupported.diag b/tests/std-net-connect-unsupported.diag new file mode 100644 index 0000000..c5bd652 --- /dev/null +++ b/tests/std-net-connect-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.net.connect` is not supported") + (file "") + (span + (bytes 38 53) + (range 5 4 5 19) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.net.connect") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-num-cast-checked-unsupported.diag b/tests/std-num-cast-checked-unsupported.diag new file mode 100644 index 0000000..fec81d4 --- /dev/null +++ b/tests/std-num-cast-checked-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.num.cast_checked` is not supported") + (file "") + (span + (bytes 38 58) + (range 5 4 5 24) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.num.cast_checked") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-num-cast-unsupported.diag b/tests/std-num-cast-unsupported.diag new file mode 100644 index 0000000..20bbbc3 --- /dev/null +++ b/tests/std-num-cast-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.num.cast` is not supported") + (file "") + (span + (bytes 38 50) + (range 5 4 5 16) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.num.cast") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-num-f64-to-i32-result-arity.diag b/tests/std-num-f64-to-i32-result-arity.diag new file mode 100644 index 0000000..aad7e7a --- /dev/null +++ b/tests/std-num-f64-to-i32-result-arity.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ArityMismatch) + (message "function `std.num.f64_to_i32_result` called with wrong number of arguments") + (file "") + (span + (bytes 50 77) + (range 5 3 5 30) + ) + (expected "1") + (found "0") +) diff --git a/tests/std-num-f64-to-i32-result-bool-context.diag b/tests/std-num-f64-to-i32-result-bool-context.diag new file mode 100644 index 0000000..a1b9431 --- /dev/null +++ b/tests/std-num-f64-to-i32-result-bool-context.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code IfConditionNotBool) + (message "`if` condition must be bool") + (file "") + (span + (bytes 41 72) + (range 5 7 5 38) + ) + (expected "bool") + (found "(result i32 i32)") +) diff --git a/tests/std-num-f64-to-i32-result-context.diag b/tests/std-num-f64-to-i32-result-context.diag new file mode 100644 index 0000000..c0db5d2 --- /dev/null +++ b/tests/std-num-f64-to-i32-result-context.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ReturnTypeMismatch) + (message "function `main` returns wrong type") + (file "") + (span + (bytes 16 69) + (range 4 1 5 35) + ) + (expected "i32") + (found "(result i32 i32)") +) diff --git a/tests/std-num-f64-to-i32-result-name-shadow.diag b/tests/std-num-f64-to-i32-result-name-shadow.diag new file mode 100644 index 0000000..75ca968 --- /dev/null +++ b/tests/std-num-f64-to-i32-result-name-shadow.diag @@ -0,0 +1,13 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code MalformedResultConstructor) + (message "`ok` constructor must be `(ok i32 i32 value)`") + (file "") + (span + (bytes 82 88) + (range 5 3 5 9) + ) + (hint "use `(ok i32 i32 value)`") +) diff --git a/tests/std-num-f64-to-i32-result-type.diag b/tests/std-num-f64-to-i32-result-type.diag new file mode 100644 index 0000000..9a96f95 --- /dev/null +++ b/tests/std-num-f64-to-i32-result-type.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code TypeMismatch) + (message "cannot call `std.num.f64_to_i32_result` with argument of wrong type") + (file "") + (span + (bytes 77 78) + (range 5 30 5 31) + ) + (expected "f64") + (found "i32") +) diff --git a/tests/std-num-f64-to-i32-unsupported.diag b/tests/std-num-f64-to-i32-unsupported.diag new file mode 100644 index 0000000..ae6075b --- /dev/null +++ b/tests/std-num-f64-to-i32-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.num.f64_to_i32` is not supported") + (file "") + (span + (bytes 38 56) + (range 5 4 5 22) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.num.f64_to_i32") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-num-f64-to-i64-result-arity.diag b/tests/std-num-f64-to-i64-result-arity.diag new file mode 100644 index 0000000..7318f7e --- /dev/null +++ b/tests/std-num-f64-to-i64-result-arity.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ArityMismatch) + (message "function `std.num.f64_to_i64_result` called with wrong number of arguments") + (file "") + (span + (bytes 50 77) + (range 5 3 5 30) + ) + (expected "1") + (found "0") +) diff --git a/tests/std-num-f64-to-i64-result-bool-context.diag b/tests/std-num-f64-to-i64-result-bool-context.diag new file mode 100644 index 0000000..84eb7c0 --- /dev/null +++ b/tests/std-num-f64-to-i64-result-bool-context.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code IfConditionNotBool) + (message "`if` condition must be bool") + (file "") + (span + (bytes 41 72) + (range 5 7 5 38) + ) + (expected "bool") + (found "(result i64 i32)") +) diff --git a/tests/std-num-f64-to-i64-result-context.diag b/tests/std-num-f64-to-i64-result-context.diag new file mode 100644 index 0000000..c6f1a86 --- /dev/null +++ b/tests/std-num-f64-to-i64-result-context.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ReturnTypeMismatch) + (message "function `main` returns wrong type") + (file "") + (span + (bytes 16 69) + (range 4 1 5 35) + ) + (expected "i32") + (found "(result i64 i32)") +) diff --git a/tests/std-num-f64-to-i64-result-name-shadow.diag b/tests/std-num-f64-to-i64-result-name-shadow.diag new file mode 100644 index 0000000..85eb753 --- /dev/null +++ b/tests/std-num-f64-to-i64-result-name-shadow.diag @@ -0,0 +1,13 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code MalformedResultConstructor) + (message "`ok` constructor must be `(ok i32 i32 value)`") + (file "") + (span + (bytes 82 91) + (range 5 3 5 12) + ) + (hint "use `(ok i32 i32 value)`") +) diff --git a/tests/std-num-f64-to-i64-result-type.diag b/tests/std-num-f64-to-i64-result-type.diag new file mode 100644 index 0000000..c14d7a6 --- /dev/null +++ b/tests/std-num-f64-to-i64-result-type.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code TypeMismatch) + (message "cannot call `std.num.f64_to_i64_result` with argument of wrong type") + (file "") + (span + (bytes 77 78) + (range 5 30 5 31) + ) + (expected "f64") + (found "i32") +) diff --git a/tests/std-num-f64-to-i64-unsupported.diag b/tests/std-num-f64-to-i64-unsupported.diag new file mode 100644 index 0000000..e730294 --- /dev/null +++ b/tests/std-num-f64-to-i64-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.num.f64_to_i64` is not supported") + (file "") + (span + (bytes 38 56) + (range 5 4 5 22) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.num.f64_to_i64") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-num-f64-to-string-arity.diag b/tests/std-num-f64-to-string-arity.diag new file mode 100644 index 0000000..8d09233 --- /dev/null +++ b/tests/std-num-f64-to-string-arity.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ArityMismatch) + (message "function `std.num.f64_to_string` called with wrong number of arguments") + (file "") + (span + (bytes 40 63) + (range 5 3 5 26) + ) + (expected "1") + (found "0") +) diff --git a/tests/std-num-f64-to-string-bool-context.diag b/tests/std-num-f64-to-string-bool-context.diag new file mode 100644 index 0000000..d4e6edf --- /dev/null +++ b/tests/std-num-f64-to-string-bool-context.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code IfConditionNotBool) + (message "`if` condition must be bool") + (file "") + (span + (bytes 41 68) + (range 5 7 5 34) + ) + (expected "bool") + (found "string") +) diff --git a/tests/std-num-f64-to-string-context.diag b/tests/std-num-f64-to-string-context.diag new file mode 100644 index 0000000..7500b45 --- /dev/null +++ b/tests/std-num-f64-to-string-context.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ReturnTypeMismatch) + (message "function `main` returns wrong type") + (file "") + (span + (bytes 16 66) + (range 4 1 6 2) + ) + (expected "i32") + (found "string") +) diff --git a/tests/std-num-f64-to-string-helper-shadow.diag b/tests/std-num-f64-to-string-helper-shadow.diag new file mode 100644 index 0000000..71e4513 --- /dev/null +++ b/tests/std-num-f64-to-string-helper-shadow.diag @@ -0,0 +1,12 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code DuplicateFunction) + (message "compiler-known callable `__glagol_num_f64_to_string` is reserved") + (file "") + (span + (bytes 16 79) + (range 4 1 5 9) + ) +) diff --git a/tests/std-num-f64-to-string-name-shadow.diag b/tests/std-num-f64-to-string-name-shadow.diag new file mode 100644 index 0000000..4f2cc08 --- /dev/null +++ b/tests/std-num-f64-to-string-name-shadow.diag @@ -0,0 +1,12 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code DuplicateFunction) + (message "duplicate function `std.num.f64_to_string`") + (file "") + (span + (bytes 16 74) + (range 4 1 5 9) + ) +) diff --git a/tests/std-num-f64-to-string-type.diag b/tests/std-num-f64-to-string-type.diag new file mode 100644 index 0000000..e190191 --- /dev/null +++ b/tests/std-num-f64-to-string-type.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code TypeMismatch) + (message "cannot call `std.num.f64_to_string` with argument of wrong type") + (file "") + (span + (bytes 63 64) + (range 5 26 5 27) + ) + (expected "f64") + (found "i32") +) diff --git a/tests/std-num-i32-to-f64-type.diag b/tests/std-num-i32-to-f64-type.diag new file mode 100644 index 0000000..b8b3d91 --- /dev/null +++ b/tests/std-num-i32-to-f64-type.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code TypeMismatch) + (message "cannot call `std.num.i32_to_f64` with argument of wrong type") + (file "") + (span + (bytes 57 61) + (range 5 23 5 27) + ) + (expected "i32") + (found "i64") +) diff --git a/tests/std-num-i32-to-i64-arity.diag b/tests/std-num-i32-to-i64-arity.diag new file mode 100644 index 0000000..8c6809e --- /dev/null +++ b/tests/std-num-i32-to-i64-arity.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ArityMismatch) + (message "function `std.num.i32_to_i64` called with wrong number of arguments") + (file "") + (span + (bytes 37 57) + (range 5 3 5 23) + ) + (expected "1") + (found "0") +) diff --git a/tests/std-num-i32-to-i64-result-unsupported.diag b/tests/std-num-i32-to-i64-result-unsupported.diag new file mode 100644 index 0000000..b205738 --- /dev/null +++ b/tests/std-num-i32-to-i64-result-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.num.i32_to_i64_result` is not supported") + (file "") + (span + (bytes 38 63) + (range 5 4 5 29) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.num.i32_to_i64_result") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-num-i32-to-string-arity.diag b/tests/std-num-i32-to-string-arity.diag new file mode 100644 index 0000000..934267e --- /dev/null +++ b/tests/std-num-i32-to-string-arity.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ArityMismatch) + (message "function `std.num.i32_to_string` called with wrong number of arguments") + (file "") + (span + (bytes 40 63) + (range 5 3 5 26) + ) + (expected "1") + (found "0") +) diff --git a/tests/std-num-i64-to-i32-result-arity.diag b/tests/std-num-i64-to-i32-result-arity.diag new file mode 100644 index 0000000..78e7a73 --- /dev/null +++ b/tests/std-num-i64-to-i32-result-arity.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ArityMismatch) + (message "function `std.num.i64_to_i32_result` called with wrong number of arguments") + (file "") + (span + (bytes 50 77) + (range 5 3 5 30) + ) + (expected "1") + (found "0") +) diff --git a/tests/std-num-i64-to-i32-result-type.diag b/tests/std-num-i64-to-i32-result-type.diag new file mode 100644 index 0000000..13a1da0 --- /dev/null +++ b/tests/std-num-i64-to-i32-result-type.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code TypeMismatch) + (message "cannot call `std.num.i64_to_i32_result` with argument of wrong type") + (file "") + (span + (bytes 77 78) + (range 5 30 5 31) + ) + (expected "i64") + (found "i32") +) diff --git a/tests/std-num-i64-to-i32-unsupported.diag b/tests/std-num-i64-to-i32-unsupported.diag new file mode 100644 index 0000000..f16b22a --- /dev/null +++ b/tests/std-num-i64-to-i32-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.num.i64_to_i32` is not supported") + (file "") + (span + (bytes 38 56) + (range 5 4 5 22) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.num.i64_to_i32") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-num-i64-to-string-type.diag b/tests/std-num-i64-to-string-type.diag new file mode 100644 index 0000000..35ff898 --- /dev/null +++ b/tests/std-num-i64-to-string-type.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code TypeMismatch) + (message "cannot call `std.num.i64_to_string` with argument of wrong type") + (file "") + (span + (bytes 63 64) + (range 5 26 5 27) + ) + (expected "i64") + (found "i32") +) diff --git a/tests/std-num-to-string-unsupported.diag b/tests/std-num-to-string-unsupported.diag new file mode 100644 index 0000000..db288df --- /dev/null +++ b/tests/std-num-to-string-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.num.to_string` is not supported") + (file "") + (span + (bytes 38 55) + (range 5 4 5 21) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.num.to_string") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-package-load-unsupported.diag b/tests/std-package-load-unsupported.diag new file mode 100644 index 0000000..c9649b0 --- /dev/null +++ b/tests/std-package-load-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.package.load` is not supported") + (file "") + (span + (bytes 38 54) + (range 5 4 5 20) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.package.load") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-parameter-shadows-runtime.diag b/tests/std-parameter-shadows-runtime.diag new file mode 100644 index 0000000..51aebfa --- /dev/null +++ b/tests/std-parameter-shadows-runtime.diag @@ -0,0 +1,13 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ParameterShadowsCallable) + (message "parameter `std.io.print_i32` conflicts with a compiler-known callable name") + (file "") + (span + (bytes 27 43) + (range 4 12 4 28) + ) + (hint "choose a parameter name distinct from compiler-known callable names") +) diff --git a/tests/std-platform-os-unsupported.diag b/tests/std-platform-os-unsupported.diag new file mode 100644 index 0000000..410212c --- /dev/null +++ b/tests/std-platform-os-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.platform.os` is not supported") + (file "") + (span + (bytes 38 53) + (range 5 4 5 19) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.platform.os") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-print-bool-arity.diag b/tests/std-print-bool-arity.diag new file mode 100644 index 0000000..77342fb --- /dev/null +++ b/tests/std-print-bool-arity.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ArityMismatch) + (message "function `std.io.print_bool` called with wrong number of arguments") + (file "") + (span + (bytes 37 67) + (range 5 3 5 33) + ) + (expected "1") + (found "2") +) diff --git a/tests/std-print-bool-result.diag b/tests/std-print-bool-result.diag new file mode 100644 index 0000000..fab29fd --- /dev/null +++ b/tests/std-print-bool-result.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ReturnTypeMismatch) + (message "function `main` returns wrong type") + (file "") + (span + (bytes 16 62) + (range 4 1 5 28) + ) + (expected "i32") + (found "unit") +) diff --git a/tests/std-process-arg-result-arity.diag b/tests/std-process-arg-result-arity.diag new file mode 100644 index 0000000..490f87d --- /dev/null +++ b/tests/std-process-arg-result-arity.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ArityMismatch) + (message "function `std.process.arg_result` called with wrong number of arguments") + (file "") + (span + (bytes 37 61) + (range 5 3 5 27) + ) + (expected "1") + (found "0") +) diff --git a/tests/std-process-arg-result-helper-shadow.diag b/tests/std-process-arg-result-helper-shadow.diag new file mode 100644 index 0000000..56bedd9 --- /dev/null +++ b/tests/std-process-arg-result-helper-shadow.diag @@ -0,0 +1,12 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code DuplicateFunction) + (message "compiler-known callable `__glagol_process_arg_result` is reserved") + (file "") + (span + (bytes 16 106) + (range 4 1 5 22) + ) +) diff --git a/tests/std-process-arg-result-name-shadow.diag b/tests/std-process-arg-result-name-shadow.diag new file mode 100644 index 0000000..107bbb3 --- /dev/null +++ b/tests/std-process-arg-result-name-shadow.diag @@ -0,0 +1,12 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code DuplicateFunction) + (message "duplicate function `std.process.arg_result`") + (file "") + (span + (bytes 16 101) + (range 4 1 5 22) + ) +) diff --git a/tests/std-process-arg-type.diag b/tests/std-process-arg-type.diag new file mode 100644 index 0000000..6f690e0 --- /dev/null +++ b/tests/std-process-arg-type.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code TypeMismatch) + (message "cannot call `std.process.arg` with argument of wrong type") + (file "") + (span + (bytes 57 61) + (range 5 20 5 24) + ) + (expected "i32") + (found "bool") +) diff --git a/tests/std-random-bytes-unsupported.diag b/tests/std-random-bytes-unsupported.diag new file mode 100644 index 0000000..443cde3 --- /dev/null +++ b/tests/std-random-bytes-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.random.bytes` is not supported") + (file "") + (span + (bytes 38 54) + (range 5 4 5 20) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.random.bytes") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-random-crypto-i32-unsupported.diag b/tests/std-random-crypto-i32-unsupported.diag new file mode 100644 index 0000000..fc9f3e1 --- /dev/null +++ b/tests/std-random-crypto-i32-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.random.crypto_i32` is not supported") + (file "") + (span + (bytes 38 59) + (range 5 4 5 25) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.random.crypto_i32") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-random-float-unsupported.diag b/tests/std-random-float-unsupported.diag new file mode 100644 index 0000000..eac70e9 --- /dev/null +++ b/tests/std-random-float-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.random.float` is not supported") + (file "") + (span + (bytes 38 54) + (range 5 4 5 20) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.random.float") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-random-i32-arity.diag b/tests/std-random-i32-arity.diag new file mode 100644 index 0000000..c519d69 --- /dev/null +++ b/tests/std-random-i32-arity.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ArityMismatch) + (message "function `std.random.i32` called with wrong number of arguments") + (file "") + (span + (bytes 37 55) + (range 5 3 5 21) + ) + (expected "0") + (found "1") +) diff --git a/tests/std-random-i32-bool-context.diag b/tests/std-random-i32-bool-context.diag new file mode 100644 index 0000000..647508a --- /dev/null +++ b/tests/std-random-i32-bool-context.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code IfConditionNotBool) + (message "`if` condition must be bool") + (file "") + (span + (bytes 41 57) + (range 5 7 5 23) + ) + (expected "bool") + (found "i32") +) diff --git a/tests/std-random-i32-helper-shadow.diag b/tests/std-random-i32-helper-shadow.diag new file mode 100644 index 0000000..ce3b670 --- /dev/null +++ b/tests/std-random-i32-helper-shadow.diag @@ -0,0 +1,12 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code DuplicateFunction) + (message "compiler-known callable `__glagol_random_i32` is reserved") + (file "") + (span + (bytes 16 54) + (range 4 1 5 5) + ) +) diff --git a/tests/std-random-i32-name-shadow.diag b/tests/std-random-i32-name-shadow.diag new file mode 100644 index 0000000..6f3ac14 --- /dev/null +++ b/tests/std-random-i32-name-shadow.diag @@ -0,0 +1,12 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code DuplicateFunction) + (message "duplicate function `std.random.i32`") + (file "") + (span + (bytes 16 49) + (range 4 1 5 5) + ) +) diff --git a/tests/std-random-range-unsupported.diag b/tests/std-random-range-unsupported.diag new file mode 100644 index 0000000..bd57d72 --- /dev/null +++ b/tests/std-random-range-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.random.range` is not supported") + (file "") + (span + (bytes 38 54) + (range 5 4 5 20) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.random.range") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-random-seed-unsupported.diag b/tests/std-random-seed-unsupported.diag new file mode 100644 index 0000000..777484c --- /dev/null +++ b/tests/std-random-seed-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.random.seed` is not supported") + (file "") + (span + (bytes 38 53) + (range 5 4 5 19) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.random.seed") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-random-shuffle-unsupported.diag b/tests/std-random-shuffle-unsupported.diag new file mode 100644 index 0000000..125a800 --- /dev/null +++ b/tests/std-random-shuffle-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.random.shuffle` is not supported") + (file "") + (span + (bytes 38 56) + (range 5 4 5 22) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.random.shuffle") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-random-string-unsupported.diag b/tests/std-random-string-unsupported.diag new file mode 100644 index 0000000..f9fd784 --- /dev/null +++ b/tests/std-random-string-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.random.string` is not supported") + (file "") + (span + (bytes 41 58) + (range 5 4 5 21) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.random.string") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-random-uuid-unsupported.diag b/tests/std-random-uuid-unsupported.diag new file mode 100644 index 0000000..3b08840 --- /dev/null +++ b/tests/std-random-uuid-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.random.uuid` is not supported") + (file "") + (span + (bytes 41 56) + (range 5 4 5 19) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.random.uuid") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-result-map-unsupported.diag b/tests/std-result-map-unsupported.diag new file mode 100644 index 0000000..86b0700 --- /dev/null +++ b/tests/std-result-map-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.result.map` is not supported") + (file "") + (span + (bytes 38 52) + (range 5 4 5 18) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.result.map") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-string-concat-arity.diag b/tests/std-string-concat-arity.diag new file mode 100644 index 0000000..f892560 --- /dev/null +++ b/tests/std-string-concat-arity.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ArityMismatch) + (message "function `std.string.concat` called with wrong number of arguments") + (file "") + (span + (bytes 40 63) + (range 5 3 5 26) + ) + (expected "2") + (found "1") +) diff --git a/tests/std-string-concat-helper-shadow.diag b/tests/std-string-concat-helper-shadow.diag new file mode 100644 index 0000000..5dc8a21 --- /dev/null +++ b/tests/std-string-concat-helper-shadow.diag @@ -0,0 +1,12 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code DuplicateFunction) + (message "compiler-known callable `__glagol_string_concat` is reserved") + (file "") + (span + (bytes 16 100) + (range 4 1 5 17) + ) +) diff --git a/tests/std-string-concat-result-context.diag b/tests/std-string-concat-result-context.diag new file mode 100644 index 0000000..857890c --- /dev/null +++ b/tests/std-string-concat-result-context.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ReturnTypeMismatch) + (message "function `main` returns wrong type") + (file "") + (span + (bytes 16 65) + (range 4 1 5 31) + ) + (expected "i32") + (found "string") +) diff --git a/tests/std-string-concat-type.diag b/tests/std-string-concat-type.diag new file mode 100644 index 0000000..878fe21 --- /dev/null +++ b/tests/std-string-concat-type.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code TypeMismatch) + (message "cannot call `std.string.concat` with argument of wrong type") + (file "") + (span + (bytes 59 60) + (range 5 22 5 23) + ) + (expected "string") + (found "i32") +) diff --git a/tests/std-string-concat-unsupported-string-container.diag b/tests/std-string-concat-unsupported-string-container.diag new file mode 100644 index 0000000..0af0ca4 --- /dev/null +++ b/tests/std-string-concat-unsupported-string-container.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code TypeMismatch) + (message "cannot call `std.string.concat` with argument of wrong type") + (file "") + (span + (bytes 59 77) + (range 5 22 5 40) + ) + (expected "string") + (found "(array string 1)") +) diff --git a/tests/std-string-from-i64-unsupported.diag b/tests/std-string-from-i64-unsupported.diag new file mode 100644 index 0000000..1a21109 --- /dev/null +++ b/tests/std-string-from-i64-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.string.from_i64` is not supported") + (file "") + (span + (bytes 38 57) + (range 5 4 5 23) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.string.from_i64") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-string-index-unsupported.diag b/tests/std-string-index-unsupported.diag new file mode 100644 index 0000000..7623dfc --- /dev/null +++ b/tests/std-string-index-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.string.index` is not supported") + (file "") + (span + (bytes 38 54) + (range 5 4 5 20) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.string.index") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-string-len-type.diag b/tests/std-string-len-type.diag new file mode 100644 index 0000000..02445cc --- /dev/null +++ b/tests/std-string-len-type.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code TypeMismatch) + (message "cannot call `std.string.len` with argument of wrong type") + (file "") + (span + (bytes 53 54) + (range 5 19 5 20) + ) + (expected "string") + (found "i32") +) diff --git a/tests/std-string-parse-bool-result-arity.diag b/tests/std-string-parse-bool-result-arity.diag new file mode 100644 index 0000000..4d0c95a --- /dev/null +++ b/tests/std-string-parse-bool-result-arity.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ArityMismatch) + (message "function `std.string.parse_bool_result` called with wrong number of arguments") + (file "") + (span + (bytes 51 81) + (range 5 3 5 33) + ) + (expected "1") + (found "0") +) diff --git a/tests/std-string-parse-bool-result-bool-context.diag b/tests/std-string-parse-bool-result-bool-context.diag new file mode 100644 index 0000000..f2c4ca4 --- /dev/null +++ b/tests/std-string-parse-bool-result-bool-context.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code IfConditionNotBool) + (message "`if` condition must be bool") + (file "") + (span + (bytes 41 78) + (range 5 7 5 44) + ) + (expected "bool") + (found "(result bool i32)") +) diff --git a/tests/std-string-parse-bool-result-context.diag b/tests/std-string-parse-bool-result-context.diag new file mode 100644 index 0000000..d77d93f --- /dev/null +++ b/tests/std-string-parse-bool-result-context.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ReturnTypeMismatch) + (message "function `main` returns wrong type") + (file "") + (span + (bytes 16 75) + (range 4 1 5 41) + ) + (expected "i32") + (found "(result bool i32)") +) diff --git a/tests/std-string-parse-bool-result-helper-shadow.diag b/tests/std-string-parse-bool-result-helper-shadow.diag new file mode 100644 index 0000000..289fbba --- /dev/null +++ b/tests/std-string-parse-bool-result-helper-shadow.diag @@ -0,0 +1,12 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code DuplicateFunction) + (message "compiler-known callable `__glagol_string_parse_bool_result` is reserved") + (file "") + (span + (bytes 16 129) + (range 4 1 5 39) + ) +) diff --git a/tests/std-string-parse-bool-result-name-shadow.diag b/tests/std-string-parse-bool-result-name-shadow.diag new file mode 100644 index 0000000..a1a1ae9 --- /dev/null +++ b/tests/std-string-parse-bool-result-name-shadow.diag @@ -0,0 +1,12 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code DuplicateFunction) + (message "duplicate function `std.string.parse_bool_result`") + (file "") + (span + (bytes 16 124) + (range 4 1 5 39) + ) +) diff --git a/tests/std-string-parse-bool-result-type.diag b/tests/std-string-parse-bool-result-type.diag new file mode 100644 index 0000000..9b0f569 --- /dev/null +++ b/tests/std-string-parse-bool-result-type.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code TypeMismatch) + (message "cannot call `std.string.parse_bool_result` with argument of wrong type") + (file "") + (span + (bytes 81 82) + (range 5 33 5 34) + ) + (expected "string") + (found "i32") +) diff --git a/tests/std-string-parse-bool-unsupported.diag b/tests/std-string-parse-bool-unsupported.diag new file mode 100644 index 0000000..6cbc526 --- /dev/null +++ b/tests/std-string-parse-bool-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.string.parse_bool` is not supported") + (file "") + (span + (bytes 38 59) + (range 5 4 5 25) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.string.parse_bool") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-string-parse-bytes-unsupported.diag b/tests/std-string-parse-bytes-unsupported.diag new file mode 100644 index 0000000..79e8e8a --- /dev/null +++ b/tests/std-string-parse-bytes-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.string.parse_bytes_result` is not supported") + (file "") + (span + (bytes 38 67) + (range 5 4 5 33) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.string.parse_bytes_result") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-string-parse-f64-result-arity.diag b/tests/std-string-parse-f64-result-arity.diag new file mode 100644 index 0000000..bd6f8e6 --- /dev/null +++ b/tests/std-string-parse-f64-result-arity.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ArityMismatch) + (message "function `std.string.parse_f64_result` called with wrong number of arguments") + (file "") + (span + (bytes 50 79) + (range 5 3 5 32) + ) + (expected "1") + (found "0") +) diff --git a/tests/std-string-parse-f64-result-bool-context.diag b/tests/std-string-parse-f64-result-bool-context.diag new file mode 100644 index 0000000..778829d --- /dev/null +++ b/tests/std-string-parse-f64-result-bool-context.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code IfConditionNotBool) + (message "`if` condition must be bool") + (file "") + (span + (bytes 41 76) + (range 5 7 5 42) + ) + (expected "bool") + (found "(result f64 i32)") +) diff --git a/tests/std-string-parse-f64-result-context.diag b/tests/std-string-parse-f64-result-context.diag new file mode 100644 index 0000000..3f2e723 --- /dev/null +++ b/tests/std-string-parse-f64-result-context.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ReturnTypeMismatch) + (message "function `main` returns wrong type") + (file "") + (span + (bytes 16 73) + (range 4 1 5 39) + ) + (expected "i32") + (found "(result f64 i32)") +) diff --git a/tests/std-string-parse-f64-result-helper-shadow.diag b/tests/std-string-parse-f64-result-helper-shadow.diag new file mode 100644 index 0000000..914bf80 --- /dev/null +++ b/tests/std-string-parse-f64-result-helper-shadow.diag @@ -0,0 +1,12 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code DuplicateFunction) + (message "compiler-known callable `__glagol_string_parse_f64_result` is reserved") + (file "") + (span + (bytes 16 126) + (range 4 1 5 38) + ) +) diff --git a/tests/std-string-parse-f64-result-name-shadow.diag b/tests/std-string-parse-f64-result-name-shadow.diag new file mode 100644 index 0000000..ceeadbc --- /dev/null +++ b/tests/std-string-parse-f64-result-name-shadow.diag @@ -0,0 +1,12 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code DuplicateFunction) + (message "duplicate function `std.string.parse_f64_result`") + (file "") + (span + (bytes 16 121) + (range 4 1 5 38) + ) +) diff --git a/tests/std-string-parse-f64-result-type.diag b/tests/std-string-parse-f64-result-type.diag new file mode 100644 index 0000000..32b707b --- /dev/null +++ b/tests/std-string-parse-f64-result-type.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code TypeMismatch) + (message "cannot call `std.string.parse_f64_result` with argument of wrong type") + (file "") + (span + (bytes 79 80) + (range 5 32 5 33) + ) + (expected "string") + (found "i32") +) diff --git a/tests/std-string-parse-f64-unsupported.diag b/tests/std-string-parse-f64-unsupported.diag new file mode 100644 index 0000000..daa6d45 --- /dev/null +++ b/tests/std-string-parse-f64-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.string.parse_f64` is not supported") + (file "") + (span + (bytes 38 58) + (range 5 4 5 24) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.string.parse_f64") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-string-parse-generic-unsupported.diag b/tests/std-string-parse-generic-unsupported.diag new file mode 100644 index 0000000..e8cfb33 --- /dev/null +++ b/tests/std-string-parse-generic-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.string.parse_result` is not supported") + (file "") + (span + (bytes 38 61) + (range 5 4 5 27) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.string.parse_result") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-string-parse-i32-base-prefix-unsupported.diag b/tests/std-string-parse-i32-base-prefix-unsupported.diag new file mode 100644 index 0000000..da82e42 --- /dev/null +++ b/tests/std-string-parse-i32-base-prefix-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.string.parse_i32_base_prefix_result` is not supported") + (file "") + (span + (bytes 38 77) + (range 5 4 5 43) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.string.parse_i32_base_prefix_result") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-string-parse-i32-binary-unsupported.diag b/tests/std-string-parse-i32-binary-unsupported.diag new file mode 100644 index 0000000..1996420 --- /dev/null +++ b/tests/std-string-parse-i32-binary-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.string.parse_i32_binary_result` is not supported") + (file "") + (span + (bytes 38 72) + (range 5 4 5 38) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.string.parse_i32_binary_result") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-string-parse-i32-code-unsupported.diag b/tests/std-string-parse-i32-code-unsupported.diag new file mode 100644 index 0000000..84b1b3a --- /dev/null +++ b/tests/std-string-parse-i32-code-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.string.parse_i32_code_result` is not supported") + (file "") + (span + (bytes 38 70) + (range 5 4 5 36) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.string.parse_i32_code_result") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-string-parse-i32-error-adt-unsupported.diag b/tests/std-string-parse-i32-error-adt-unsupported.diag new file mode 100644 index 0000000..0673555 --- /dev/null +++ b/tests/std-string-parse-i32-error-adt-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.string.parse_i32_error_result` is not supported") + (file "") + (span + (bytes 38 71) + (range 5 4 5 37) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.string.parse_i32_error_result") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-string-parse-i32-hex-unsupported.diag b/tests/std-string-parse-i32-hex-unsupported.diag new file mode 100644 index 0000000..39db654 --- /dev/null +++ b/tests/std-string-parse-i32-hex-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.string.parse_i32_hex_result` is not supported") + (file "") + (span + (bytes 38 69) + (range 5 4 5 35) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.string.parse_i32_hex_result") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-string-parse-i32-locale-unsupported.diag b/tests/std-string-parse-i32-locale-unsupported.diag new file mode 100644 index 0000000..9e4cc30 --- /dev/null +++ b/tests/std-string-parse-i32-locale-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.string.parse_i32_locale_result` is not supported") + (file "") + (span + (bytes 38 72) + (range 5 4 5 38) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.string.parse_i32_locale_result") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-string-parse-i32-message-unsupported.diag b/tests/std-string-parse-i32-message-unsupported.diag new file mode 100644 index 0000000..aa1ce04 --- /dev/null +++ b/tests/std-string-parse-i32-message-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.string.parse_i32_message_result` is not supported") + (file "") + (span + (bytes 38 73) + (range 5 4 5 39) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.string.parse_i32_message_result") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-string-parse-i32-octal-unsupported.diag b/tests/std-string-parse-i32-octal-unsupported.diag new file mode 100644 index 0000000..06fe5ec --- /dev/null +++ b/tests/std-string-parse-i32-octal-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.string.parse_i32_octal_result` is not supported") + (file "") + (span + (bytes 38 71) + (range 5 4 5 37) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.string.parse_i32_octal_result") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-string-parse-i32-plus-unsupported.diag b/tests/std-string-parse-i32-plus-unsupported.diag new file mode 100644 index 0000000..9941d82 --- /dev/null +++ b/tests/std-string-parse-i32-plus-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.string.parse_i32_plus_result` is not supported") + (file "") + (span + (bytes 38 70) + (range 5 4 5 36) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.string.parse_i32_plus_result") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-string-parse-i32-radix-unsupported.diag b/tests/std-string-parse-i32-radix-unsupported.diag new file mode 100644 index 0000000..ccc9785 --- /dev/null +++ b/tests/std-string-parse-i32-radix-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.string.parse_i32_radix_result` is not supported") + (file "") + (span + (bytes 38 71) + (range 5 4 5 37) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.string.parse_i32_radix_result") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-string-parse-i32-result-arity.diag b/tests/std-string-parse-i32-result-arity.diag new file mode 100644 index 0000000..8a7d2b7 --- /dev/null +++ b/tests/std-string-parse-i32-result-arity.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ArityMismatch) + (message "function `std.string.parse_i32_result` called with wrong number of arguments") + (file "") + (span + (bytes 50 79) + (range 5 3 5 32) + ) + (expected "1") + (found "0") +) diff --git a/tests/std-string-parse-i32-result-bool-context.diag b/tests/std-string-parse-i32-result-bool-context.diag new file mode 100644 index 0000000..4486a68 --- /dev/null +++ b/tests/std-string-parse-i32-result-bool-context.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code IfConditionNotBool) + (message "`if` condition must be bool") + (file "") + (span + (bytes 41 75) + (range 5 7 5 41) + ) + (expected "bool") + (found "(result i32 i32)") +) diff --git a/tests/std-string-parse-i32-result-context.diag b/tests/std-string-parse-i32-result-context.diag new file mode 100644 index 0000000..2cd6113 --- /dev/null +++ b/tests/std-string-parse-i32-result-context.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ReturnTypeMismatch) + (message "function `main` returns wrong type") + (file "") + (span + (bytes 16 72) + (range 4 1 5 38) + ) + (expected "i32") + (found "(result i32 i32)") +) diff --git a/tests/std-string-parse-i32-result-helper-shadow.diag b/tests/std-string-parse-i32-result-helper-shadow.diag new file mode 100644 index 0000000..5a0f5f3 --- /dev/null +++ b/tests/std-string-parse-i32-result-helper-shadow.diag @@ -0,0 +1,12 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code DuplicateFunction) + (message "compiler-known callable `__glagol_string_parse_i32_result` is reserved") + (file "") + (span + (bytes 16 107) + (range 4 1 5 19) + ) +) diff --git a/tests/std-string-parse-i32-result-name-shadow.diag b/tests/std-string-parse-i32-result-name-shadow.diag new file mode 100644 index 0000000..500bbc9 --- /dev/null +++ b/tests/std-string-parse-i32-result-name-shadow.diag @@ -0,0 +1,12 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code DuplicateFunction) + (message "duplicate function `std.string.parse_i32_result`") + (file "") + (span + (bytes 16 102) + (range 4 1 5 19) + ) +) diff --git a/tests/std-string-parse-i32-result-type.diag b/tests/std-string-parse-i32-result-type.diag new file mode 100644 index 0000000..80a6411 --- /dev/null +++ b/tests/std-string-parse-i32-result-type.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code TypeMismatch) + (message "cannot call `std.string.parse_i32_result` with argument of wrong type") + (file "") + (span + (bytes 79 80) + (range 5 32 5 33) + ) + (expected "string") + (found "i32") +) diff --git a/tests/std-string-parse-i32-trim-unsupported.diag b/tests/std-string-parse-i32-trim-unsupported.diag new file mode 100644 index 0000000..73c11ca --- /dev/null +++ b/tests/std-string-parse-i32-trim-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.string.parse_i32_trim_result` is not supported") + (file "") + (span + (bytes 38 70) + (range 5 4 5 36) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.string.parse_i32_trim_result") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-string-parse-i32-underscore-unsupported.diag b/tests/std-string-parse-i32-underscore-unsupported.diag new file mode 100644 index 0000000..b72c5f0 --- /dev/null +++ b/tests/std-string-parse-i32-underscore-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.string.parse_i32_underscore_result` is not supported") + (file "") + (span + (bytes 38 76) + (range 5 4 5 42) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.string.parse_i32_underscore_result") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-string-parse-i32-unicode-unsupported.diag b/tests/std-string-parse-i32-unicode-unsupported.diag new file mode 100644 index 0000000..dc3c2b8 --- /dev/null +++ b/tests/std-string-parse-i32-unicode-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.string.parse_i32_unicode_result` is not supported") + (file "") + (span + (bytes 38 73) + (range 5 4 5 39) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.string.parse_i32_unicode_result") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-string-parse-i32-unsupported.diag b/tests/std-string-parse-i32-unsupported.diag new file mode 100644 index 0000000..4d18635 --- /dev/null +++ b/tests/std-string-parse-i32-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.string.parse_i32` is not supported") + (file "") + (span + (bytes 38 58) + (range 5 4 5 24) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.string.parse_i32") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-string-parse-i32-whitespace-unsupported.diag b/tests/std-string-parse-i32-whitespace-unsupported.diag new file mode 100644 index 0000000..2b9810b --- /dev/null +++ b/tests/std-string-parse-i32-whitespace-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.string.parse_i32_whitespace_result` is not supported") + (file "") + (span + (bytes 38 76) + (range 5 4 5 42) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.string.parse_i32_whitespace_result") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-string-parse-i64-result-arity.diag b/tests/std-string-parse-i64-result-arity.diag new file mode 100644 index 0000000..5d5420a --- /dev/null +++ b/tests/std-string-parse-i64-result-arity.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ArityMismatch) + (message "function `std.string.parse_i64_result` called with wrong number of arguments") + (file "") + (span + (bytes 50 79) + (range 5 3 5 32) + ) + (expected "1") + (found "0") +) diff --git a/tests/std-string-parse-i64-result-bool-context.diag b/tests/std-string-parse-i64-result-bool-context.diag new file mode 100644 index 0000000..971bc01 --- /dev/null +++ b/tests/std-string-parse-i64-result-bool-context.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code IfConditionNotBool) + (message "`if` condition must be bool") + (file "") + (span + (bytes 41 75) + (range 5 7 5 41) + ) + (expected "bool") + (found "(result i64 i32)") +) diff --git a/tests/std-string-parse-i64-result-context.diag b/tests/std-string-parse-i64-result-context.diag new file mode 100644 index 0000000..398f1e3 --- /dev/null +++ b/tests/std-string-parse-i64-result-context.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ReturnTypeMismatch) + (message "function `main` returns wrong type") + (file "") + (span + (bytes 16 72) + (range 4 1 5 38) + ) + (expected "i32") + (found "(result i64 i32)") +) diff --git a/tests/std-string-parse-i64-result-helper-shadow.diag b/tests/std-string-parse-i64-result-helper-shadow.diag new file mode 100644 index 0000000..1a05cab --- /dev/null +++ b/tests/std-string-parse-i64-result-helper-shadow.diag @@ -0,0 +1,12 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code DuplicateFunction) + (message "compiler-known callable `__glagol_string_parse_i64_result` is reserved") + (file "") + (span + (bytes 16 107) + (range 4 1 5 19) + ) +) diff --git a/tests/std-string-parse-i64-result-name-shadow.diag b/tests/std-string-parse-i64-result-name-shadow.diag new file mode 100644 index 0000000..bdfa6fa --- /dev/null +++ b/tests/std-string-parse-i64-result-name-shadow.diag @@ -0,0 +1,12 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code DuplicateFunction) + (message "duplicate function `std.string.parse_i64_result`") + (file "") + (span + (bytes 16 102) + (range 4 1 5 19) + ) +) diff --git a/tests/std-string-parse-i64-result-type.diag b/tests/std-string-parse-i64-result-type.diag new file mode 100644 index 0000000..cce0535 --- /dev/null +++ b/tests/std-string-parse-i64-result-type.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code TypeMismatch) + (message "cannot call `std.string.parse_i64_result` with argument of wrong type") + (file "") + (span + (bytes 79 80) + (range 5 32 5 33) + ) + (expected "string") + (found "i32") +) diff --git a/tests/std-string-parse-string-unsupported.diag b/tests/std-string-parse-string-unsupported.diag new file mode 100644 index 0000000..04563ca --- /dev/null +++ b/tests/std-string-parse-string-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.string.parse_string_result` is not supported") + (file "") + (span + (bytes 38 68) + (range 5 4 5 34) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.string.parse_string_result") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-string-scan-unsupported.diag b/tests/std-string-scan-unsupported.diag new file mode 100644 index 0000000..1d84f09 --- /dev/null +++ b/tests/std-string-scan-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.string.scan` is not supported") + (file "") + (span + (bytes 38 53) + (range 5 4 5 19) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.string.scan") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-string-slice-unsupported.diag b/tests/std-string-slice-unsupported.diag new file mode 100644 index 0000000..e2ade07 --- /dev/null +++ b/tests/std-string-slice-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.string.slice` is not supported") + (file "") + (span + (bytes 38 54) + (range 5 4 5 20) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.string.slice") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-string-tokenize-unsupported.diag b/tests/std-string-tokenize-unsupported.diag new file mode 100644 index 0000000..613f0cb --- /dev/null +++ b/tests/std-string-tokenize-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.string.tokenize` is not supported") + (file "") + (span + (bytes 38 57) + (range 5 4 5 23) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.string.tokenize") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-terminal-clear-unsupported.diag b/tests/std-terminal-clear-unsupported.diag new file mode 100644 index 0000000..55b6104 --- /dev/null +++ b/tests/std-terminal-clear-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.terminal.clear` is not supported") + (file "") + (span + (bytes 38 56) + (range 5 4 5 22) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.terminal.clear") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-terminal-echo-unsupported.diag b/tests/std-terminal-echo-unsupported.diag new file mode 100644 index 0000000..ed407e8 --- /dev/null +++ b/tests/std-terminal-echo-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.terminal.echo` is not supported") + (file "") + (span + (bytes 38 55) + (range 5 4 5 21) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.terminal.echo") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-terminal-is-tty-unsupported.diag b/tests/std-terminal-is-tty-unsupported.diag new file mode 100644 index 0000000..fd45756 --- /dev/null +++ b/tests/std-terminal-is-tty-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.terminal.is_tty` is not supported") + (file "") + (span + (bytes 39 58) + (range 5 4 5 23) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.terminal.is_tty") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-terminal-raw-mode-unsupported.diag b/tests/std-terminal-raw-mode-unsupported.diag new file mode 100644 index 0000000..2ca2706 --- /dev/null +++ b/tests/std-terminal-raw-mode-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.terminal.raw_mode` is not supported") + (file "") + (span + (bytes 38 59) + (range 5 4 5 25) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.terminal.raw_mode") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-time-monotonic-arity.diag b/tests/std-time-monotonic-arity.diag new file mode 100644 index 0000000..06118fb --- /dev/null +++ b/tests/std-time-monotonic-arity.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ArityMismatch) + (message "function `std.time.monotonic_ms` called with wrong number of arguments") + (file "") + (span + (bytes 37 62) + (range 5 3 5 28) + ) + (expected "0") + (found "1") +) diff --git a/tests/std-time-monotonic-helper-shadow.diag b/tests/std-time-monotonic-helper-shadow.diag new file mode 100644 index 0000000..9f2fa08 --- /dev/null +++ b/tests/std-time-monotonic-helper-shadow.diag @@ -0,0 +1,12 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code DuplicateFunction) + (message "compiler-known callable `__glagol_time_monotonic_ms` is reserved") + (file "") + (span + (bytes 16 61) + (range 4 1 5 5) + ) +) diff --git a/tests/std-time-monotonic-name-shadow.diag b/tests/std-time-monotonic-name-shadow.diag new file mode 100644 index 0000000..53fad23 --- /dev/null +++ b/tests/std-time-monotonic-name-shadow.diag @@ -0,0 +1,12 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code DuplicateFunction) + (message "duplicate function `std.time.monotonic_ms`") + (file "") + (span + (bytes 16 56) + (range 4 1 5 5) + ) +) diff --git a/tests/std-time-monotonic-result-context.diag b/tests/std-time-monotonic-result-context.diag new file mode 100644 index 0000000..f6266ea --- /dev/null +++ b/tests/std-time-monotonic-result-context.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code TypeMismatch) + (message "local `elapsed` initializer has the wrong type") + (file "") + (span + (bytes 57 80) + (range 5 23 5 46) + ) + (expected "string") + (found "i32") +) diff --git a/tests/std-time-now-unsupported.diag b/tests/std-time-now-unsupported.diag new file mode 100644 index 0000000..48380e2 --- /dev/null +++ b/tests/std-time-now-unsupported.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.time.now` is not supported") + (file "") + (span + (bytes 38 50) + (range 5 4 5 16) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.time.now") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-time-sleep-arity.diag b/tests/std-time-sleep-arity.diag new file mode 100644 index 0000000..095dd87 --- /dev/null +++ b/tests/std-time-sleep-arity.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ArityMismatch) + (message "function `std.time.sleep_ms` called with wrong number of arguments") + (file "") + (span + (bytes 37 56) + (range 5 3 5 22) + ) + (expected "1") + (found "0") +) diff --git a/tests/std-time-sleep-helper-shadow.diag b/tests/std-time-sleep-helper-shadow.diag new file mode 100644 index 0000000..6a70ddc --- /dev/null +++ b/tests/std-time-sleep-helper-shadow.diag @@ -0,0 +1,12 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code DuplicateFunction) + (message "compiler-known callable `__glagol_time_sleep_ms` is reserved") + (file "") + (span + (bytes 16 66) + (range 4 1 5 6) + ) +) diff --git a/tests/std-time-sleep-name-shadow.diag b/tests/std-time-sleep-name-shadow.diag new file mode 100644 index 0000000..81b38c1 --- /dev/null +++ b/tests/std-time-sleep-name-shadow.diag @@ -0,0 +1,12 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code DuplicateFunction) + (message "duplicate function `std.time.sleep_ms`") + (file "") + (span + (bytes 16 61) + (range 4 1 5 6) + ) +) diff --git a/tests/std-time-sleep-type.diag b/tests/std-time-sleep-type.diag new file mode 100644 index 0000000..a6cfe18 --- /dev/null +++ b/tests/std-time-sleep-type.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code TypeMismatch) + (message "cannot call `std.time.sleep_ms` with argument of wrong type") + (file "") + (span + (bytes 56 60) + (range 5 22 5 26) + ) + (expected "i32") + (found "bool") +) diff --git a/tests/std-vec-i32-append-vector-type.diag b/tests/std-vec-i32-append-vector-type.diag new file mode 100644 index 0000000..a130d42 --- /dev/null +++ b/tests/std-vec-i32-append-vector-type.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code TypeMismatch) + (message "cannot call `std.vec.i32.append` with argument of wrong type") + (file "") + (span + (bytes 63 64) + (range 5 23 5 24) + ) + (expected "(vec i32)") + (found "i32") +) diff --git a/tests/std-vec-i32-arity.diag b/tests/std-vec-i32-arity.diag new file mode 100644 index 0000000..c575def --- /dev/null +++ b/tests/std-vec-i32-arity.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ArityMismatch) + (message "function `std.vec.i32.append` called with wrong number of arguments") + (file "") + (span + (bytes 43 83) + (range 5 3 5 43) + ) + (expected "2") + (found "1") +) diff --git a/tests/std-vec-i32-empty-arity.diag b/tests/std-vec-i32-empty-arity.diag new file mode 100644 index 0000000..5c438d1 --- /dev/null +++ b/tests/std-vec-i32-empty-arity.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ArityMismatch) + (message "function `std.vec.i32.empty` called with wrong number of arguments") + (file "") + (span + (bytes 43 64) + (range 5 3 5 24) + ) + (expected "0") + (found "1") +) diff --git a/tests/std-vec-i32-helper-shadow.diag b/tests/std-vec-i32-helper-shadow.diag new file mode 100644 index 0000000..7f4e4f3 --- /dev/null +++ b/tests/std-vec-i32-helper-shadow.diag @@ -0,0 +1,12 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code DuplicateFunction) + (message "compiler-known callable `__glagol_vec_i32_eq` is reserved") + (file "") + (span + (bytes 16 102) + (range 4 1 5 18) + ) +) diff --git a/tests/std-vec-i32-index-arity.diag b/tests/std-vec-i32-index-arity.diag new file mode 100644 index 0000000..27828a9 --- /dev/null +++ b/tests/std-vec-i32-index-arity.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ArityMismatch) + (message "function `std.vec.i32.index` called with wrong number of arguments") + (file "") + (span + (bytes 37 76) + (range 5 3 5 42) + ) + (expected "2") + (found "1") +) diff --git a/tests/std-vec-i32-index-index-type.diag b/tests/std-vec-i32-index-index-type.diag new file mode 100644 index 0000000..f74e35b --- /dev/null +++ b/tests/std-vec-i32-index-index-type.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code TypeMismatch) + (message "cannot call `std.vec.i32.index` with argument of wrong type") + (file "") + (span + (bytes 76 80) + (range 5 42 5 46) + ) + (expected "i32") + (found "bool") +) diff --git a/tests/std-vec-i32-index-vector-type.diag b/tests/std-vec-i32-index-vector-type.diag new file mode 100644 index 0000000..97f08ac --- /dev/null +++ b/tests/std-vec-i32-index-vector-type.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code TypeMismatch) + (message "cannot call `std.vec.i32.index` with argument of wrong type") + (file "") + (span + (bytes 56 57) + (range 5 22 5 23) + ) + (expected "(vec i32)") + (found "i32") +) diff --git a/tests/std-vec-i32-len-arity.diag b/tests/std-vec-i32-len-arity.diag new file mode 100644 index 0000000..69e6aee --- /dev/null +++ b/tests/std-vec-i32-len-arity.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ArityMismatch) + (message "function `std.vec.i32.len` called with wrong number of arguments") + (file "") + (span + (bytes 37 54) + (range 5 3 5 20) + ) + (expected "1") + (found "0") +) diff --git a/tests/std-vec-i32-len-type.diag b/tests/std-vec-i32-len-type.diag new file mode 100644 index 0000000..3ad12a2 --- /dev/null +++ b/tests/std-vec-i32-len-type.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code TypeMismatch) + (message "cannot call `std.vec.i32.len` with argument of wrong type") + (file "") + (span + (bytes 54 55) + (range 5 20 5 21) + ) + (expected "(vec i32)") + (found "i32") +) diff --git a/tests/std-vec-i32-one-sided-equality.diag b/tests/std-vec-i32-one-sided-equality.diag new file mode 100644 index 0000000..31800c5 --- /dev/null +++ b/tests/std-vec-i32-one-sided-equality.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedVectorEquality) + (message "vector equality is supported only for `(vec i32)`, `(vec i64)`, `(vec f64)`, `(vec bool)`, or `(vec string)` values") + (file "") + (span + (bytes 38 63) + (range 5 3 5 28) + ) + (expected "(vec i32), (vec i64), (vec f64), (vec bool), or (vec string) on both sides") + (found "(vec i32) and i32") +) diff --git a/tests/std-vec-i32-push-alias.diag b/tests/std-vec-i32-push-alias.diag new file mode 100644 index 0000000..58ba89c --- /dev/null +++ b/tests/std-vec-i32-push-alias.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.vec.i32.push` is not supported") + (file "") + (span + (bytes 44 60) + (range 5 4 5 20) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.vec.i32.push") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-vec-i32-result-context.diag b/tests/std-vec-i32-result-context.diag new file mode 100644 index 0000000..c055050 --- /dev/null +++ b/tests/std-vec-i32-result-context.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ReturnTypeMismatch) + (message "function `main` returns wrong type") + (file "") + (span + (bytes 16 57) + (range 4 1 5 23) + ) + (expected "i32") + (found "(vec i32)") +) diff --git a/tests/std-vec-i32-type.diag b/tests/std-vec-i32-type.diag new file mode 100644 index 0000000..c5b2112 --- /dev/null +++ b/tests/std-vec-i32-type.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code TypeMismatch) + (message "cannot call `std.vec.i32.append` with argument of wrong type") + (file "") + (span + (bytes 83 87) + (range 5 43 5 47) + ) + (expected "i32") + (found "bool") +) diff --git a/tests/std-vec-i64-append-vector-type.diag b/tests/std-vec-i64-append-vector-type.diag new file mode 100644 index 0000000..4bbe1d8 --- /dev/null +++ b/tests/std-vec-i64-append-vector-type.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code TypeMismatch) + (message "cannot call `std.vec.i64.append` with argument of wrong type") + (file "") + (span + (bytes 63 64) + (range 5 23 5 24) + ) + (expected "(vec i64)") + (found "i32") +) diff --git a/tests/std-vec-i64-arity.diag b/tests/std-vec-i64-arity.diag new file mode 100644 index 0000000..ad93fc6 --- /dev/null +++ b/tests/std-vec-i64-arity.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ArityMismatch) + (message "function `std.vec.i64.append` called with wrong number of arguments") + (file "") + (span + (bytes 43 83) + (range 5 3 5 43) + ) + (expected "2") + (found "1") +) diff --git a/tests/std-vec-i64-empty-arity.diag b/tests/std-vec-i64-empty-arity.diag new file mode 100644 index 0000000..bedf2df --- /dev/null +++ b/tests/std-vec-i64-empty-arity.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ArityMismatch) + (message "function `std.vec.i64.empty` called with wrong number of arguments") + (file "") + (span + (bytes 43 64) + (range 5 3 5 24) + ) + (expected "0") + (found "1") +) diff --git a/tests/std-vec-i64-helper-shadow.diag b/tests/std-vec-i64-helper-shadow.diag new file mode 100644 index 0000000..7ebc9fd --- /dev/null +++ b/tests/std-vec-i64-helper-shadow.diag @@ -0,0 +1,12 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code DuplicateFunction) + (message "compiler-known callable `__glagol_vec_i64_eq` is reserved") + (file "") + (span + (bytes 16 102) + (range 4 1 5 18) + ) +) diff --git a/tests/std-vec-i64-index-arity.diag b/tests/std-vec-i64-index-arity.diag new file mode 100644 index 0000000..9993416 --- /dev/null +++ b/tests/std-vec-i64-index-arity.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ArityMismatch) + (message "function `std.vec.i64.index` called with wrong number of arguments") + (file "") + (span + (bytes 37 76) + (range 5 3 5 42) + ) + (expected "2") + (found "1") +) diff --git a/tests/std-vec-i64-index-index-type.diag b/tests/std-vec-i64-index-index-type.diag new file mode 100644 index 0000000..90e4bac --- /dev/null +++ b/tests/std-vec-i64-index-index-type.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code TypeMismatch) + (message "cannot call `std.vec.i64.index` with argument of wrong type") + (file "") + (span + (bytes 76 80) + (range 5 42 5 46) + ) + (expected "i32") + (found "bool") +) diff --git a/tests/std-vec-i64-index-vector-type.diag b/tests/std-vec-i64-index-vector-type.diag new file mode 100644 index 0000000..bc54f3c --- /dev/null +++ b/tests/std-vec-i64-index-vector-type.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code TypeMismatch) + (message "cannot call `std.vec.i64.index` with argument of wrong type") + (file "") + (span + (bytes 56 57) + (range 5 22 5 23) + ) + (expected "(vec i64)") + (found "i32") +) diff --git a/tests/std-vec-i64-len-arity.diag b/tests/std-vec-i64-len-arity.diag new file mode 100644 index 0000000..f10f8d5 --- /dev/null +++ b/tests/std-vec-i64-len-arity.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ArityMismatch) + (message "function `std.vec.i64.len` called with wrong number of arguments") + (file "") + (span + (bytes 37 54) + (range 5 3 5 20) + ) + (expected "1") + (found "0") +) diff --git a/tests/std-vec-i64-len-type.diag b/tests/std-vec-i64-len-type.diag new file mode 100644 index 0000000..df0afdd --- /dev/null +++ b/tests/std-vec-i64-len-type.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code TypeMismatch) + (message "cannot call `std.vec.i64.len` with argument of wrong type") + (file "") + (span + (bytes 54 55) + (range 5 20 5 21) + ) + (expected "(vec i64)") + (found "i32") +) diff --git a/tests/std-vec-i64-one-sided-equality.diag b/tests/std-vec-i64-one-sided-equality.diag new file mode 100644 index 0000000..48f7819 --- /dev/null +++ b/tests/std-vec-i64-one-sided-equality.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedVectorEquality) + (message "vector equality is supported only for `(vec i32)`, `(vec i64)`, `(vec f64)`, `(vec bool)`, or `(vec string)` values") + (file "") + (span + (bytes 38 63) + (range 5 3 5 28) + ) + (expected "(vec i32), (vec i64), (vec f64), (vec bool), or (vec string) on both sides") + (found "(vec i64) and i32") +) diff --git a/tests/std-vec-i64-push-alias.diag b/tests/std-vec-i64-push-alias.diag new file mode 100644 index 0000000..82f3136 --- /dev/null +++ b/tests/std-vec-i64-push-alias.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.vec.i64.push` is not supported") + (file "") + (span + (bytes 44 60) + (range 5 4 5 20) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.vec.i64.push") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-vec-i64-result-context.diag b/tests/std-vec-i64-result-context.diag new file mode 100644 index 0000000..c871215 --- /dev/null +++ b/tests/std-vec-i64-result-context.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ReturnTypeMismatch) + (message "function `main` returns wrong type") + (file "") + (span + (bytes 16 57) + (range 4 1 5 23) + ) + (expected "i32") + (found "(vec i64)") +) diff --git a/tests/std-vec-i64-type.diag b/tests/std-vec-i64-type.diag new file mode 100644 index 0000000..37184ca --- /dev/null +++ b/tests/std-vec-i64-type.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code TypeMismatch) + (message "cannot call `std.vec.i64.append` with argument of wrong type") + (file "") + (span + (bytes 83 87) + (range 5 43 5 47) + ) + (expected "i64") + (found "bool") +) diff --git a/tests/std-vec-string-append-vector-type.diag b/tests/std-vec-string-append-vector-type.diag new file mode 100644 index 0000000..02b9488 --- /dev/null +++ b/tests/std-vec-string-append-vector-type.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code TypeMismatch) + (message "cannot call `std.vec.string.append` with argument of wrong type") + (file "") + (span + (bytes 69 70) + (range 5 26 5 27) + ) + (expected "(vec string)") + (found "i32") +) diff --git a/tests/std-vec-string-arity.diag b/tests/std-vec-string-arity.diag new file mode 100644 index 0000000..3f19d96 --- /dev/null +++ b/tests/std-vec-string-arity.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ArityMismatch) + (message "function `std.vec.string.append` called with wrong number of arguments") + (file "") + (span + (bytes 46 92) + (range 5 3 5 49) + ) + (expected "2") + (found "1") +) diff --git a/tests/std-vec-string-empty-arity.diag b/tests/std-vec-string-empty-arity.diag new file mode 100644 index 0000000..c97303a --- /dev/null +++ b/tests/std-vec-string-empty-arity.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ArityMismatch) + (message "function `std.vec.string.empty` called with wrong number of arguments") + (file "") + (span + (bytes 46 76) + (range 5 3 5 33) + ) + (expected "0") + (found "1") +) diff --git a/tests/std-vec-string-helper-shadow.diag b/tests/std-vec-string-helper-shadow.diag new file mode 100644 index 0000000..c008ea2 --- /dev/null +++ b/tests/std-vec-string-helper-shadow.diag @@ -0,0 +1,12 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code DuplicateFunction) + (message "compiler-known callable `__glagol_vec_string_eq` is reserved") + (file "") + (span + (bytes 16 111) + (range 4 1 5 18) + ) +) diff --git a/tests/std-vec-string-index-arity.diag b/tests/std-vec-string-index-arity.diag new file mode 100644 index 0000000..739f8d6 --- /dev/null +++ b/tests/std-vec-string-index-arity.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ArityMismatch) + (message "function `std.vec.string.index` called with wrong number of arguments") + (file "") + (span + (bytes 40 85) + (range 5 3 5 48) + ) + (expected "2") + (found "1") +) diff --git a/tests/std-vec-string-index-index-type.diag b/tests/std-vec-string-index-index-type.diag new file mode 100644 index 0000000..84ae9bc --- /dev/null +++ b/tests/std-vec-string-index-index-type.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code TypeMismatch) + (message "cannot call `std.vec.string.index` with argument of wrong type") + (file "") + (span + (bytes 85 89) + (range 5 48 5 52) + ) + (expected "i32") + (found "bool") +) diff --git a/tests/std-vec-string-index-vector-type.diag b/tests/std-vec-string-index-vector-type.diag new file mode 100644 index 0000000..f1109ec --- /dev/null +++ b/tests/std-vec-string-index-vector-type.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code TypeMismatch) + (message "cannot call `std.vec.string.index` with argument of wrong type") + (file "") + (span + (bytes 62 63) + (range 5 25 5 26) + ) + (expected "(vec string)") + (found "i32") +) diff --git a/tests/std-vec-string-len-arity.diag b/tests/std-vec-string-len-arity.diag new file mode 100644 index 0000000..a23eea2 --- /dev/null +++ b/tests/std-vec-string-len-arity.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ArityMismatch) + (message "function `std.vec.string.len` called with wrong number of arguments") + (file "") + (span + (bytes 37 57) + (range 5 3 5 23) + ) + (expected "1") + (found "0") +) diff --git a/tests/std-vec-string-len-type.diag b/tests/std-vec-string-len-type.diag new file mode 100644 index 0000000..71cc38d --- /dev/null +++ b/tests/std-vec-string-len-type.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code TypeMismatch) + (message "cannot call `std.vec.string.len` with argument of wrong type") + (file "") + (span + (bytes 57 58) + (range 5 23 5 24) + ) + (expected "(vec string)") + (found "i32") +) diff --git a/tests/std-vec-string-one-sided-equality.diag b/tests/std-vec-string-one-sided-equality.diag new file mode 100644 index 0000000..0724685 --- /dev/null +++ b/tests/std-vec-string-one-sided-equality.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedVectorEquality) + (message "vector equality is supported only for `(vec i32)`, `(vec i64)`, `(vec f64)`, `(vec bool)`, or `(vec string)` values") + (file "") + (span + (bytes 38 66) + (range 5 3 5 31) + ) + (expected "(vec i32), (vec i64), (vec f64), (vec bool), or (vec string) on both sides") + (found "(vec string) and i32") +) diff --git a/tests/std-vec-string-push-alias.diag b/tests/std-vec-string-push-alias.diag new file mode 100644 index 0000000..eaa904b --- /dev/null +++ b/tests/std-vec-string-push-alias.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.vec.string.push` is not supported") + (file "") + (span + (bytes 47 66) + (range 5 4 5 23) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.vec.string.push") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/std-vec-string-result-context.diag b/tests/std-vec-string-result-context.diag new file mode 100644 index 0000000..750e7a1 --- /dev/null +++ b/tests/std-vec-string-result-context.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ReturnTypeMismatch) + (message "function `main` returns wrong type") + (file "") + (span + (bytes 16 60) + (range 4 1 5 26) + ) + (expected "i32") + (found "(vec string)") +) diff --git a/tests/std-vec-string-type.diag b/tests/std-vec-string-type.diag new file mode 100644 index 0000000..f416a5d --- /dev/null +++ b/tests/std-vec-string-type.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code TypeMismatch) + (message "cannot call `std.vec.string.append` with argument of wrong type") + (file "") + (span + (bytes 92 93) + (range 5 49 5 50) + ) + (expected "string") + (found "i32") +) diff --git a/tests/stdin-result.checked.lower b/tests/stdin-result.checked.lower new file mode 100644 index 0000000..53b7414 --- /dev/null +++ b/tests/stdin-result.checked.lower @@ -0,0 +1,55 @@ +program main + fn stdin_result() -> (result string i32) + call std.io.read_stdin_result : (result string i32) + fn stdin_text_or_empty(value: (result string i32)) -> string + match : string + subject + var value : (result string i32) + arm ok text + var text : string + arm err code + string "" : string + fn stdin_len_or_code(value: (result string i32)) -> i32 + match : i32 + subject + var value : (result string i32) + arm ok text + call std.string.len : i32 + var text : string + arm err code + var code : i32 + fn stdin_ok_len() -> i32 + call std.string.len : i32 + unwrap_ok : string + call stdin_result : (result string i32) + fn main() -> i32 + local let value : unit + call stdin_result : (result string i32) + if : i32 + is_ok : bool + var value : (result string i32) + call std.string.len : i32 + unwrap_ok : string + var value : (result string i32) + int 1 : i32 + test "stdin result test runner returns ok" + is_ok : bool + call stdin_result : (result string i32) + test "stdin result payload length matches match" + local let value : unit + call stdin_result : (result string i32) + binary = : bool + call std.string.len : i32 + unwrap_ok : string + var value : (result string i32) + call stdin_len_or_code : i32 + var value : (result string i32) + test "stdin result match observes ok payload" + local let value : unit + call stdin_result : (result string i32) + binary = : bool + call std.string.len : i32 + call stdin_text_or_empty : string + var value : (result string i32) + call stdin_len_or_code : i32 + var value : (result string i32) diff --git a/tests/stdin-result.slo b/tests/stdin-result.slo new file mode 100644 index 0000000..2a55836 --- /dev/null +++ b/tests/stdin-result.slo @@ -0,0 +1,38 @@ +(module main) + +(fn stdin_result () -> (result string i32) + (std.io.read_stdin_result)) + +(fn stdin_text_or_empty ((value (result string i32))) -> string + (match value + ((ok text) + text) + ((err code) + ""))) + +(fn stdin_len_or_code ((value (result string i32))) -> i32 + (match value + ((ok text) + (std.string.len text)) + ((err code) + code))) + +(fn stdin_ok_len () -> i32 + (std.string.len (unwrap_ok (stdin_result)))) + +(test "stdin result test runner returns ok" + (is_ok (stdin_result))) + +(test "stdin result payload length matches match" + (let value (result string i32) (stdin_result)) + (= (std.string.len (unwrap_ok value)) (stdin_len_or_code value))) + +(test "stdin result match observes ok payload" + (let value (result string i32) (stdin_result)) + (= (std.string.len (stdin_text_or_empty value)) (stdin_len_or_code value))) + +(fn main () -> i32 + (let value (result string i32) (stdin_result)) + (if (is_ok value) + (std.string.len (unwrap_ok value)) + 1)) diff --git a/tests/stdin-result.surface.lower b/tests/stdin-result.surface.lower new file mode 100644 index 0000000..1450b53 --- /dev/null +++ b/tests/stdin-result.surface.lower @@ -0,0 +1,55 @@ +program main + fn stdin_result() -> (result string i32) + call std.io.read_stdin_result + fn stdin_text_or_empty(value: (result string i32)) -> string + match + subject + var value + arm ok text + var text + arm err code + string "" + fn stdin_len_or_code(value: (result string i32)) -> i32 + match + subject + var value + arm ok text + call std.string.len + var text + arm err code + var code + fn stdin_ok_len() -> i32 + call std.string.len + unwrap_ok + call stdin_result + fn main() -> i32 + local let value: (result string i32) + call stdin_result + if + is_ok + var value + call std.string.len + unwrap_ok + var value + int 1 + test "stdin result test runner returns ok" + is_ok + call stdin_result + test "stdin result payload length matches match" + local let value: (result string i32) + call stdin_result + binary = + call std.string.len + unwrap_ok + var value + call stdin_len_or_code + var value + test "stdin result match observes ok payload" + local let value: (result string i32) + call stdin_result + binary = + call std.string.len + call stdin_text_or_empty + var value + call stdin_len_or_code + var value diff --git a/tests/string-len-arity.diag b/tests/string-len-arity.diag new file mode 100644 index 0000000..23f3d17 --- /dev/null +++ b/tests/string-len-arity.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ArityMismatch) + (message "function `string_len` called with wrong number of arguments") + (file "") + (span + (bytes 37 57) + (range 5 3 5 23) + ) + (expected "1") + (found "2") +) diff --git a/tests/string-len-type.diag b/tests/string-len-type.diag new file mode 100644 index 0000000..602e7de --- /dev/null +++ b/tests/string-len-type.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code TypeMismatch) + (message "cannot call `string_len` with argument of wrong type") + (file "") + (span + (bytes 49 50) + (range 5 15 5 16) + ) + (expected "string") + (found "i32") +) diff --git a/tests/string-parse-bool-result.checked.lower b/tests/string-parse-bool-result.checked.lower new file mode 100644 index 0000000..35d26b4 --- /dev/null +++ b/tests/string-parse-bool-result.checked.lower @@ -0,0 +1,99 @@ +program main + fn parse_bool(text: string) -> (result bool i32) + call std.string.parse_bool_result : (result bool i32) + var text : string + fn bool_score(text: string) -> i32 + local let value : unit + call parse_bool : (result bool i32) + var text : string + if : i32 + std.result.is_ok : bool + var value : (result bool i32) + if : i32 + std.result.unwrap_ok : bool + var value : (result bool i32) + int 1 : i32 + int 0 : i32 + std.result.unwrap_err : i32 + var value : (result bool i32) + fn main() -> i32 + local let value : unit + call parse_bool : (result bool i32) + string "true" : string + if : i32 + std.result.is_ok : bool + var value : (result bool i32) + if : i32 + std.result.unwrap_ok : bool + var value : (result bool i32) + int 0 : i32 + int 1 : i32 + std.result.unwrap_err : i32 + var value : (result bool i32) + test "parse bool true ok" + local let value : unit + call parse_bool : (result bool i32) + string "true" : string + if : bool + std.result.is_ok : bool + var value : (result bool i32) + std.result.unwrap_ok : bool + var value : (result bool i32) + bool false : bool + test "parse bool false ok" + local let value : unit + call parse_bool : (result bool i32) + string "false" : string + if : bool + std.result.is_ok : bool + var value : (result bool i32) + if : bool + std.result.unwrap_ok : bool + var value : (result bool i32) + bool false : bool + bool true : bool + bool false : bool + test "parse bool uppercase err" + local let value : unit + call parse_bool : (result bool i32) + string "TRUE" : string + if : bool + std.result.is_err : bool + var value : (result bool i32) + binary = : bool + std.result.unwrap_err : i32 + var value : (result bool i32) + int 1 : i32 + bool false : bool + test "parse bool empty err" + local let value : unit + call parse_bool : (result bool i32) + string "" : string + if : bool + std.result.is_err : bool + var value : (result bool i32) + binary = : bool + std.result.unwrap_err : i32 + var value : (result bool i32) + int 1 : i32 + bool false : bool + test "parse bool whitespace err" + local let value : unit + call parse_bool : (result bool i32) + string " true" : string + if : bool + std.result.is_err : bool + var value : (result bool i32) + binary = : bool + std.result.unwrap_err : i32 + var value : (result bool i32) + int 1 : i32 + bool false : bool + test "parse bool helper flow" + binary = : bool + binary + : i32 + call bool_score : i32 + string "true" : string + call bool_score : i32 + string "false" : string + int 1 : i32 diff --git a/tests/string-parse-bool-result.slo b/tests/string-parse-bool-result.slo new file mode 100644 index 0000000..881f083 --- /dev/null +++ b/tests/string-parse-bool-result.slo @@ -0,0 +1,55 @@ +(module main) + +(fn parse_bool ((text string)) -> (result bool i32) + (std.string.parse_bool_result text)) + +(fn bool_score ((text string)) -> i32 + (let value (result bool i32) (parse_bool text)) + (if (std.result.is_ok value) + (if (std.result.unwrap_ok value) + 1 + 0) + (std.result.unwrap_err value))) + +(test "parse bool true ok" + (let value (result bool i32) (parse_bool "true")) + (if (std.result.is_ok value) + (std.result.unwrap_ok value) + false)) + +(test "parse bool false ok" + (let value (result bool i32) (parse_bool "false")) + (if (std.result.is_ok value) + (if (std.result.unwrap_ok value) + false + true) + false)) + +(test "parse bool uppercase err" + (let value (result bool i32) (parse_bool "TRUE")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "parse bool empty err" + (let value (result bool i32) (parse_bool "")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "parse bool whitespace err" + (let value (result bool i32) (parse_bool " true")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "parse bool helper flow" + (= (+ (bool_score "true") (bool_score "false")) 1)) + +(fn main () -> i32 + (let value (result bool i32) (parse_bool "true")) + (if (std.result.is_ok value) + (if (std.result.unwrap_ok value) + 0 + 1) + (std.result.unwrap_err value))) diff --git a/tests/string-parse-bool-result.surface.lower b/tests/string-parse-bool-result.surface.lower new file mode 100644 index 0000000..d1eaca3 --- /dev/null +++ b/tests/string-parse-bool-result.surface.lower @@ -0,0 +1,99 @@ +program main + fn parse_bool(text: string) -> (result bool i32) + call std.string.parse_bool_result + var text + fn bool_score(text: string) -> i32 + local let value: (result bool i32) + call parse_bool + var text + if + std.result.is_ok + var value + if + std.result.unwrap_ok + var value + int 1 + int 0 + std.result.unwrap_err + var value + fn main() -> i32 + local let value: (result bool i32) + call parse_bool + string "true" + if + std.result.is_ok + var value + if + std.result.unwrap_ok + var value + int 0 + int 1 + std.result.unwrap_err + var value + test "parse bool true ok" + local let value: (result bool i32) + call parse_bool + string "true" + if + std.result.is_ok + var value + std.result.unwrap_ok + var value + bool false + test "parse bool false ok" + local let value: (result bool i32) + call parse_bool + string "false" + if + std.result.is_ok + var value + if + std.result.unwrap_ok + var value + bool false + bool true + bool false + test "parse bool uppercase err" + local let value: (result bool i32) + call parse_bool + string "TRUE" + if + std.result.is_err + var value + binary = + std.result.unwrap_err + var value + int 1 + bool false + test "parse bool empty err" + local let value: (result bool i32) + call parse_bool + string "" + if + std.result.is_err + var value + binary = + std.result.unwrap_err + var value + int 1 + bool false + test "parse bool whitespace err" + local let value: (result bool i32) + call parse_bool + string " true" + if + std.result.is_err + var value + binary = + std.result.unwrap_err + var value + int 1 + bool false + test "parse bool helper flow" + binary = + binary + + call bool_score + string "true" + call bool_score + string "false" + int 1 diff --git a/tests/string-parse-f64-result.checked.lower b/tests/string-parse-f64-result.checked.lower new file mode 100644 index 0000000..982a73d --- /dev/null +++ b/tests/string-parse-f64-result.checked.lower @@ -0,0 +1,72 @@ +program main + fn parse_f64(text: string) -> (result f64 i32) + call std.string.parse_f64_result : (result f64 i32) + var text : string + fn main() -> i32 + local let value : unit + call parse_f64 : (result f64 i32) + string "-0.25" : string + if : i32 + std.result.is_ok : bool + var value : (result f64 i32) + if : i32 + binary = : bool + std.result.unwrap_ok : f64 + var value : (result f64 i32) + binary - : f64 + float 0 : f64 + float 0.25 : f64 + int 0 : i32 + int 1 : i32 + std.result.unwrap_err : i32 + var value : (result f64 i32) + test "parse f64 decimal ok" + local let value : unit + call parse_f64 : (result f64 i32) + string "12.5" : string + if : bool + std.result.is_ok : bool + var value : (result f64 i32) + binary = : bool + std.result.unwrap_ok : f64 + var value : (result f64 i32) + float 12.5 : f64 + bool false : bool + test "parse f64 negative decimal ok" + local let value : unit + call parse_f64 : (result f64 i32) + string "-0.25" : string + if : bool + std.result.is_ok : bool + var value : (result f64 i32) + binary = : bool + std.result.unwrap_ok : f64 + var value : (result f64 i32) + binary - : f64 + float 0 : f64 + float 0.25 : f64 + bool false : bool + test "parse f64 text err" + local let value : unit + call parse_f64 : (result f64 i32) + string "abc" : string + if : bool + std.result.is_err : bool + var value : (result f64 i32) + binary = : bool + std.result.unwrap_err : i32 + var value : (result f64 i32) + int 1 : i32 + bool false : bool + test "parse f64 nan err" + local let value : unit + call parse_f64 : (result f64 i32) + string "nan" : string + if : bool + std.result.is_err : bool + var value : (result f64 i32) + binary = : bool + std.result.unwrap_err : i32 + var value : (result f64 i32) + int 1 : i32 + bool false : bool diff --git a/tests/string-parse-f64-result.slo b/tests/string-parse-f64-result.slo new file mode 100644 index 0000000..6d48b57 --- /dev/null +++ b/tests/string-parse-f64-result.slo @@ -0,0 +1,36 @@ +(module main) + +(fn parse_f64 ((text string)) -> (result f64 i32) + (std.string.parse_f64_result text)) + +(test "parse f64 decimal ok" + (let value (result f64 i32) (parse_f64 "12.5")) + (if (std.result.is_ok value) + (= (std.result.unwrap_ok value) 12.5) + false)) + +(test "parse f64 negative decimal ok" + (let value (result f64 i32) (parse_f64 "-0.25")) + (if (std.result.is_ok value) + (= (std.result.unwrap_ok value) (- 0.0 0.25)) + false)) + +(test "parse f64 text err" + (let value (result f64 i32) (parse_f64 "abc")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "parse f64 nan err" + (let value (result f64 i32) (parse_f64 "nan")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(fn main () -> i32 + (let value (result f64 i32) (parse_f64 "-0.25")) + (if (std.result.is_ok value) + (if (= (std.result.unwrap_ok value) (- 0.0 0.25)) + 0 + 1) + (std.result.unwrap_err value))) diff --git a/tests/string-parse-f64-result.surface.lower b/tests/string-parse-f64-result.surface.lower new file mode 100644 index 0000000..10da02d --- /dev/null +++ b/tests/string-parse-f64-result.surface.lower @@ -0,0 +1,72 @@ +program main + fn parse_f64(text: string) -> (result f64 i32) + call std.string.parse_f64_result + var text + fn main() -> i32 + local let value: (result f64 i32) + call parse_f64 + string "-0.25" + if + std.result.is_ok + var value + if + binary = + std.result.unwrap_ok + var value + binary - + float 0 + float 0.25 + int 0 + int 1 + std.result.unwrap_err + var value + test "parse f64 decimal ok" + local let value: (result f64 i32) + call parse_f64 + string "12.5" + if + std.result.is_ok + var value + binary = + std.result.unwrap_ok + var value + float 12.5 + bool false + test "parse f64 negative decimal ok" + local let value: (result f64 i32) + call parse_f64 + string "-0.25" + if + std.result.is_ok + var value + binary = + std.result.unwrap_ok + var value + binary - + float 0 + float 0.25 + bool false + test "parse f64 text err" + local let value: (result f64 i32) + call parse_f64 + string "abc" + if + std.result.is_err + var value + binary = + std.result.unwrap_err + var value + int 1 + bool false + test "parse f64 nan err" + local let value: (result f64 i32) + call parse_f64 + string "nan" + if + std.result.is_err + var value + binary = + std.result.unwrap_err + var value + int 1 + bool false diff --git a/tests/string-parse-i32-result.checked.lower b/tests/string-parse-i32-result.checked.lower new file mode 100644 index 0000000..6cd6286 --- /dev/null +++ b/tests/string-parse-i32-result.checked.lower @@ -0,0 +1,70 @@ +program main + fn parse_text(text: string) -> (result i32 i32) + call std.string.parse_i32_result : (result i32 i32) + var text : string + fn parse_stdin_text() -> (result i32 i32) + local let input : unit + call std.io.read_stdin_result : (result string i32) + match : (result i32 i32) + subject + var input : (result string i32) + arm ok text + call std.string.parse_i32_result : (result i32 i32) + var text : string + arm err code + err : (result i32 i32) + var code : i32 + fn main() -> i32 + local let value : unit + call parse_stdin_text : (result i32 i32) + if : i32 + is_ok : bool + var value : (result i32 i32) + unwrap_ok : i32 + var value : (result i32 i32) + unwrap_err : i32 + var value : (result i32 i32) + test "parse 42 ok" + binary = : bool + unwrap_ok : i32 + call parse_text : (result i32 i32) + string "42" : string + int 42 : i32 + test "parse negative 7 ok" + binary = : bool + unwrap_ok : i32 + call parse_text : (result i32 i32) + string "-7" : string + int -7 : i32 + test "parse empty err" + binary = : bool + unwrap_err : i32 + call parse_text : (result i32 i32) + string "" : string + int 1 : i32 + test "parse trailing byte err" + binary = : bool + unwrap_err : i32 + call parse_text : (result i32 i32) + string "12x" : string + int 1 : i32 + test "parse overflow err" + binary = : bool + unwrap_err : i32 + call parse_text : (result i32 i32) + string "2147483648" : string + int 1 : i32 + test "parse stdin text structurally" + local let value : unit + call parse_stdin_text : (result i32 i32) + match : bool + subject + var value : (result i32 i32) + arm ok parsed + binary = : bool + var parsed : i32 + var parsed : i32 + arm err code + binary = : bool + var code : i32 + int 1 : i32 diff --git a/tests/string-parse-i32-result.slo b/tests/string-parse-i32-result.slo new file mode 100644 index 0000000..6f02489 --- /dev/null +++ b/tests/string-parse-i32-result.slo @@ -0,0 +1,41 @@ +(module main) + +(fn parse_text ((text string)) -> (result i32 i32) + (std.string.parse_i32_result text)) + +(fn parse_stdin_text () -> (result i32 i32) + (let input (result string i32) (std.io.read_stdin_result)) + (match input + ((ok text) + (std.string.parse_i32_result text)) + ((err code) + (err i32 i32 code)))) + +(test "parse 42 ok" + (= (unwrap_ok (parse_text "42")) 42)) + +(test "parse negative 7 ok" + (= (unwrap_ok (parse_text "-7")) -7)) + +(test "parse empty err" + (= (unwrap_err (parse_text "")) 1)) + +(test "parse trailing byte err" + (= (unwrap_err (parse_text "12x")) 1)) + +(test "parse overflow err" + (= (unwrap_err (parse_text "2147483648")) 1)) + +(test "parse stdin text structurally" + (let value (result i32 i32) (parse_stdin_text)) + (match value + ((ok parsed) + (= parsed parsed)) + ((err code) + (= code 1)))) + +(fn main () -> i32 + (let value (result i32 i32) (parse_stdin_text)) + (if (is_ok value) + (unwrap_ok value) + (unwrap_err value))) diff --git a/tests/string-parse-i32-result.surface.lower b/tests/string-parse-i32-result.surface.lower new file mode 100644 index 0000000..ed80e54 --- /dev/null +++ b/tests/string-parse-i32-result.surface.lower @@ -0,0 +1,70 @@ +program main + fn parse_text(text: string) -> (result i32 i32) + call std.string.parse_i32_result + var text + fn parse_stdin_text() -> (result i32 i32) + local let input: (result string i32) + call std.io.read_stdin_result + match + subject + var input + arm ok text + call std.string.parse_i32_result + var text + arm err code + err i32 i32 + var code + fn main() -> i32 + local let value: (result i32 i32) + call parse_stdin_text + if + is_ok + var value + unwrap_ok + var value + unwrap_err + var value + test "parse 42 ok" + binary = + unwrap_ok + call parse_text + string "42" + int 42 + test "parse negative 7 ok" + binary = + unwrap_ok + call parse_text + string "-7" + int -7 + test "parse empty err" + binary = + unwrap_err + call parse_text + string "" + int 1 + test "parse trailing byte err" + binary = + unwrap_err + call parse_text + string "12x" + int 1 + test "parse overflow err" + binary = + unwrap_err + call parse_text + string "2147483648" + int 1 + test "parse stdin text structurally" + local let value: (result i32 i32) + call parse_stdin_text + match + subject + var value + arm ok parsed + binary = + var parsed + var parsed + arm err code + binary = + var code + int 1 diff --git a/tests/string-parse-i64-result.checked.lower b/tests/string-parse-i64-result.checked.lower new file mode 100644 index 0000000..b831a66 --- /dev/null +++ b/tests/string-parse-i64-result.checked.lower @@ -0,0 +1,82 @@ +program main + fn parse_i64(text: string) -> (result i64 i32) + call std.string.parse_i64_result : (result i64 i32) + var text : string + fn parsed_text(text: string) -> string + call std.num.i64_to_string : string + std.result.unwrap_ok : i64 + call parse_i64 : (result i64 i32) + var text : string + fn main() -> i32 + local let value : unit + call parse_i64 : (result i64 i32) + string "-7" : string + if : i32 + std.result.is_ok : bool + var value : (result i64 i32) + if : i32 + binary = : bool + call std.num.i64_to_string : string + std.result.unwrap_ok : i64 + var value : (result i64 i32) + string "-7" : string + int 0 : i32 + int 1 : i32 + std.result.unwrap_err : i32 + var value : (result i64 i32) + test "parse i64 zero ok" + binary = : bool + call parsed_text : string + string "0" : string + string "0" : string + test "parse i64 negative ok" + binary = : bool + call parsed_text : string + string "-7" : string + string "-7" : string + test "parse i64 low ok" + binary = : bool + call parsed_text : string + string "-9223372036854775808" : string + string "-9223372036854775808" : string + test "parse i64 high ok" + binary = : bool + call parsed_text : string + string "9223372036854775807" : string + string "9223372036854775807" : string + test "parse i64 empty err" + local let value : unit + call parse_i64 : (result i64 i32) + string "" : string + if : bool + std.result.is_err : bool + var value : (result i64 i32) + binary = : bool + std.result.unwrap_err : i32 + var value : (result i64 i32) + int 1 : i32 + bool false : bool + test "parse i64 plus err" + local let value : unit + call parse_i64 : (result i64 i32) + string "+1" : string + if : bool + std.result.is_err : bool + var value : (result i64 i32) + binary = : bool + std.result.unwrap_err : i32 + var value : (result i64 i32) + int 1 : i32 + bool false : bool + test "parse i64 above range err" + local let value : unit + call parse_i64 : (result i64 i32) + string "9223372036854775808" : string + if : bool + std.result.is_err : bool + var value : (result i64 i32) + binary = : bool + std.result.unwrap_err : i32 + var value : (result i64 i32) + int 1 : i32 + bool false : bool diff --git a/tests/string-parse-i64-result.slo b/tests/string-parse-i64-result.slo new file mode 100644 index 0000000..2b35533 --- /dev/null +++ b/tests/string-parse-i64-result.slo @@ -0,0 +1,45 @@ +(module main) + +(fn parse_i64 ((text string)) -> (result i64 i32) + (std.string.parse_i64_result text)) + +(fn parsed_text ((text string)) -> string + (std.num.i64_to_string (std.result.unwrap_ok (parse_i64 text)))) + +(test "parse i64 zero ok" + (= (parsed_text "0") "0")) + +(test "parse i64 negative ok" + (= (parsed_text "-7") "-7")) + +(test "parse i64 low ok" + (= (parsed_text "-9223372036854775808") "-9223372036854775808")) + +(test "parse i64 high ok" + (= (parsed_text "9223372036854775807") "9223372036854775807")) + +(test "parse i64 empty err" + (let value (result i64 i32) (parse_i64 "")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "parse i64 plus err" + (let value (result i64 i32) (parse_i64 "+1")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "parse i64 above range err" + (let value (result i64 i32) (parse_i64 "9223372036854775808")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(fn main () -> i32 + (let value (result i64 i32) (parse_i64 "-7")) + (if (std.result.is_ok value) + (if (= (std.num.i64_to_string (std.result.unwrap_ok value)) "-7") + 0 + 1) + (std.result.unwrap_err value))) diff --git a/tests/string-parse-i64-result.surface.lower b/tests/string-parse-i64-result.surface.lower new file mode 100644 index 0000000..4cd53bc --- /dev/null +++ b/tests/string-parse-i64-result.surface.lower @@ -0,0 +1,82 @@ +program main + fn parse_i64(text: string) -> (result i64 i32) + call std.string.parse_i64_result + var text + fn parsed_text(text: string) -> string + call std.num.i64_to_string + std.result.unwrap_ok + call parse_i64 + var text + fn main() -> i32 + local let value: (result i64 i32) + call parse_i64 + string "-7" + if + std.result.is_ok + var value + if + binary = + call std.num.i64_to_string + std.result.unwrap_ok + var value + string "-7" + int 0 + int 1 + std.result.unwrap_err + var value + test "parse i64 zero ok" + binary = + call parsed_text + string "0" + string "0" + test "parse i64 negative ok" + binary = + call parsed_text + string "-7" + string "-7" + test "parse i64 low ok" + binary = + call parsed_text + string "-9223372036854775808" + string "-9223372036854775808" + test "parse i64 high ok" + binary = + call parsed_text + string "9223372036854775807" + string "9223372036854775807" + test "parse i64 empty err" + local let value: (result i64 i32) + call parse_i64 + string "" + if + std.result.is_err + var value + binary = + std.result.unwrap_err + var value + int 1 + bool false + test "parse i64 plus err" + local let value: (result i64 i32) + call parse_i64 + string "+1" + if + std.result.is_err + var value + binary = + std.result.unwrap_err + var value + int 1 + bool false + test "parse i64 above range err" + local let value: (result i64 i32) + call parse_i64 + string "9223372036854775808" + if + std.result.is_err + var value + binary = + std.result.unwrap_err + var value + int 1 + bool false diff --git a/tests/string-parse-u32-result.checked.lower b/tests/string-parse-u32-result.checked.lower new file mode 100644 index 0000000..f9d9fc7 --- /dev/null +++ b/tests/string-parse-u32-result.checked.lower @@ -0,0 +1,84 @@ +program main + fn parse_u32(text: string) -> (result u32 i32) + call std.string.parse_u32_result : (result u32 i32) + var text : string + fn parsed_text(text: string) -> string + call std.num.u32_to_string : string + std.result.unwrap_ok : u32 + call parse_u32 : (result u32 i32) + var text : string + fn main() -> i32 + local let value : unit + call parse_u32 : (result u32 i32) + string "42" : string + if : i32 + std.result.is_ok : bool + var value : (result u32 i32) + if : i32 + binary = : bool + call std.num.u32_to_string : string + std.result.unwrap_ok : u32 + var value : (result u32 i32) + string "42" : string + int 0 : i32 + int 1 : i32 + std.result.unwrap_err : i32 + var value : (result u32 i32) + test "parse u32 zero ok" + binary = : bool + call parsed_text : string + string "0" : string + string "0" : string + test "parse u32 high ok" + binary = : bool + call parsed_text : string + string "4294967295" : string + string "4294967295" : string + test "parse u32 empty err" + local let value : unit + call parse_u32 : (result u32 i32) + string "" : string + if : bool + std.result.is_err : bool + var value : (result u32 i32) + binary = : bool + std.result.unwrap_err : i32 + var value : (result u32 i32) + int 1 : i32 + bool false : bool + test "parse u32 plus err" + local let value : unit + call parse_u32 : (result u32 i32) + string "+1" : string + if : bool + std.result.is_err : bool + var value : (result u32 i32) + binary = : bool + std.result.unwrap_err : i32 + var value : (result u32 i32) + int 1 : i32 + bool false : bool + test "parse u32 negative err" + local let value : unit + call parse_u32 : (result u32 i32) + string "-1" : string + if : bool + std.result.is_err : bool + var value : (result u32 i32) + binary = : bool + std.result.unwrap_err : i32 + var value : (result u32 i32) + int 1 : i32 + bool false : bool + test "parse u32 above range err" + local let value : unit + call parse_u32 : (result u32 i32) + string "4294967296" : string + if : bool + std.result.is_err : bool + var value : (result u32 i32) + binary = : bool + std.result.unwrap_err : i32 + var value : (result u32 i32) + int 1 : i32 + bool false : bool diff --git a/tests/string-parse-u32-result.slo b/tests/string-parse-u32-result.slo new file mode 100644 index 0000000..76fad8d --- /dev/null +++ b/tests/string-parse-u32-result.slo @@ -0,0 +1,45 @@ +(module main) + +(fn parse_u32 ((text string)) -> (result u32 i32) + (std.string.parse_u32_result text)) + +(fn parsed_text ((text string)) -> string + (std.num.u32_to_string (std.result.unwrap_ok (parse_u32 text)))) + +(test "parse u32 zero ok" + (= (parsed_text "0") "0")) + +(test "parse u32 high ok" + (= (parsed_text "4294967295") "4294967295")) + +(test "parse u32 empty err" + (let value (result u32 i32) (parse_u32 "")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "parse u32 plus err" + (let value (result u32 i32) (parse_u32 "+1")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "parse u32 negative err" + (let value (result u32 i32) (parse_u32 "-1")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "parse u32 above range err" + (let value (result u32 i32) (parse_u32 "4294967296")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(fn main () -> i32 + (let value (result u32 i32) (parse_u32 "42")) + (if (std.result.is_ok value) + (if (= (std.num.u32_to_string (std.result.unwrap_ok value)) "42") + 0 + 1) + (std.result.unwrap_err value))) diff --git a/tests/string-parse-u32-result.surface.lower b/tests/string-parse-u32-result.surface.lower new file mode 100644 index 0000000..2968f0f --- /dev/null +++ b/tests/string-parse-u32-result.surface.lower @@ -0,0 +1,84 @@ +program main + fn parse_u32(text: string) -> (result u32 i32) + call std.string.parse_u32_result + var text + fn parsed_text(text: string) -> string + call std.num.u32_to_string + std.result.unwrap_ok + call parse_u32 + var text + fn main() -> i32 + local let value: (result u32 i32) + call parse_u32 + string "42" + if + std.result.is_ok + var value + if + binary = + call std.num.u32_to_string + std.result.unwrap_ok + var value + string "42" + int 0 + int 1 + std.result.unwrap_err + var value + test "parse u32 zero ok" + binary = + call parsed_text + string "0" + string "0" + test "parse u32 high ok" + binary = + call parsed_text + string "4294967295" + string "4294967295" + test "parse u32 empty err" + local let value: (result u32 i32) + call parse_u32 + string "" + if + std.result.is_err + var value + binary = + std.result.unwrap_err + var value + int 1 + bool false + test "parse u32 plus err" + local let value: (result u32 i32) + call parse_u32 + string "+1" + if + std.result.is_err + var value + binary = + std.result.unwrap_err + var value + int 1 + bool false + test "parse u32 negative err" + local let value: (result u32 i32) + call parse_u32 + string "-1" + if + std.result.is_err + var value + binary = + std.result.unwrap_err + var value + int 1 + bool false + test "parse u32 above range err" + local let value: (result u32 i32) + call parse_u32 + string "4294967296" + if + std.result.is_err + var value + binary = + std.result.unwrap_err + var value + int 1 + bool false diff --git a/tests/string-parse-u64-result.checked.lower b/tests/string-parse-u64-result.checked.lower new file mode 100644 index 0000000..4e7dc93 --- /dev/null +++ b/tests/string-parse-u64-result.checked.lower @@ -0,0 +1,84 @@ +program main + fn parse_u64(text: string) -> (result u64 i32) + call std.string.parse_u64_result : (result u64 i32) + var text : string + fn parsed_text(text: string) -> string + call std.num.u64_to_string : string + std.result.unwrap_ok : u64 + call parse_u64 : (result u64 i32) + var text : string + fn main() -> i32 + local let value : unit + call parse_u64 : (result u64 i32) + string "42" : string + if : i32 + std.result.is_ok : bool + var value : (result u64 i32) + if : i32 + binary = : bool + call std.num.u64_to_string : string + std.result.unwrap_ok : u64 + var value : (result u64 i32) + string "42" : string + int 0 : i32 + int 1 : i32 + std.result.unwrap_err : i32 + var value : (result u64 i32) + test "parse u64 zero ok" + binary = : bool + call parsed_text : string + string "0" : string + string "0" : string + test "parse u64 high ok" + binary = : bool + call parsed_text : string + string "18446744073709551615" : string + string "18446744073709551615" : string + test "parse u64 empty err" + local let value : unit + call parse_u64 : (result u64 i32) + string "" : string + if : bool + std.result.is_err : bool + var value : (result u64 i32) + binary = : bool + std.result.unwrap_err : i32 + var value : (result u64 i32) + int 1 : i32 + bool false : bool + test "parse u64 plus err" + local let value : unit + call parse_u64 : (result u64 i32) + string "+1" : string + if : bool + std.result.is_err : bool + var value : (result u64 i32) + binary = : bool + std.result.unwrap_err : i32 + var value : (result u64 i32) + int 1 : i32 + bool false : bool + test "parse u64 negative err" + local let value : unit + call parse_u64 : (result u64 i32) + string "-1" : string + if : bool + std.result.is_err : bool + var value : (result u64 i32) + binary = : bool + std.result.unwrap_err : i32 + var value : (result u64 i32) + int 1 : i32 + bool false : bool + test "parse u64 above range err" + local let value : unit + call parse_u64 : (result u64 i32) + string "18446744073709551616" : string + if : bool + std.result.is_err : bool + var value : (result u64 i32) + binary = : bool + std.result.unwrap_err : i32 + var value : (result u64 i32) + int 1 : i32 + bool false : bool diff --git a/tests/string-parse-u64-result.slo b/tests/string-parse-u64-result.slo new file mode 100644 index 0000000..362ac5f --- /dev/null +++ b/tests/string-parse-u64-result.slo @@ -0,0 +1,45 @@ +(module main) + +(fn parse_u64 ((text string)) -> (result u64 i32) + (std.string.parse_u64_result text)) + +(fn parsed_text ((text string)) -> string + (std.num.u64_to_string (std.result.unwrap_ok (parse_u64 text)))) + +(test "parse u64 zero ok" + (= (parsed_text "0") "0")) + +(test "parse u64 high ok" + (= (parsed_text "18446744073709551615") "18446744073709551615")) + +(test "parse u64 empty err" + (let value (result u64 i32) (parse_u64 "")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "parse u64 plus err" + (let value (result u64 i32) (parse_u64 "+1")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "parse u64 negative err" + (let value (result u64 i32) (parse_u64 "-1")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(test "parse u64 above range err" + (let value (result u64 i32) (parse_u64 "18446744073709551616")) + (if (std.result.is_err value) + (= (std.result.unwrap_err value) 1) + false)) + +(fn main () -> i32 + (let value (result u64 i32) (parse_u64 "42")) + (if (std.result.is_ok value) + (if (= (std.num.u64_to_string (std.result.unwrap_ok value)) "42") + 0 + 1) + (std.result.unwrap_err value))) diff --git a/tests/string-parse-u64-result.surface.lower b/tests/string-parse-u64-result.surface.lower new file mode 100644 index 0000000..57b8b89 --- /dev/null +++ b/tests/string-parse-u64-result.surface.lower @@ -0,0 +1,84 @@ +program main + fn parse_u64(text: string) -> (result u64 i32) + call std.string.parse_u64_result + var text + fn parsed_text(text: string) -> string + call std.num.u64_to_string + std.result.unwrap_ok + call parse_u64 + var text + fn main() -> i32 + local let value: (result u64 i32) + call parse_u64 + string "42" + if + std.result.is_ok + var value + if + binary = + call std.num.u64_to_string + std.result.unwrap_ok + var value + string "42" + int 0 + int 1 + std.result.unwrap_err + var value + test "parse u64 zero ok" + binary = + call parsed_text + string "0" + string "0" + test "parse u64 high ok" + binary = + call parsed_text + string "18446744073709551615" + string "18446744073709551615" + test "parse u64 empty err" + local let value: (result u64 i32) + call parse_u64 + string "" + if + std.result.is_err + var value + binary = + std.result.unwrap_err + var value + int 1 + bool false + test "parse u64 plus err" + local let value: (result u64 i32) + call parse_u64 + string "+1" + if + std.result.is_err + var value + binary = + std.result.unwrap_err + var value + int 1 + bool false + test "parse u64 negative err" + local let value: (result u64 i32) + call parse_u64 + string "-1" + if + std.result.is_err + var value + binary = + std.result.unwrap_err + var value + int 1 + bool false + test "parse u64 above range err" + local let value: (result u64 i32) + call parse_u64 + string "18446744073709551616" + if + std.result.is_err + var value + binary = + std.result.unwrap_err + var value + int 1 + bool false diff --git a/tests/string-print.checked.lower b/tests/string-print.checked.lower new file mode 100644 index 0000000..8ebec1e --- /dev/null +++ b/tests/string-print.checked.lower @@ -0,0 +1,7 @@ +program main + fn main() -> i32 + call print_string : unit + string "hello" : string + call print_string : unit + string "line\nquote\"slash\\tab\t" : string + int 0 : i32 diff --git a/tests/string-print.slo b/tests/string-print.slo new file mode 100644 index 0000000..abd8fa5 --- /dev/null +++ b/tests/string-print.slo @@ -0,0 +1,6 @@ +(module main) + +(fn main () -> i32 + (print_string "hello") + (print_string "line\nquote\"slash\\tab\t") + 0) diff --git a/tests/string-print.surface.lower b/tests/string-print.surface.lower new file mode 100644 index 0000000..ac9126a --- /dev/null +++ b/tests/string-print.surface.lower @@ -0,0 +1,7 @@ +program main + fn main() -> i32 + call print_string + string "hello" + call print_string + string "line\nquote\"slash\\tab\t" + int 0 diff --git a/tests/string-value-flow.checked.lower b/tests/string-value-flow.checked.lower new file mode 100644 index 0000000..eacf881 --- /dev/null +++ b/tests/string-value-flow.checked.lower @@ -0,0 +1,33 @@ +program main + fn label() -> string + string "slovo" : string + fn echo(value: string) -> string + var value : string + fn local_label() -> string + local let value : unit + call label : string + var value : string + fn label_len() -> i32 + call string_len : i32 + call local_label : string + fn main() -> i32 + call print_string : unit + call local_label : string + call label_len : i32 + test "string literal equality" + binary = : bool + string "slovo" : string + string "slovo" : string + test "string parameter equality" + binary = : bool + call echo : string + string "runtime" : string + string "runtime" : string + test "string call return equality" + binary = : bool + call local_label : string + string "slovo" : string + test "string byte length" + binary = : bool + call label_len : i32 + int 5 : i32 diff --git a/tests/string-value-flow.slo b/tests/string-value-flow.slo new file mode 100644 index 0000000..8780425 --- /dev/null +++ b/tests/string-value-flow.slo @@ -0,0 +1,30 @@ +(module main) + +(fn label () -> string + "slovo") + +(fn echo ((value string)) -> string + value) + +(fn local_label () -> string + (let value string (label)) + value) + +(fn label_len () -> i32 + (string_len (local_label))) + +(test "string literal equality" + (= "slovo" "slovo")) + +(test "string parameter equality" + (= (echo "runtime") "runtime")) + +(test "string call return equality" + (= (local_label) "slovo")) + +(test "string byte length" + (= (label_len) 5)) + +(fn main () -> i32 + (print_string (local_label)) + (label_len)) diff --git a/tests/string-value-flow.surface.lower b/tests/string-value-flow.surface.lower new file mode 100644 index 0000000..df60727 --- /dev/null +++ b/tests/string-value-flow.surface.lower @@ -0,0 +1,33 @@ +program main + fn label() -> string + string "slovo" + fn echo(value: string) -> string + var value + fn local_label() -> string + local let value: string + call label + var value + fn label_len() -> i32 + call string_len + call local_label + fn main() -> i32 + call print_string + call local_label + call label_len + test "string literal equality" + binary = + string "slovo" + string "slovo" + test "string parameter equality" + binary = + call echo + string "runtime" + string "runtime" + test "string call return equality" + binary = + call local_label + string "slovo" + test "string byte length" + binary = + call label_len + int 5 diff --git a/tests/struct-field-order-mismatch.diag b/tests/struct-field-order-mismatch.diag new file mode 100644 index 0000000..c683a2d --- /dev/null +++ b/tests/struct-field-order-mismatch.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code StructConstructorFieldOrderMismatch) + (message "constructor for `Point` lists fields out of order") + (file "") + (span + (bytes 84 85) + (range 9 14 9 15) + ) + (expected "x") + (found "y") +) diff --git a/tests/struct-missing-field.diag b/tests/struct-missing-field.diag new file mode 100644 index 0000000..8050591 --- /dev/null +++ b/tests/struct-missing-field.diag @@ -0,0 +1,13 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code MissingStructField) + (message "constructor for `Point` is missing field `y`") + (file "") + (span + (bytes 76 89) + (range 9 6 9 19) + ) + (expected "field `y`") +) diff --git a/tests/struct-unknown-field.diag b/tests/struct-unknown-field.diag new file mode 100644 index 0000000..6ae900a --- /dev/null +++ b/tests/struct-unknown-field.diag @@ -0,0 +1,12 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnknownStructField) + (message "struct `Point` has no field `z`") + (file "") + (span + (bytes 90 91) + (range 9 20 9 21) + ) +) diff --git a/tests/struct-value-flow.checked.lower b/tests/struct-value-flow.checked.lower new file mode 100644 index 0000000..e582f3e --- /dev/null +++ b/tests/struct-value-flow.checked.lower @@ -0,0 +1,48 @@ +program main + struct Point + field x: i32 + field y: i32 + fn make_point(x: i32, y: i32) -> Point + construct Point : Point + field x + var x : i32 + field y + var y : i32 + fn point_x(p: Point) -> i32 + field-access x : i32 + var p : Point + fn point_sum(p: Point) -> i32 + binary + : i32 + field-access x : i32 + var p : Point + field-access y : i32 + var p : Point + fn local_point_sum() -> i32 + local let p : unit + call make_point : Point + int 20 : i32 + int 22 : i32 + call point_sum : i32 + var p : Point + fn main() -> i32 + call local_point_sum : i32 + test "struct local value flow" + binary = : bool + call local_point_sum : i32 + int 42 : i32 + test "struct parameter value flow" + binary = : bool + call point_x : i32 + call make_point : Point + int 7 : i32 + int 9 : i32 + int 7 : i32 + test "stored struct field access" + local let p : unit + call make_point : Point + int 3 : i32 + int 4 : i32 + binary = : bool + field-access y : i32 + var p : Point + int 4 : i32 diff --git a/tests/struct-value-flow.slo b/tests/struct-value-flow.slo new file mode 100644 index 0000000..6b73912 --- /dev/null +++ b/tests/struct-value-flow.slo @@ -0,0 +1,31 @@ +(module main) + +(struct Point + (x i32) + (y i32)) + +(fn make_point ((x i32) (y i32)) -> Point + (Point (x x) (y y))) + +(fn point_x ((p Point)) -> i32 + (. p x)) + +(fn point_sum ((p Point)) -> i32 + (+ (. p x) (. p y))) + +(fn local_point_sum () -> i32 + (let p Point (make_point 20 22)) + (point_sum p)) + +(test "struct local value flow" + (= (local_point_sum) 42)) + +(test "struct parameter value flow" + (= (point_x (make_point 7 9)) 7)) + +(test "stored struct field access" + (let p Point (make_point 3 4)) + (= (. p y) 4)) + +(fn main () -> i32 + (local_point_sum)) diff --git a/tests/struct-value-flow.surface.lower b/tests/struct-value-flow.surface.lower new file mode 100644 index 0000000..b6c1688 --- /dev/null +++ b/tests/struct-value-flow.surface.lower @@ -0,0 +1,48 @@ +program main + struct Point + field x: i32 + field y: i32 + fn make_point(x: i32, y: i32) -> Point + construct Point + field x + var x + field y + var y + fn point_x(p: Point) -> i32 + field-access x + var p + fn point_sum(p: Point) -> i32 + binary + + field-access x + var p + field-access y + var p + fn local_point_sum() -> i32 + local let p: Point + call make_point + int 20 + int 22 + call point_sum + var p + fn main() -> i32 + call local_point_sum + test "struct local value flow" + binary = + call local_point_sum + int 42 + test "struct parameter value flow" + binary = + call point_x + call make_point + int 7 + int 9 + int 7 + test "stored struct field access" + local let p: Point + call make_point + int 3 + int 4 + binary = + field-access y + var p + int 4 diff --git a/tests/struct.checked.lower b/tests/struct.checked.lower new file mode 100644 index 0000000..83d45aa --- /dev/null +++ b/tests/struct.checked.lower @@ -0,0 +1,33 @@ +program main + struct Point + field x: i32 + field y: i32 + fn point_sum() -> i32 + binary + : i32 + field-access x : i32 + construct Point : Point + field x + int 20 : i32 + field y + int 22 : i32 + field-access y : i32 + construct Point : Point + field x + int 20 : i32 + field y + int 22 : i32 + fn main() -> i32 + call point_sum : i32 + test "struct field access" + binary = : bool + call point_sum : i32 + int 42 : i32 + test "struct field compares" + binary = : bool + field-access y : i32 + construct Point : Point + field x + int 7 : i32 + field y + int 9 : i32 + int 9 : i32 diff --git a/tests/struct.slo b/tests/struct.slo new file mode 100644 index 0000000..1cecdcb --- /dev/null +++ b/tests/struct.slo @@ -0,0 +1,17 @@ +(module main) + +(struct Point + (x i32) + (y i32)) + +(fn point_sum () -> i32 + (+ (. (Point (x 20) (y 22)) x) (. (Point (x 20) (y 22)) y))) + +(test "struct field access" + (= (point_sum) 42)) + +(test "struct field compares" + (= (. (Point (x 7) (y 9)) y) 9)) + +(fn main () -> i32 + (point_sum)) diff --git a/tests/struct.surface.lower b/tests/struct.surface.lower new file mode 100644 index 0000000..2c9d745 --- /dev/null +++ b/tests/struct.surface.lower @@ -0,0 +1,33 @@ +program main + struct Point + field x: i32 + field y: i32 + fn point_sum() -> i32 + binary + + field-access x + construct Point + field x + int 20 + field y + int 22 + field-access y + construct Point + field x + int 20 + field y + int 22 + fn main() -> i32 + call point_sum + test "struct field access" + binary = + call point_sum + int 42 + test "struct field compares" + binary = + field-access y + construct Point + field x + int 7 + field y + int 9 + int 9 diff --git a/tests/test-duplicate-name.diag b/tests/test-duplicate-name.diag new file mode 100644 index 0000000..8a44c2c --- /dev/null +++ b/tests/test-duplicate-name.diag @@ -0,0 +1,21 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code DuplicateTestName) + (message "duplicate test name `same`") + (file "") + (span + (bytes 41 47) + (range 5 7 5 13) + ) + (hint "test names must be unique within a module") + (related + (span + (file "") + (bytes 22 28) + (range 4 7 4 13) + (message "original test name") + ) + ) +) diff --git a/tests/test-invalid-escaped-name.diag b/tests/test-invalid-escaped-name.diag new file mode 100644 index 0000000..8fcf746 --- /dev/null +++ b/tests/test-invalid-escaped-name.diag @@ -0,0 +1,13 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code InvalidTestName) + (message "test name must be non-empty printable ASCII without quotes, backslashes, or newlines") + (file "") + (span + (bytes 22 33) + (range 4 7 4 18) + ) + (expected "non-empty printable ASCII without quotes, backslashes, or newlines") +) diff --git a/tests/test-invalid-form.diag b/tests/test-invalid-form.diag new file mode 100644 index 0000000..e75accd --- /dev/null +++ b/tests/test-invalid-form.diag @@ -0,0 +1,13 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code MalformedTestForm) + (message "test body forms before the final expression must be local declarations, assignments, or while loops") + (file "") + (span + (bytes 33 37) + (range 4 18 4 22) + ) + (hint "use `(let name i32 expr)`, `(var name i32 expr)`, `(set name expr)`, or `(while condition body...)` before the final bool expression") +) diff --git a/tests/test-invalid-name.diag b/tests/test-invalid-name.diag new file mode 100644 index 0000000..f63cb7c --- /dev/null +++ b/tests/test-invalid-name.diag @@ -0,0 +1,13 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code InvalidTestName) + (message "test name must be a string literal") + (file "") + (span + (bytes 22 34) + (range 4 7 4 19) + ) + (expected "string") +) diff --git a/tests/test-non-bool.diag b/tests/test-non-bool.diag new file mode 100644 index 0000000..bc87f11 --- /dev/null +++ b/tests/test-non-bool.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code TestExpressionNotBool) + (message "test `not bool` must evaluate to bool") + (file "") + (span + (bytes 35 36) + (range 5 3 5 4) + ) + (expected "bool") + (found "i32") +) diff --git a/tests/time-sleep.checked.lower b/tests/time-sleep.checked.lower new file mode 100644 index 0000000..8abefb6 --- /dev/null +++ b/tests/time-sleep.checked.lower @@ -0,0 +1,22 @@ +program main + fn monotonic_self_equal() -> bool + local let now : unit + call std.time.monotonic_ms : i32 + binary = : bool + var now : i32 + var now : i32 + fn sleep_zero_then_self_equal() -> bool + call std.time.sleep_ms : unit + int 0 : i32 + call monotonic_self_equal : bool + fn main() -> i32 + call std.time.sleep_ms : unit + int 0 : i32 + if : i32 + call monotonic_self_equal : bool + int 0 : i32 + int 1 : i32 + test "monotonic value is self equal" + call monotonic_self_equal : bool + test "sleep zero returns" + call sleep_zero_then_self_equal : bool diff --git a/tests/time-sleep.slo b/tests/time-sleep.slo new file mode 100644 index 0000000..f01577c --- /dev/null +++ b/tests/time-sleep.slo @@ -0,0 +1,21 @@ +(module main) + +(fn monotonic_self_equal () -> bool + (let now i32 (std.time.monotonic_ms)) + (= now now)) + +(fn sleep_zero_then_self_equal () -> bool + (std.time.sleep_ms 0) + (monotonic_self_equal)) + +(test "monotonic value is self equal" + (monotonic_self_equal)) + +(test "sleep zero returns" + (sleep_zero_then_self_equal)) + +(fn main () -> i32 + (std.time.sleep_ms 0) + (if (monotonic_self_equal) + 0 + 1)) diff --git a/tests/time-sleep.surface.lower b/tests/time-sleep.surface.lower new file mode 100644 index 0000000..13ffd60 --- /dev/null +++ b/tests/time-sleep.surface.lower @@ -0,0 +1,22 @@ +program main + fn monotonic_self_equal() -> bool + local let now: i32 + call std.time.monotonic_ms + binary = + var now + var now + fn sleep_zero_then_self_equal() -> bool + call std.time.sleep_ms + int 0 + call monotonic_self_equal + fn main() -> i32 + call std.time.sleep_ms + int 0 + if + call monotonic_self_equal + int 0 + int 1 + test "monotonic value is self equal" + call monotonic_self_equal + test "sleep zero returns" + call sleep_zero_then_self_equal diff --git a/tests/top-level-test.checked.lower b/tests/top-level-test.checked.lower new file mode 100644 index 0000000..665e787 --- /dev/null +++ b/tests/top-level-test.checked.lower @@ -0,0 +1,13 @@ +program tests + fn add(a: i32, b: i32) -> i32 + binary + : i32 + var a : i32 + var b : i32 + fn main() -> i32 + int 0 : i32 + test "add works" + binary = : bool + call add : i32 + int 2 : i32 + int 3 : i32 + int 5 : i32 diff --git a/tests/top-level-test.fmt b/tests/top-level-test.fmt new file mode 100644 index 0000000..9004f81 --- /dev/null +++ b/tests/top-level-test.fmt @@ -0,0 +1,13 @@ +; status: formatter-canonical +; Scope: promoted top-level test formatter contract. + +(module tests) + +(fn add ((a i32) (b i32)) -> i32 + (+ a b)) + +(test "add works" + (= (add 2 3) 5)) + +(fn main () -> i32 + 0) diff --git a/tests/top-level-test.sexpr b/tests/top-level-test.sexpr new file mode 100644 index 0000000..352b208 --- /dev/null +++ b/tests/top-level-test.sexpr @@ -0,0 +1,36 @@ +list + ident module + ident tests +list + ident fn + ident add + list + list + ident a + ident i32 + list + ident b + ident i32 + arrow -> + ident i32 + list + ident + + ident a + ident b +list + ident test + string "add works" + list + ident = + list + ident add + int 2 + int 3 + int 5 +list + ident fn + ident main + list + arrow -> + ident i32 + int 0 diff --git a/tests/top-level-test.slo b/tests/top-level-test.slo new file mode 100644 index 0000000..284e49e --- /dev/null +++ b/tests/top-level-test.slo @@ -0,0 +1,10 @@ +(module tests) + +(fn add ((a i32) (b i32)) -> i32 + (+ a b)) + +(test "add works" + (= (add 2 3) 5)) + +(fn main () -> i32 + 0) diff --git a/tests/top-level-test.surface.lower b/tests/top-level-test.surface.lower new file mode 100644 index 0000000..d67f4a4 --- /dev/null +++ b/tests/top-level-test.surface.lower @@ -0,0 +1,13 @@ +program tests + fn add(a: i32, b: i32) -> i32 + binary + + var a + var b + fn main() -> i32 + int 0 + test "add works" + binary = + call add + int 2 + int 3 + int 5 diff --git a/tests/type-mismatch.diag b/tests/type-mismatch.diag new file mode 100644 index 0000000..226e810 --- /dev/null +++ b/tests/type-mismatch.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code TypeMismatch) + (message "cannot call `id` with argument of wrong type") + (file "") + (span + (bytes 79 83) + (range 8 7 8 11) + ) + (expected "i32") + (found "bool") +) diff --git a/tests/u32-numeric-primitive.checked.lower b/tests/u32-numeric-primitive.checked.lower new file mode 100644 index 0000000..67c83a3 --- /dev/null +++ b/tests/u32-numeric-primitive.checked.lower @@ -0,0 +1,57 @@ +program main + fn base() -> u32 + u32 1073741824 : u32 + fn adjust(value: u32, delta: u32) -> u32 + binary + : u32 + var value : u32 + var delta : u32 + fn doubled(value: u32) -> u32 + binary * : u32 + var value : u32 + u32 2 : u32 + fn local_total() -> u32 + local let offset : unit + u32 15 : u32 + call adjust : u32 + call doubled : u32 + call base : u32 + var offset : u32 + fn high_enough(value: u32) -> bool + if : bool + binary > : bool + var value : u32 + u32 2147483660 : u32 + binary < : bool + var value : u32 + u32 2147483670 : u32 + bool false : bool + fn exact_u32() -> bool + binary = : bool + call local_total : u32 + u32 2147483663 : u32 + fn main() -> i32 + call std.io.print_u32 : unit + call local_total : u32 + if : i32 + call high_enough : bool + call local_total : u32 + int 0 : i32 + int 1 : i32 + test "u32 arithmetic returns exact fixture value" + call exact_u32 : bool + test "u32 comparison works in predicates" + call high_enough : bool + call local_total : u32 + test "u32 division and ordering" + if : bool + binary >= : bool + binary / : u32 + call local_total : u32 + u32 3 : u32 + u32 715827887 : u32 + binary <= : bool + binary / : u32 + call local_total : u32 + u32 3 : u32 + u32 715827887 : u32 + bool false : bool diff --git a/tests/u32-numeric-primitive.slo b/tests/u32-numeric-primitive.slo new file mode 100644 index 0000000..672e636 --- /dev/null +++ b/tests/u32-numeric-primitive.slo @@ -0,0 +1,39 @@ +(module main) + +(fn base () -> u32 + 1073741824u32) + +(fn adjust ((value u32) (delta u32)) -> u32 + (+ value delta)) + +(fn doubled ((value u32)) -> u32 + (* value 2u32)) + +(fn local_total () -> u32 + (let offset u32 15u32) + (adjust (doubled (base)) offset)) + +(fn high_enough ((value u32)) -> bool + (if (> value 2147483660u32) + (< value 2147483670u32) + false)) + +(fn exact_u32 () -> bool + (= (local_total) 2147483663u32)) + +(fn main () -> i32 + (std.io.print_u32 (local_total)) + (if (high_enough (local_total)) + 0 + 1)) + +(test "u32 arithmetic returns exact fixture value" + (exact_u32)) + +(test "u32 comparison works in predicates" + (high_enough (local_total))) + +(test "u32 division and ordering" + (if (>= (/ (local_total) 3u32) 715827887u32) + (<= (/ (local_total) 3u32) 715827887u32) + false)) diff --git a/tests/u32-numeric-primitive.surface.lower b/tests/u32-numeric-primitive.surface.lower new file mode 100644 index 0000000..f0d6aab --- /dev/null +++ b/tests/u32-numeric-primitive.surface.lower @@ -0,0 +1,57 @@ +program main + fn base() -> u32 + u32 1073741824 + fn adjust(value: u32, delta: u32) -> u32 + binary + + var value + var delta + fn doubled(value: u32) -> u32 + binary * + var value + u32 2 + fn local_total() -> u32 + local let offset: u32 + u32 15 + call adjust + call doubled + call base + var offset + fn high_enough(value: u32) -> bool + if + binary > + var value + u32 2147483660 + binary < + var value + u32 2147483670 + bool false + fn exact_u32() -> bool + binary = + call local_total + u32 2147483663 + fn main() -> i32 + call std.io.print_u32 + call local_total + if + call high_enough + call local_total + int 0 + int 1 + test "u32 arithmetic returns exact fixture value" + call exact_u32 + test "u32 comparison works in predicates" + call high_enough + call local_total + test "u32 division and ordering" + if + binary >= + binary / + call local_total + u32 3 + u32 715827887 + binary <= + binary / + call local_total + u32 3 + u32 715827887 + bool false diff --git a/tests/u64-numeric-primitive.checked.lower b/tests/u64-numeric-primitive.checked.lower new file mode 100644 index 0000000..94b48d0 --- /dev/null +++ b/tests/u64-numeric-primitive.checked.lower @@ -0,0 +1,59 @@ +program main + fn base() -> u64 + u64 4294967296 : u64 + fn adjust(value: u64, delta: u64) -> u64 + binary + : u64 + var value : u64 + var delta : u64 + fn doubled(value: u64) -> u64 + binary * : u64 + var value : u64 + u64 2 : u64 + fn local_total() -> u64 + local let offset : unit + u64 19 : u64 + call adjust : u64 + binary / : u64 + call doubled : u64 + call base : u64 + u64 2 : u64 + var offset : u64 + fn high_enough(value: u64) -> bool + if : bool + binary > : bool + var value : u64 + u64 4294967300 : u64 + binary < : bool + var value : u64 + u64 4294967320 : u64 + bool false : bool + fn exact_u64() -> bool + binary = : bool + call local_total : u64 + u64 4294967315 : u64 + fn main() -> i32 + call std.io.print_u64 : unit + call local_total : u64 + if : i32 + call high_enough : bool + call local_total : u64 + int 0 : i32 + int 1 : i32 + test "u64 arithmetic returns exact fixture value" + call exact_u64 : bool + test "u64 comparison works in predicates" + call high_enough : bool + call local_total : u64 + test "u64 division and ordering" + if : bool + binary >= : bool + binary / : u64 + call local_total : u64 + u64 5 : u64 + u64 858993463 : u64 + binary <= : bool + binary / : u64 + call local_total : u64 + u64 5 : u64 + u64 858993463 : u64 + bool false : bool diff --git a/tests/u64-numeric-primitive.slo b/tests/u64-numeric-primitive.slo new file mode 100644 index 0000000..6d3441d --- /dev/null +++ b/tests/u64-numeric-primitive.slo @@ -0,0 +1,39 @@ +(module main) + +(fn base () -> u64 + 4294967296u64) + +(fn adjust ((value u64) (delta u64)) -> u64 + (+ value delta)) + +(fn doubled ((value u64)) -> u64 + (* value 2u64)) + +(fn local_total () -> u64 + (let offset u64 19u64) + (adjust (/ (doubled (base)) 2u64) offset)) + +(fn high_enough ((value u64)) -> bool + (if (> value 4294967300u64) + (< value 4294967320u64) + false)) + +(fn exact_u64 () -> bool + (= (local_total) 4294967315u64)) + +(fn main () -> i32 + (std.io.print_u64 (local_total)) + (if (high_enough (local_total)) + 0 + 1)) + +(test "u64 arithmetic returns exact fixture value" + (exact_u64)) + +(test "u64 comparison works in predicates" + (high_enough (local_total))) + +(test "u64 division and ordering" + (if (>= (/ (local_total) 5u64) 858993463u64) + (<= (/ (local_total) 5u64) 858993463u64) + false)) diff --git a/tests/u64-numeric-primitive.surface.lower b/tests/u64-numeric-primitive.surface.lower new file mode 100644 index 0000000..1529385 --- /dev/null +++ b/tests/u64-numeric-primitive.surface.lower @@ -0,0 +1,59 @@ +program main + fn base() -> u64 + u64 4294967296 + fn adjust(value: u64, delta: u64) -> u64 + binary + + var value + var delta + fn doubled(value: u64) -> u64 + binary * + var value + u64 2 + fn local_total() -> u64 + local let offset: u64 + u64 19 + call adjust + binary / + call doubled + call base + u64 2 + var offset + fn high_enough(value: u64) -> bool + if + binary > + var value + u64 4294967300 + binary < + var value + u64 4294967320 + bool false + fn exact_u64() -> bool + binary = + call local_total + u64 4294967315 + fn main() -> i32 + call std.io.print_u64 + call local_total + if + call high_enough + call local_total + int 0 + int 1 + test "u64 arithmetic returns exact fixture value" + call exact_u64 + test "u64 comparison works in predicates" + call high_enough + call local_total + test "u64 division and ordering" + if + binary >= + binary / + call local_total + u64 5 + u64 858993463 + binary <= + binary / + call local_total + u64 5 + u64 858993463 + bool false diff --git a/tests/unclosed-list.diag b/tests/unclosed-list.diag new file mode 100644 index 0000000..2d52b62 --- /dev/null +++ b/tests/unclosed-list.diag @@ -0,0 +1,13 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnclosedList) + (message "unclosed list") + (file "") + (span + (bytes 16 17) + (range 4 1 4 2) + ) + (hint "add a closing `)`") +) diff --git a/tests/unknown-function.diag b/tests/unknown-function.diag new file mode 100644 index 0000000..faa87f1 --- /dev/null +++ b/tests/unknown-function.diag @@ -0,0 +1,12 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnknownFunction) + (message "unknown function `missing`") + (file "") + (span + (bytes 37 48) + (range 5 3 5 14) + ) +) diff --git a/tests/unknown-struct-local.diag b/tests/unknown-struct-local.diag new file mode 100644 index 0000000..a973392 --- /dev/null +++ b/tests/unknown-struct-local.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnknownStructType) + (message "local declaration type `Missing` is not a declared struct") + (file "") + (span + (bytes 42 47) + (range 5 8 5 13) + ) + (expected "known struct") + (found "Missing") + (hint "declare the struct before using it as a type") +) diff --git a/tests/unknown-struct-parameter.diag b/tests/unknown-struct-parameter.diag new file mode 100644 index 0000000..294cb7e --- /dev/null +++ b/tests/unknown-struct-parameter.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnknownStructType) + (message "function parameter type `Missing` is not a declared struct") + (file "") + (span + (bytes 33 40) + (range 4 18 4 25) + ) + (expected "known struct") + (found "Missing") + (hint "declare the struct before using it as a type") +) diff --git a/tests/unknown-struct-return.diag b/tests/unknown-struct-return.diag new file mode 100644 index 0000000..74f7a63 --- /dev/null +++ b/tests/unknown-struct-return.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnknownStructType) + (message "function return type `Missing` is not a declared struct") + (file "") + (span + (bytes 31 38) + (range 4 16 4 23) + ) + (expected "known struct") + (found "Missing") + (hint "declare the struct before using it as a type") +) diff --git a/tests/unknown-top-level-form.diag b/tests/unknown-top-level-form.diag new file mode 100644 index 0000000..c889ef6 --- /dev/null +++ b/tests/unknown-top-level-form.diag @@ -0,0 +1,12 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnknownTopLevelForm) + (message "unknown top-level form `bogus`") + (file "") + (span + (bytes 16 33) + (range 4 1 4 18) + ) +) diff --git a/tests/unsafe-parameter-shadows-callable.diag b/tests/unsafe-parameter-shadows-callable.diag new file mode 100644 index 0000000..fd25783 --- /dev/null +++ b/tests/unsafe-parameter-shadows-callable.diag @@ -0,0 +1,13 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ParameterShadowsCallable) + (message "parameter `alloc` conflicts with a compiler-known callable name") + (file "") + (span + (bytes 27 32) + (range 4 12 4 17) + ) + (hint "choose a parameter name distinct from compiler-known callable names") +) diff --git a/tests/unsafe-required-dealloc.diag b/tests/unsafe-required-dealloc.diag new file mode 100644 index 0000000..5e9c054 --- /dev/null +++ b/tests/unsafe-required-dealloc.diag @@ -0,0 +1,21 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsafeRequired) + (message "unsafe operation `dealloc` requires an `unsafe` block") + (file "") + (span + (bytes 37 50) + (range 5 3 5 16) + ) + (hint "wrap the operation in `(unsafe ...)`") + (related + (span + (file "") + (bytes 38 45) + (range 5 4 5 11) + (message "unsafe operation head") + ) + ) +) diff --git a/tests/unsafe-required-ffi-call.diag b/tests/unsafe-required-ffi-call.diag new file mode 100644 index 0000000..ac71d84 --- /dev/null +++ b/tests/unsafe-required-ffi-call.diag @@ -0,0 +1,21 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsafeRequired) + (message "unsafe operation `ffi_call` requires an `unsafe` block") + (file "") + (span + (bytes 37 51) + (range 5 3 5 17) + ) + (hint "wrap the operation in `(unsafe ...)`") + (related + (span + (file "") + (bytes 38 46) + (range 5 4 5 12) + (message "unsafe operation head") + ) + ) +) diff --git a/tests/unsafe-required-load.diag b/tests/unsafe-required-load.diag new file mode 100644 index 0000000..5f7f7a3 --- /dev/null +++ b/tests/unsafe-required-load.diag @@ -0,0 +1,21 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsafeRequired) + (message "unsafe operation `load` requires an `unsafe` block") + (file "") + (span + (bytes 37 47) + (range 5 3 5 13) + ) + (hint "wrap the operation in `(unsafe ...)`") + (related + (span + (file "") + (bytes 38 42) + (range 5 4 5 8) + (message "unsafe operation head") + ) + ) +) diff --git a/tests/unsafe-required-operation.diag b/tests/unsafe-required-operation.diag new file mode 100644 index 0000000..ccb6b19 --- /dev/null +++ b/tests/unsafe-required-operation.diag @@ -0,0 +1,21 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsafeRequired) + (message "unsafe operation `alloc` requires an `unsafe` block") + (file "") + (span + (bytes 37 48) + (range 5 3 5 14) + ) + (hint "wrap the operation in `(unsafe ...)`") + (related + (span + (file "") + (bytes 38 43) + (range 5 4 5 9) + (message "unsafe operation head") + ) + ) +) diff --git a/tests/unsafe-required-ptr-add.diag b/tests/unsafe-required-ptr-add.diag new file mode 100644 index 0000000..d0ff8a1 --- /dev/null +++ b/tests/unsafe-required-ptr-add.diag @@ -0,0 +1,21 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsafeRequired) + (message "unsafe operation `ptr_add` requires an `unsafe` block") + (file "") + (span + (bytes 37 50) + (range 5 3 5 16) + ) + (hint "wrap the operation in `(unsafe ...)`") + (related + (span + (file "") + (bytes 38 45) + (range 5 4 5 11) + (message "unsafe operation head") + ) + ) +) diff --git a/tests/unsafe-required-reinterpret.diag b/tests/unsafe-required-reinterpret.diag new file mode 100644 index 0000000..49a43e1 --- /dev/null +++ b/tests/unsafe-required-reinterpret.diag @@ -0,0 +1,21 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsafeRequired) + (message "unsafe operation `reinterpret` requires an `unsafe` block") + (file "") + (span + (bytes 37 54) + (range 5 3 5 20) + ) + (hint "wrap the operation in `(unsafe ...)`") + (related + (span + (file "") + (bytes 38 49) + (range 5 4 5 15) + (message "unsafe operation head") + ) + ) +) diff --git a/tests/unsafe-required-store.diag b/tests/unsafe-required-store.diag new file mode 100644 index 0000000..fab7aa6 --- /dev/null +++ b/tests/unsafe-required-store.diag @@ -0,0 +1,21 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsafeRequired) + (message "unsafe operation `store` requires an `unsafe` block") + (file "") + (span + (bytes 37 48) + (range 5 3 5 14) + ) + (hint "wrap the operation in `(unsafe ...)`") + (related + (span + (file "") + (bytes 38 43) + (range 5 4 5 9) + (message "unsafe operation head") + ) + ) +) diff --git a/tests/unsafe-required-unchecked-index.diag b/tests/unsafe-required-unchecked-index.diag new file mode 100644 index 0000000..88a9999 --- /dev/null +++ b/tests/unsafe-required-unchecked-index.diag @@ -0,0 +1,21 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsafeRequired) + (message "unsafe operation `unchecked_index` requires an `unsafe` block") + (file "") + (span + (bytes 37 58) + (range 5 3 5 24) + ) + (hint "wrap the operation in `(unsafe ...)`") + (related + (span + (file "") + (bytes 38 53) + (range 5 4 5 19) + (message "unsafe operation head") + ) + ) +) diff --git a/tests/unsafe.checked.lower b/tests/unsafe.checked.lower new file mode 100644 index 0000000..a794669 --- /dev/null +++ b/tests/unsafe.checked.lower @@ -0,0 +1,22 @@ +program main + fn add_one_in_unsafe(value: i32) -> i32 + unsafe : i32 + local let one : unit + int 1 : i32 + binary + : i32 + var value : i32 + var one : i32 + fn main() -> i32 + call add_one_in_unsafe : i32 + int 41 : i32 + test "unsafe block returns final value" + binary = : bool + call add_one_in_unsafe : i32 + int 4 : i32 + int 5 : i32 + test "unsafe block can return bool" + unsafe : bool + binary = : bool + call add_one_in_unsafe : i32 + int 1 : i32 + int 2 : i32 diff --git a/tests/unsafe.slo b/tests/unsafe.slo new file mode 100644 index 0000000..ab8651c --- /dev/null +++ b/tests/unsafe.slo @@ -0,0 +1,16 @@ +(module main) + +(fn add_one_in_unsafe ((value i32)) -> i32 + (unsafe + (let one i32 1) + (+ value one))) + +(test "unsafe block returns final value" + (= (add_one_in_unsafe 4) 5)) + +(test "unsafe block can return bool" + (unsafe + (= (add_one_in_unsafe 1) 2))) + +(fn main () -> i32 + (add_one_in_unsafe 41)) diff --git a/tests/unsafe.surface.lower b/tests/unsafe.surface.lower new file mode 100644 index 0000000..8207ca0 --- /dev/null +++ b/tests/unsafe.surface.lower @@ -0,0 +1,22 @@ +program main + fn add_one_in_unsafe(value: i32) -> i32 + unsafe + local let one: i32 + int 1 + binary + + var value + var one + fn main() -> i32 + call add_one_in_unsafe + int 41 + test "unsafe block returns final value" + binary = + call add_one_in_unsafe + int 4 + int 5 + test "unsafe block can return bool" + unsafe + binary = + call add_one_in_unsafe + int 1 + int 2 diff --git a/tests/unsigned-integer-to-string.checked.lower b/tests/unsigned-integer-to-string.checked.lower new file mode 100644 index 0000000..c7a0ae7 --- /dev/null +++ b/tests/unsigned-integer-to-string.checked.lower @@ -0,0 +1,64 @@ +program main + fn u32_zero_text() -> string + call std.num.u32_to_string : string + u32 0 : u32 + fn u32_high_text() -> string + call std.num.u32_to_string : string + u32 4294967295 : u32 + fn u64_zero_text() -> string + call std.num.u64_to_string : string + u64 0 : u64 + fn u64_high_text() -> string + call std.num.u64_to_string : string + u64 18446744073709551615 : u64 + fn u64_beyond_u32_text() -> string + call std.num.u64_to_string : string + u64 4294967296 : u64 + fn main() -> i32 + call std.io.print_string : unit + call u32_zero_text : string + call std.io.print_string : unit + call u32_high_text : string + call std.io.print_string : unit + call u64_zero_text : string + call std.io.print_string : unit + call u64_high_text : string + call std.io.print_string : unit + call u64_beyond_u32_text : string + if : i32 + binary = : bool + call std.string.len : i32 + call u64_beyond_u32_text : string + int 10 : i32 + int 0 : i32 + int 1 : i32 + test "u32 zero to string" + binary = : bool + call u32_zero_text : string + string "0" : string + test "u32 high to string" + binary = : bool + call u32_high_text : string + string "4294967295" : string + test "u32 high string length" + binary = : bool + call std.string.len : i32 + call u32_high_text : string + int 10 : i32 + test "u64 zero to string" + binary = : bool + call u64_zero_text : string + string "0" : string + test "u64 high to string" + binary = : bool + call u64_high_text : string + string "18446744073709551615" : string + test "u64 beyond u32 to string" + binary = : bool + call u64_beyond_u32_text : string + string "4294967296" : string + test "u64 high string length" + binary = : bool + call std.string.len : i32 + call u64_high_text : string + int 20 : i32 diff --git a/tests/unsigned-integer-to-string.slo b/tests/unsigned-integer-to-string.slo new file mode 100644 index 0000000..1ef0efe --- /dev/null +++ b/tests/unsigned-integer-to-string.slo @@ -0,0 +1,47 @@ +(module main) + +(fn u32_zero_text () -> string + (std.num.u32_to_string 0u32)) + +(fn u32_high_text () -> string + (std.num.u32_to_string 4294967295u32)) + +(fn u64_zero_text () -> string + (std.num.u64_to_string 0u64)) + +(fn u64_high_text () -> string + (std.num.u64_to_string 18446744073709551615u64)) + +(fn u64_beyond_u32_text () -> string + (std.num.u64_to_string 4294967296u64)) + +(test "u32 zero to string" + (= (u32_zero_text) "0")) + +(test "u32 high to string" + (= (u32_high_text) "4294967295")) + +(test "u32 high string length" + (= (std.string.len (u32_high_text)) 10)) + +(test "u64 zero to string" + (= (u64_zero_text) "0")) + +(test "u64 high to string" + (= (u64_high_text) "18446744073709551615")) + +(test "u64 beyond u32 to string" + (= (u64_beyond_u32_text) "4294967296")) + +(test "u64 high string length" + (= (std.string.len (u64_high_text)) 20)) + +(fn main () -> i32 + (std.io.print_string (u32_zero_text)) + (std.io.print_string (u32_high_text)) + (std.io.print_string (u64_zero_text)) + (std.io.print_string (u64_high_text)) + (std.io.print_string (u64_beyond_u32_text)) + (if (= (std.string.len (u64_beyond_u32_text)) 10) + 0 + 1)) diff --git a/tests/unsigned-integer-to-string.surface.lower b/tests/unsigned-integer-to-string.surface.lower new file mode 100644 index 0000000..2043a88 --- /dev/null +++ b/tests/unsigned-integer-to-string.surface.lower @@ -0,0 +1,64 @@ +program main + fn u32_zero_text() -> string + call std.num.u32_to_string + u32 0 + fn u32_high_text() -> string + call std.num.u32_to_string + u32 4294967295 + fn u64_zero_text() -> string + call std.num.u64_to_string + u64 0 + fn u64_high_text() -> string + call std.num.u64_to_string + u64 18446744073709551615 + fn u64_beyond_u32_text() -> string + call std.num.u64_to_string + u64 4294967296 + fn main() -> i32 + call std.io.print_string + call u32_zero_text + call std.io.print_string + call u32_high_text + call std.io.print_string + call u64_zero_text + call std.io.print_string + call u64_high_text + call std.io.print_string + call u64_beyond_u32_text + if + binary = + call std.string.len + call u64_beyond_u32_text + int 10 + int 0 + int 1 + test "u32 zero to string" + binary = + call u32_zero_text + string "0" + test "u32 high to string" + binary = + call u32_high_text + string "4294967295" + test "u32 high string length" + binary = + call std.string.len + call u32_high_text + int 10 + test "u64 zero to string" + binary = + call u64_zero_text + string "0" + test "u64 high to string" + binary = + call u64_high_text + string "18446744073709551615" + test "u64 beyond u32 to string" + binary = + call u64_beyond_u32_text + string "4294967296" + test "u64 high string length" + binary = + call std.string.len + call u64_high_text + int 20 diff --git a/tests/unsupported-array-element-type.diag b/tests/unsupported-array-element-type.diag new file mode 100644 index 0000000..468bc22 --- /dev/null +++ b/tests/unsupported-array-element-type.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedArrayElementType) + (message "fixed arrays support direct scalar `i32`, `i64`, `u32`, `u64`, `f64`, `bool`, `string`, direct known enum, or current known non-recursive struct elements") + (file "") + (span + (bytes 51 64) + (range 5 17 5 30) + ) + (expected "i32, i64, u32, u64, f64, bool, string, direct known enum, or current known non-recursive struct type") + (found "(array i32 1)") +) diff --git a/tests/unsupported-array-equality.diag b/tests/unsupported-array-equality.diag new file mode 100644 index 0000000..267f2dc --- /dev/null +++ b/tests/unsupported-array-equality.diag @@ -0,0 +1,13 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedArrayEquality) + (message "array equality is not supported in the first-pass array feature") + (file "") + (span + (bytes 41 72) + (range 5 7 5 38) + ) + (hint "compare indexed array elements instead") +) diff --git a/tests/unsupported-array-local-mutation.diag b/tests/unsupported-array-local-mutation.diag new file mode 100644 index 0000000..5a48aae --- /dev/null +++ b/tests/unsupported-array-local-mutation.diag @@ -0,0 +1,13 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code MutableArrayLocalUnsupported) + (message "first-pass arrays can be stored only in immutable locals") + (file "") + (span + (bytes 42 48) + (range 5 8 5 14) + ) + (hint "declare array locals with `let`") +) diff --git a/tests/unsupported-array-print.diag b/tests/unsupported-array-print.diag new file mode 100644 index 0000000..bd54503 --- /dev/null +++ b/tests/unsupported-array-print.diag @@ -0,0 +1,13 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedArrayPrint) + (message "array printing is not supported in the first-pass array feature") + (file "") + (span + (bytes 48 61) + (range 5 14 5 27) + ) + (hint "index one array element and print that value instead") +) diff --git a/tests/unsupported-array-signature-element-type.diag b/tests/unsupported-array-signature-element-type.diag new file mode 100644 index 0000000..f49fd0e --- /dev/null +++ b/tests/unsupported-array-signature-element-type.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedArrayElementType) + (message "fixed arrays support direct scalar `i32`, `i64`, `u32`, `u64`, `f64`, `bool`, `string`, direct known enum, or current known non-recursive struct elements") + (file "") + (span + (bytes 35 61) + (range 4 20 4 46) + ) + (expected "i32, i64, u32, u64, f64, bool, string, direct known enum, or current known non-recursive struct type") + (found "(array string 1)") +) diff --git a/tests/unsupported-float-literal.diag b/tests/unsupported-float-literal.diag new file mode 100644 index 0000000..375fe07 --- /dev/null +++ b/tests/unsupported-float-literal.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedFloatLiteral) + (message "f64 literals must be finite in exp-20") + (file "") + (span + (bytes 52 55) + (range 5 18 5 21) + ) + (expected "finite f64 literal") + (found "NaN") + (hint "NaN and infinity semantics remain deferred") +) diff --git a/tests/unsupported-generic-vec-type.diag b/tests/unsupported-generic-vec-type.diag new file mode 100644 index 0000000..9df613b --- /dev/null +++ b/tests/unsupported-generic-vec-type.diag @@ -0,0 +1,12 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code InvalidReturnType) + (message "invalid return type") + (file "") + (span + (bytes 31 36) + (range 4 16 4 21) + ) +) diff --git a/tests/unsupported-local-type.diag b/tests/unsupported-local-type.diag new file mode 100644 index 0000000..079dcf0 --- /dev/null +++ b/tests/unsupported-local-type.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedLocalType) + (message "local variables support `i32`, `bool`, `i64`, `u32`, `u64`, `f64`, `string`, immutable direct-scalar-or-string arrays, concrete vectors, option/result, known struct values, and enum values") + (file "") + (span + (bytes 42 49) + (range 5 8 5 15) + ) + (expected "i32, bool, i64, u32, u64, f64, string, (array i32 N), (array i64 N), (array u32 N), (array u64 N), (array f64 N), (array bool N), (array string N), (vec i32), (vec i64), (vec f64), (vec bool), (vec string), option/result, known struct, or enum") + (found "unit") +) diff --git a/tests/unsupported-match-container.diag b/tests/unsupported-match-container.diag new file mode 100644 index 0000000..859d1f9 --- /dev/null +++ b/tests/unsupported-match-container.diag @@ -0,0 +1,13 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedMatchContainer) + (message "match does not support option/result values in array containers") + (file "") + (span + (bytes 51 63) + (range 5 17 5 29) + ) + (hint "match a direct `(option i32)`, `(option i64)`, `(option u32)`, `(option u64)`, `(option f64)`, `(option bool)`, `(option string)`, `(result i32 i32)`, `(result i64 i32)`, `(result u32 i32)`, `(result u64 i32)`, `(result f64 i32)`, `(result bool i32)`, or `(result string i32)` value") +) diff --git a/tests/unsupported-match-mutation.diag b/tests/unsupported-match-mutation.diag new file mode 100644 index 0000000..cc3666d --- /dev/null +++ b/tests/unsupported-match-mutation.diag @@ -0,0 +1,13 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedMatchMutation) + (message "match subject cannot be an option/result assignment") + (file "") + (span + (bytes 69 74) + (range 5 15 5 20) + ) + (hint "evaluate and match an immutable option/result value") +) diff --git a/tests/unsupported-match-payload-type.diag b/tests/unsupported-match-payload-type.diag new file mode 100644 index 0000000..5516679 --- /dev/null +++ b/tests/unsupported-match-payload-type.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedMatchPayloadType) + (message "match supports only `(option i32)`, `(option i64)`, `(option f64)`, `(option bool)`, `(option string)`, `(result i32 i32)`, `(result i64 i32)`, `(result f64 i32)`, `(result bool i32)`, and `(result string i32)` payloads") + (file "") + (span + (bytes 50 59) + (range 5 16 5 25) + ) + (expected "i32 option payloads, i64 option payloads, f64 option payloads, bool option payloads, string option payloads, i32/i32 result payloads, i64/i32 result payloads, f64/i32 result payloads, bool/i32 result payloads, or string/i32 result payloads") + (found "(vec i32)") +) diff --git a/tests/unsupported-option-parameter-payload-type.diag b/tests/unsupported-option-parameter-payload-type.diag new file mode 100644 index 0000000..cdf20dc --- /dev/null +++ b/tests/unsupported-option-parameter-payload-type.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedOptionPayloadType) + (message "first-pass options support only `i32`, `i64`, `u32`, `u64`, `f64`, `bool`, and `string` payloads") + (file "") + (span + (bytes 33 51) + (range 4 18 4 36) + ) + (expected "i32, i64, u32, u64, f64, bool, or string") + (found "(vec i32)") +) diff --git a/tests/unsupported-option-payload-type.diag b/tests/unsupported-option-payload-type.diag new file mode 100644 index 0000000..46d5464 --- /dev/null +++ b/tests/unsupported-option-payload-type.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedOptionPayloadType) + (message "first-pass options support only `i32`, `i64`, `u32`, `u64`, `f64`, `bool`, and `string` payloads") + (file "") + (span + (bytes 43 52) + (range 5 9 5 18) + ) + (expected "i32, i64, u32, u64, f64, bool, or string") + (found "(vec i32)") +) diff --git a/tests/unsupported-option-result-equality.diag b/tests/unsupported-option-result-equality.diag new file mode 100644 index 0000000..d7af262 --- /dev/null +++ b/tests/unsupported-option-result-equality.diag @@ -0,0 +1,13 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedOptionResultEquality) + (message "option/result equality is not supported in the current value-flow slice") + (file "") + (span + (bytes 41 68) + (range 5 7 5 34) + ) + (hint "observe the tag with `is_some`, `is_none`, `is_ok`, or `is_err`") +) diff --git a/tests/unsupported-option-result-print.diag b/tests/unsupported-option-result-print.diag new file mode 100644 index 0000000..00b2499 --- /dev/null +++ b/tests/unsupported-option-result-print.diag @@ -0,0 +1,13 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedOptionResultPrint) + (message "option/result printing is not supported in the current value-flow slice") + (file "") + (span + (bytes 48 58) + (range 5 14 5 24) + ) + (hint "observe the tag with `is_some`, `is_none`, `is_ok`, or `is_err`") +) diff --git a/tests/unsupported-option-return-payload-type.diag b/tests/unsupported-option-return-payload-type.diag new file mode 100644 index 0000000..6fd3f59 --- /dev/null +++ b/tests/unsupported-option-return-payload-type.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedOptionPayloadType) + (message "first-pass options support only `i32`, `i64`, `u32`, `u64`, `f64`, `bool`, and `string` payloads") + (file "") + (span + (bytes 30 48) + (range 4 15 4 33) + ) + (expected "i32, i64, u32, u64, f64, bool, or string") + (found "(vec i32)") +) diff --git a/tests/unsupported-primitive-struct-field-container.diag b/tests/unsupported-primitive-struct-field-container.diag new file mode 100644 index 0000000..4f041be --- /dev/null +++ b/tests/unsupported-primitive-struct-field-container.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedArrayElementType) + (message "fixed arrays support direct scalar `i32`, `i64`, `u32`, `u64`, `f64`, `bool`, `string`, direct known enum, or current known non-recursive struct elements") + (file "") + (span + (bytes 39 62) + (range 5 9 5 32) + ) + (expected "i32, i64, u32, u64, f64, bool, string, direct known enum, or current known non-recursive struct type") + (found "(array i32 2)") +) diff --git a/tests/unsupported-print-unit.diag b/tests/unsupported-print-unit.diag new file mode 100644 index 0000000..4474830 --- /dev/null +++ b/tests/unsupported-print-unit.diag @@ -0,0 +1,12 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnknownFunction) + (message "unknown function `print_unit`") + (file "") + (span + (bytes 37 51) + (range 5 3 5 17) + ) +) diff --git a/tests/unsupported-result-parameter-payload-type.diag b/tests/unsupported-result-parameter-payload-type.diag new file mode 100644 index 0000000..17914d6 --- /dev/null +++ b/tests/unsupported-result-parameter-payload-type.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedResultPayloadType) + (message "results currently support only `(result i32 i32)`, `(result i64 i32)`, `(result u32 i32)`, `(result u64 i32)`, `(result f64 i32)`, `(result bool i32)`, and `(result string i32)`") + (file "") + (span + (bytes 33 50) + (range 4 18 4 35) + ) + (expected "i32 ok with i32 err, i64 ok with i32 err, u32 ok with i32 err, u64 ok with i32 err, f64 ok with i32 err, bool ok with i32 err, or string ok with i32 err") + (found "bool") +) diff --git a/tests/unsupported-result-payload-type.diag b/tests/unsupported-result-payload-type.diag new file mode 100644 index 0000000..22c650b --- /dev/null +++ b/tests/unsupported-result-payload-type.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedResultPayloadType) + (message "results currently support only `(result i32 i32)`, `(result i64 i32)`, `(result u32 i32)`, `(result u64 i32)`, `(result f64 i32)`, `(result bool i32)`, and `(result string i32)`") + (file "") + (span + (bytes 45 49) + (range 5 11 5 15) + ) + (expected "i32 ok with i32 err, i64 ok with i32 err, u32 ok with i32 err, u64 ok with i32 err, f64 ok with i32 err, bool ok with i32 err, or string ok with i32 err") + (found "bool") +) diff --git a/tests/unsupported-result-return-payload-type.diag b/tests/unsupported-result-return-payload-type.diag new file mode 100644 index 0000000..87353b7 --- /dev/null +++ b/tests/unsupported-result-return-payload-type.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedResultPayloadType) + (message "results currently support only `(result i32 i32)`, `(result i64 i32)`, `(result u32 i32)`, `(result u64 i32)`, `(result f64 i32)`, `(result bool i32)`, and `(result string i32)`") + (file "") + (span + (bytes 30 47) + (range 4 15 4 32) + ) + (expected "i32 ok with i32 err, i64 ok with i32 err, u32 ok with i32 err, u64 ok with i32 err, f64 ok with i32 err, bool ok with i32 err, or string ok with i32 err") + (found "bool") +) diff --git a/tests/unsupported-result-string-bool.diag b/tests/unsupported-result-string-bool.diag new file mode 100644 index 0000000..aee105c --- /dev/null +++ b/tests/unsupported-result-string-bool.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedResultPayloadType) + (message "results currently support only `(result i32 i32)`, `(result i64 i32)`, `(result u32 i32)`, `(result u64 i32)`, `(result f64 i32)`, `(result bool i32)`, and `(result string i32)`") + (file "") + (span + (bytes 48 52) + (range 5 14 5 18) + ) + (expected "i32 ok with i32 err, i64 ok with i32 err, u32 ok with i32 err, u64 ok with i32 err, f64 ok with i32 err, bool ok with i32 err, or string ok with i32 err") + (found "bool") +) diff --git a/tests/unsupported-result-string-equality.diag b/tests/unsupported-result-string-equality.diag new file mode 100644 index 0000000..978cf1c --- /dev/null +++ b/tests/unsupported-result-string-equality.diag @@ -0,0 +1,13 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedOptionResultEquality) + (message "option/result equality is not supported in the current value-flow slice") + (file "") + (span + (bytes 41 83) + (range 5 7 5 49) + ) + (hint "observe the tag with `is_some`, `is_none`, `is_ok`, or `is_err`") +) diff --git a/tests/unsupported-result-string-print.diag b/tests/unsupported-result-string-print.diag new file mode 100644 index 0000000..54031d7 --- /dev/null +++ b/tests/unsupported-result-string-print.diag @@ -0,0 +1,13 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedOptionResultPrint) + (message "option/result printing is not supported in the current value-flow slice") + (file "") + (span + (bytes 55 74) + (range 5 21 5 40) + ) + (hint "observe the tag with `is_some`, `is_none`, `is_ok`, or `is_err`") +) diff --git a/tests/unsupported-signature-type.diag b/tests/unsupported-signature-type.diag new file mode 100644 index 0000000..070b97b --- /dev/null +++ b/tests/unsupported-signature-type.diag @@ -0,0 +1,13 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedBackendFeature) + (message "backend does not support `(ptr i32)` in function `id` signature") + (file "") + (span + (bytes 16 54) + (range 4 1 5 5) + ) + (hint "keep this type in speculative examples until layout and LLVM ABI behavior are implemented") +) diff --git a/tests/unsupported-standard-library-call.diag b/tests/unsupported-standard-library-call.diag new file mode 100644 index 0000000..71db52a --- /dev/null +++ b/tests/unsupported-standard-library-call.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStandardLibraryCall) + (message "standard library call `std.io.print_unit` is not supported") + (file "") + (span + (bytes 38 55) + (range 5 4 5 21) + ) + (expected "std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err") + (found "std.io.print_unit") + (hint "use a promoted standard-runtime name or a legacy intrinsic alias") +) diff --git a/tests/unsupported-string-array.diag b/tests/unsupported-string-array.diag new file mode 100644 index 0000000..038f898 --- /dev/null +++ b/tests/unsupported-string-array.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedArrayElementType) + (message "fixed arrays support direct scalar `i32`, `i64`, `u32`, `u64`, `f64`, `bool`, `string`, direct known enum, or current known non-recursive struct elements") + (file "") + (span + (bytes 51 67) + (range 5 17 5 33) + ) + (expected "i32, i64, u32, u64, f64, bool, string, direct known enum, or current known non-recursive struct type") + (found "(array string 1)") +) diff --git a/tests/unsupported-string-concatenation.diag b/tests/unsupported-string-concatenation.diag new file mode 100644 index 0000000..0a5ecff --- /dev/null +++ b/tests/unsupported-string-concatenation.diag @@ -0,0 +1,13 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStringConcatenation) + (message "string concatenation is not supported in the v1.2 runtime value slice") + (file "") + (span + (bytes 40 51) + (range 5 3 5 14) + ) + (hint "pass immutable string values through lets, parameters, returns, and calls without concatenating them") +) diff --git a/tests/unsupported-string-escape.diag b/tests/unsupported-string-escape.diag new file mode 100644 index 0000000..8abd543 --- /dev/null +++ b/tests/unsupported-string-escape.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStringEscape) + (message "string literal uses an unsupported escape") + (file "") + (span + (bytes 52 54) + (range 5 18 5 20) + ) + (expected "\\n, \\t, \\\", or \\\\") + (found "\\0") + (hint "the first string runtime slice supports only newline, tab, quote, and backslash escapes") +) diff --git a/tests/unsupported-string-literal.diag b/tests/unsupported-string-literal.diag new file mode 100644 index 0000000..dd36dac --- /dev/null +++ b/tests/unsupported-string-literal.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStringLiteral) + (message "string literal contains bytes outside the first runtime string slice") + (file "") + (span + (bytes 59 60) + (range 5 25 5 26) + ) + (expected "printable ASCII string literal") + (found "unsupported string literal") + (hint "use printable ASCII text plus the current \\n, \\t, \\\", and \\\\ escapes") +) diff --git a/tests/unsupported-struct-field-mutation.diag b/tests/unsupported-struct-field-mutation.diag new file mode 100644 index 0000000..2560f14 --- /dev/null +++ b/tests/unsupported-struct-field-mutation.diag @@ -0,0 +1,12 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code InvalidSetTarget) + (message "`set` target must be an identifier") + (file "") + (span + (bytes 98 105) + (range 9 8 9 15) + ) +) diff --git a/tests/unsupported-struct-field-type.diag b/tests/unsupported-struct-field-type.diag new file mode 100644 index 0000000..6063018 --- /dev/null +++ b/tests/unsupported-struct-field-type.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedStructFieldType) + (message "released struct fields support direct `i32`, `i64`, `f64`, `bool`, `string`, direct fixed arrays of those element families, direct enum fields, current concrete vec/option/result fields, and current non-recursive struct fields") + (file "") + (span + (bytes 35 39) + (range 5 6 5 10) + ) + (expected "direct `i32`, `i64`, `f64`, `bool`, `string`, direct fixed array of those element families, direct enum field, current concrete vec/option/result field, or current non-recursive struct field") + (found "unit") +) diff --git a/tests/unsupported-unit-parameter-signature.diag b/tests/unsupported-unit-parameter-signature.diag new file mode 100644 index 0000000..0cdd842 --- /dev/null +++ b/tests/unsupported-unit-parameter-signature.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedUnitSignatureType) + (message "function parameter type `unit` is unsupported") + (file "") + (span + (bytes 35 39) + (range 4 20 4 24) + ) + (expected "non-unit function parameter type") + (found "unit") + (hint "`unit` is reserved for compiler/runtime unit-producing forms") +) diff --git a/tests/unsupported-unit-return-signature.diag b/tests/unsupported-unit-return-signature.diag new file mode 100644 index 0000000..2f4d815 --- /dev/null +++ b/tests/unsupported-unit-return-signature.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedUnitSignatureType) + (message "function return type `unit` is unsupported") + (file "") + (span + (bytes 31 35) + (range 4 16 4 20) + ) + (expected "non-unit function return type") + (found "unit") + (hint "`unit` is reserved for compiler/runtime unit-producing forms") +) diff --git a/tests/unsupported-unsafe-dealloc.diag b/tests/unsupported-unsafe-dealloc.diag new file mode 100644 index 0000000..f04ea46 --- /dev/null +++ b/tests/unsupported-unsafe-dealloc.diag @@ -0,0 +1,21 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedUnsafeOperation) + (message "unsafe operation `dealloc` is outside the v1.6 unsafe contract") + (file "") + (span + (bytes 49 62) + (range 6 5 6 18) + ) + (hint "raw memory operations are not supported by v1.6 unsafe blocks") + (related + (span + (file "") + (bytes 50 57) + (range 6 6 6 13) + (message "unsafe operation head") + ) + ) +) diff --git a/tests/unsupported-unsafe-ffi-call.diag b/tests/unsupported-unsafe-ffi-call.diag new file mode 100644 index 0000000..7299664 --- /dev/null +++ b/tests/unsupported-unsafe-ffi-call.diag @@ -0,0 +1,21 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedUnsafeOperation) + (message "unsafe operation `ffi_call` is outside the v1.6 unsafe contract") + (file "") + (span + (bytes 49 63) + (range 6 5 6 19) + ) + (hint "raw memory operations are not supported by v1.6 unsafe blocks") + (related + (span + (file "") + (bytes 50 58) + (range 6 6 6 14) + (message "unsafe operation head") + ) + ) +) diff --git a/tests/unsupported-unsafe-load.diag b/tests/unsupported-unsafe-load.diag new file mode 100644 index 0000000..2953bf1 --- /dev/null +++ b/tests/unsupported-unsafe-load.diag @@ -0,0 +1,21 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedUnsafeOperation) + (message "unsafe operation `load` is outside the v1.6 unsafe contract") + (file "") + (span + (bytes 49 59) + (range 6 5 6 15) + ) + (hint "raw memory operations are not supported by v1.6 unsafe blocks") + (related + (span + (file "") + (bytes 50 54) + (range 6 6 6 10) + (message "unsafe operation head") + ) + ) +) diff --git a/tests/unsupported-unsafe-operation.diag b/tests/unsupported-unsafe-operation.diag new file mode 100644 index 0000000..0bcb20e --- /dev/null +++ b/tests/unsupported-unsafe-operation.diag @@ -0,0 +1,21 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedUnsafeOperation) + (message "unsafe operation `alloc` is outside the v1.6 unsafe contract") + (file "") + (span + (bytes 49 60) + (range 6 5 6 16) + ) + (hint "raw memory operations are not supported by v1.6 unsafe blocks") + (related + (span + (file "") + (bytes 50 55) + (range 6 6 6 11) + (message "unsafe operation head") + ) + ) +) diff --git a/tests/unsupported-unsafe-ptr-add.diag b/tests/unsupported-unsafe-ptr-add.diag new file mode 100644 index 0000000..4dc9802 --- /dev/null +++ b/tests/unsupported-unsafe-ptr-add.diag @@ -0,0 +1,21 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedUnsafeOperation) + (message "unsafe operation `ptr_add` is outside the v1.6 unsafe contract") + (file "") + (span + (bytes 49 62) + (range 6 5 6 18) + ) + (hint "raw memory operations are not supported by v1.6 unsafe blocks") + (related + (span + (file "") + (bytes 50 57) + (range 6 6 6 13) + (message "unsafe operation head") + ) + ) +) diff --git a/tests/unsupported-unsafe-reinterpret.diag b/tests/unsupported-unsafe-reinterpret.diag new file mode 100644 index 0000000..d956078 --- /dev/null +++ b/tests/unsupported-unsafe-reinterpret.diag @@ -0,0 +1,21 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedUnsafeOperation) + (message "unsafe operation `reinterpret` is outside the v1.6 unsafe contract") + (file "") + (span + (bytes 49 66) + (range 6 5 6 22) + ) + (hint "raw memory operations are not supported by v1.6 unsafe blocks") + (related + (span + (file "") + (bytes 50 61) + (range 6 6 6 17) + (message "unsafe operation head") + ) + ) +) diff --git a/tests/unsupported-unsafe-store.diag b/tests/unsupported-unsafe-store.diag new file mode 100644 index 0000000..8e50b15 --- /dev/null +++ b/tests/unsupported-unsafe-store.diag @@ -0,0 +1,21 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedUnsafeOperation) + (message "unsafe operation `store` is outside the v1.6 unsafe contract") + (file "") + (span + (bytes 49 60) + (range 6 5 6 16) + ) + (hint "raw memory operations are not supported by v1.6 unsafe blocks") + (related + (span + (file "") + (bytes 50 55) + (range 6 6 6 11) + (message "unsafe operation head") + ) + ) +) diff --git a/tests/unsupported-unsafe-unchecked-index.diag b/tests/unsupported-unsafe-unchecked-index.diag new file mode 100644 index 0000000..786e5d7 --- /dev/null +++ b/tests/unsupported-unsafe-unchecked-index.diag @@ -0,0 +1,21 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedUnsafeOperation) + (message "unsafe operation `unchecked_index` is outside the v1.6 unsafe contract") + (file "") + (span + (bytes 49 70) + (range 6 5 6 26) + ) + (hint "raw memory operations are not supported by v1.6 unsafe blocks") + (related + (span + (file "") + (bytes 50 65) + (range 6 6 6 21) + (message "unsafe operation head") + ) + ) +) diff --git a/tests/unsupported-vec-element-type.diag b/tests/unsupported-vec-element-type.diag new file mode 100644 index 0000000..a009c3d --- /dev/null +++ b/tests/unsupported-vec-element-type.diag @@ -0,0 +1,15 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedVectorElementType) + (message "vectors support only `i32`, `i64`, `f64`, `bool`, or `string` elements in the current concrete alpha slices") + (file "") + (span + (bytes 34 43) + (range 4 19 4 28) + ) + (expected "i32, i64, f64, bool, or string") + (found "f32") + (hint "use exactly `(vec i32)`, `(vec i64)`, `(vec f64)`, `(vec bool)`, or `(vec string)` in the collections alpha slices") +) diff --git a/tests/unsupported-vec-literal.diag b/tests/unsupported-vec-literal.diag new file mode 100644 index 0000000..9725959 --- /dev/null +++ b/tests/unsupported-vec-literal.diag @@ -0,0 +1,12 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnknownFunction) + (message "unknown function `vec`") + (file "") + (span + (bytes 43 54) + (range 5 3 5 14) + ) +) diff --git a/tests/unsupported-vec-nesting.diag b/tests/unsupported-vec-nesting.diag new file mode 100644 index 0000000..4a65a52 --- /dev/null +++ b/tests/unsupported-vec-nesting.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedArrayElementType) + (message "fixed arrays support direct scalar `i32`, `i64`, `u32`, `u64`, `f64`, `bool`, `string`, direct known enum, or current known non-recursive struct elements") + (file "") + (span + (bytes 34 53) + (range 4 19 4 38) + ) + (expected "i32, i64, u32, u64, f64, bool, string, direct known enum, or current known non-recursive struct type") + (found "(vec i32)") +) diff --git a/tests/unsupported-vec-option.diag b/tests/unsupported-vec-option.diag new file mode 100644 index 0000000..9195a88 --- /dev/null +++ b/tests/unsupported-vec-option.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedOptionPayloadType) + (message "first-pass options support only `i32`, `i64`, `u32`, `u64`, `f64`, `bool`, and `string` payloads") + (file "") + (span + (bytes 52 61) + (range 5 18 5 27) + ) + (expected "i32, i64, u32, u64, f64, bool, or string") + (found "(vec i32)") +) diff --git a/tests/unsupported-vec-result.diag b/tests/unsupported-vec-result.diag new file mode 100644 index 0000000..ef7acfa --- /dev/null +++ b/tests/unsupported-vec-result.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code UnsupportedResultPayloadType) + (message "results currently support only `(result i32 i32)`, `(result i64 i32)`, `(result u32 i32)`, `(result u64 i32)`, `(result f64 i32)`, `(result bool i32)`, and `(result string i32)`") + (file "") + (span + (bytes 48 57) + (range 5 14 5 23) + ) + (expected "i32 ok with i32 err, i64 ok with i32 err, u32 ok with i32 err, u64 ok with i32 err, f64 ok with i32 err, bool ok with i32 err, or string ok with i32 err") + (found "(vec i32)") +) diff --git a/tests/vec-i32.checked.lower b/tests/vec-i32.checked.lower new file mode 100644 index 0000000..441a9ee --- /dev/null +++ b/tests/vec-i32.checked.lower @@ -0,0 +1,76 @@ +program main + fn empty_values() -> (vec i32) + call std.vec.i32.empty : (vec i32) + fn pair(base: i32) -> (vec i32) + local let values : unit + call std.vec.i32.empty : (vec i32) + local let first : unit + call std.vec.i32.append : (vec i32) + var values : (vec i32) + var base : i32 + call std.vec.i32.append : (vec i32) + var first : (vec i32) + binary + : i32 + var base : i32 + int 1 : i32 + fn echo(values: (vec i32)) -> (vec i32) + var values : (vec i32) + fn length(values: (vec i32)) -> i32 + call std.vec.i32.len : i32 + var values : (vec i32) + fn at(values: (vec i32), i: i32) -> i32 + call std.vec.i32.index : i32 + var values : (vec i32) + var i : i32 + fn call_return_len() -> i32 + call std.vec.i32.len : i32 + call echo : (vec i32) + call pair : (vec i32) + int 20 : i32 + fn original_len_after_append() -> i32 + local let values : unit + call std.vec.i32.empty : (vec i32) + local let appended : unit + call std.vec.i32.append : (vec i32) + var values : (vec i32) + int 1 : i32 + call std.vec.i32.len : i32 + var values : (vec i32) + fn main() -> i32 + call std.io.print_i32 : unit + call call_return_len : i32 + call at : i32 + call pair : (vec i32) + int 40 : i32 + int 1 : i32 + test "vec i32 empty length" + binary = : bool + call std.vec.i32.len : i32 + call empty_values : (vec i32) + int 0 : i32 + test "vec i32 append length" + binary = : bool + call length : i32 + call pair : (vec i32) + int 40 : i32 + int 2 : i32 + test "vec i32 index" + binary = : bool + call at : i32 + call pair : (vec i32) + int 40 : i32 + int 1 : i32 + int 41 : i32 + test "vec i32 append is immutable" + binary = : bool + call original_len_after_append : i32 + int 0 : i32 + test "vec i32 equality" + binary = : bool + call pair : (vec i32) + int 5 : i32 + call std.vec.i32.append : (vec i32) + call std.vec.i32.append : (vec i32) + call std.vec.i32.empty : (vec i32) + int 5 : i32 + int 6 : i32 diff --git a/tests/vec-i32.slo b/tests/vec-i32.slo new file mode 100644 index 0000000..ce3cda0 --- /dev/null +++ b/tests/vec-i32.slo @@ -0,0 +1,45 @@ +(module main) + +(fn empty_values () -> (vec i32) + (std.vec.i32.empty)) + +(fn pair ((base i32)) -> (vec i32) + (let values (vec i32) (std.vec.i32.empty)) + (let first (vec i32) (std.vec.i32.append values base)) + (std.vec.i32.append first (+ base 1))) + +(fn echo ((values (vec i32))) -> (vec i32) + values) + +(fn length ((values (vec i32))) -> i32 + (std.vec.i32.len values)) + +(fn at ((values (vec i32)) (i i32)) -> i32 + (std.vec.i32.index values i)) + +(fn call_return_len () -> i32 + (std.vec.i32.len (echo (pair 20)))) + +(fn original_len_after_append () -> i32 + (let values (vec i32) (std.vec.i32.empty)) + (let appended (vec i32) (std.vec.i32.append values 1)) + (std.vec.i32.len values)) + +(test "vec i32 empty length" + (= (std.vec.i32.len (empty_values)) 0)) + +(test "vec i32 append length" + (= (length (pair 40)) 2)) + +(test "vec i32 index" + (= (at (pair 40) 1) 41)) + +(test "vec i32 append is immutable" + (= (original_len_after_append) 0)) + +(test "vec i32 equality" + (= (pair 5) (std.vec.i32.append (std.vec.i32.append (std.vec.i32.empty) 5) 6))) + +(fn main () -> i32 + (std.io.print_i32 (call_return_len)) + (at (pair 40) 1)) diff --git a/tests/vec-i32.surface.lower b/tests/vec-i32.surface.lower new file mode 100644 index 0000000..77ec967 --- /dev/null +++ b/tests/vec-i32.surface.lower @@ -0,0 +1,76 @@ +program main + fn empty_values() -> (vec i32) + call std.vec.i32.empty + fn pair(base: i32) -> (vec i32) + local let values: (vec i32) + call std.vec.i32.empty + local let first: (vec i32) + call std.vec.i32.append + var values + var base + call std.vec.i32.append + var first + binary + + var base + int 1 + fn echo(values: (vec i32)) -> (vec i32) + var values + fn length(values: (vec i32)) -> i32 + call std.vec.i32.len + var values + fn at(values: (vec i32), i: i32) -> i32 + call std.vec.i32.index + var values + var i + fn call_return_len() -> i32 + call std.vec.i32.len + call echo + call pair + int 20 + fn original_len_after_append() -> i32 + local let values: (vec i32) + call std.vec.i32.empty + local let appended: (vec i32) + call std.vec.i32.append + var values + int 1 + call std.vec.i32.len + var values + fn main() -> i32 + call std.io.print_i32 + call call_return_len + call at + call pair + int 40 + int 1 + test "vec i32 empty length" + binary = + call std.vec.i32.len + call empty_values + int 0 + test "vec i32 append length" + binary = + call length + call pair + int 40 + int 2 + test "vec i32 index" + binary = + call at + call pair + int 40 + int 1 + int 41 + test "vec i32 append is immutable" + binary = + call original_len_after_append + int 0 + test "vec i32 equality" + binary = + call pair + int 5 + call std.vec.i32.append + call std.vec.i32.append + call std.vec.i32.empty + int 5 + int 6 diff --git a/tests/while-body-local.diag b/tests/while-body-local.diag new file mode 100644 index 0000000..782bb39 --- /dev/null +++ b/tests/while-body-local.diag @@ -0,0 +1,13 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code LocalDeclarationInWhileBodyUnsupported) + (message "local declarations are not allowed inside `while` bodies") + (file "") + (span + (bytes 53 66) + (range 6 5 6 18) + ) + (hint "declare loop locals before the `while` form") +) diff --git a/tests/while-body-non-unit.diag b/tests/while-body-non-unit.diag new file mode 100644 index 0000000..1f3a016 --- /dev/null +++ b/tests/while-body-non-unit.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code WhileBodyFormNotUnit) + (message "`while` body forms must produce unit") + (file "") + (span + (bytes 53 54) + (range 6 5 6 6) + ) + (expected "unit") + (found "i32") +) diff --git a/tests/while-condition-not-bool.diag b/tests/while-condition-not-bool.diag new file mode 100644 index 0000000..52ebf4b --- /dev/null +++ b/tests/while-condition-not-bool.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code WhileConditionNotBool) + (message "`while` condition must be bool") + (file "") + (span + (bytes 44 45) + (range 5 10 5 11) + ) + (expected "bool") + (found "i32") +) diff --git a/tests/while-final-function.diag b/tests/while-final-function.diag new file mode 100644 index 0000000..9b7db22 --- /dev/null +++ b/tests/while-final-function.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ReturnTypeMismatch) + (message "function `main` returns wrong type") + (file "") + (span + (bytes 16 70) + (range 4 1 7 2) + ) + (expected "i32") + (found "unit") +) diff --git a/tests/while-final-test.diag b/tests/while-final-test.diag new file mode 100644 index 0000000..9f7a7b6 --- /dev/null +++ b/tests/while-final-test.diag @@ -0,0 +1,14 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code TestExpressionNotBool) + (message "test `loop final` must evaluate to bool") + (file "") + (span + (bytes 53 80) + (range 6 3 7 15) + ) + (expected "bool") + (found "unit") +) diff --git a/tests/while.checked.lower b/tests/while.checked.lower new file mode 100644 index 0000000..7bb8374 --- /dev/null +++ b/tests/while.checked.lower @@ -0,0 +1,42 @@ +program main + fn count_to(limit: i32) -> i32 + local var i : unit + int 0 : i32 + while : unit + binary < : bool + var i : i32 + var limit : i32 + set i : unit + binary + : i32 + var i : i32 + int 1 : i32 + var i : i32 + fn main() -> i32 + call count_to : i32 + int 4 : i32 + test "while counts" + local var i : unit + int 0 : i32 + while : unit + binary < : bool + var i : i32 + int 3 : i32 + set i : unit + binary + : i32 + var i : i32 + int 1 : i32 + binary = : bool + var i : i32 + int 3 : i32 + test "while false skips" + local var i : unit + int 0 : i32 + while : unit + bool false : bool + set i : unit + binary + : i32 + var i : i32 + int 1 : i32 + binary = : bool + var i : i32 + int 0 : i32 diff --git a/tests/while.slo b/tests/while.slo new file mode 100644 index 0000000..6c60d5b --- /dev/null +++ b/tests/while.slo @@ -0,0 +1,22 @@ +(module main) + +(fn count_to ((limit i32)) -> i32 + (var i i32 0) + (while (< i limit) + (set i (+ i 1))) + i) + +(test "while counts" + (var i i32 0) + (while (< i 3) + (set i (+ i 1))) + (= i 3)) + +(test "while false skips" + (var i i32 0) + (while false + (set i (+ i 1))) + (= i 0)) + +(fn main () -> i32 + (count_to 4)) diff --git a/tests/while.surface.lower b/tests/while.surface.lower new file mode 100644 index 0000000..4ee6458 --- /dev/null +++ b/tests/while.surface.lower @@ -0,0 +1,42 @@ +program main + fn count_to(limit: i32) -> i32 + local var i: i32 + int 0 + while + binary < + var i + var limit + set i + binary + + var i + int 1 + var i + fn main() -> i32 + call count_to + int 4 + test "while counts" + local var i: i32 + int 0 + while + binary < + var i + int 3 + set i + binary + + var i + int 1 + binary = + var i + int 3 + test "while false skips" + local var i: i32 + int 0 + while + bool false + set i + binary + + var i + int 1 + binary = + var i + int 0 diff --git a/tests/zero-length-array-type.diag b/tests/zero-length-array-type.diag new file mode 100644 index 0000000..035f75f --- /dev/null +++ b/tests/zero-length-array-type.diag @@ -0,0 +1,13 @@ +(diagnostic + (schema slovo.diagnostic) + (version 1) + (severity error) + (code ZeroLengthArrayUnsupported) + (message "first-pass arrays must have positive length") + (file "") + (span + (bytes 42 48) + (range 5 8 5 14) + ) + (hint "use one or more supported direct scalar, string, enum, or struct elements") +)